| 1 | import { createSlice, current, prepareAutoBatched } from '@reduxjs/toolkit';
|
|---|
| 2 | import { castDraft } from 'immer';
|
|---|
| 3 |
|
|---|
| 4 | /**
|
|---|
| 5 | * One Tooltip can display multiple TooltipPayloadEntries at a time.
|
|---|
| 6 | */
|
|---|
| 7 |
|
|---|
| 8 | /**
|
|---|
| 9 | * So what happens is that the tooltip payload is decided based on the available data, and the dataKey.
|
|---|
| 10 | * The dataKey can either be defined on the graphical element (like Line, or Bar)
|
|---|
| 11 | * or on the tooltip itself.
|
|---|
| 12 | *
|
|---|
| 13 | * The data can be defined in the chart element, or in the graphical item.
|
|---|
| 14 | *
|
|---|
| 15 | * So this type is all the settings, other than the data + dataKey complications.
|
|---|
| 16 | */
|
|---|
| 17 |
|
|---|
| 18 | /**
|
|---|
| 19 | * This is what Tooltip renders.
|
|---|
| 20 | */
|
|---|
| 21 |
|
|---|
| 22 | /**
|
|---|
| 23 | * null means no active index
|
|---|
| 24 | * string means: whichever index from the chart data it is.
|
|---|
| 25 | * Different charts have different requirements on data shapes,
|
|---|
| 26 | * and are also responsible for providing a function that will accept this index
|
|---|
| 27 | * and return data.
|
|---|
| 28 | */
|
|---|
| 29 |
|
|---|
| 30 | /**
|
|---|
| 31 | * Different items have different data shapes so the state has no opinion on what the data shape should be;
|
|---|
| 32 | * the only requirement is that the chart also provides a searcher function
|
|---|
| 33 | * that accepts the data, and a key, and returns whatever the payload in Tooltip should be.
|
|---|
| 34 | */
|
|---|
| 35 |
|
|---|
| 36 | /**
|
|---|
| 37 | * So this informs the "tooltip event type". Tooltip event type can be either "axis" or "item"
|
|---|
| 38 | * and it is used for two things:
|
|---|
| 39 | * 1. Sets the active area
|
|---|
| 40 | * 2. Sets the background and cursor highlights
|
|---|
| 41 | *
|
|---|
| 42 | * Some charts only allow to have one type of tooltip event type, some allow both.
|
|---|
| 43 | * Those charts that allow both will have one default, and the "shared" prop will be used to switch between them.
|
|---|
| 44 | * Undefined means "use the chart default".
|
|---|
| 45 | *
|
|---|
| 46 | * Charts that only allow one tooltip event type, will ignore the shared prop.
|
|---|
| 47 | */
|
|---|
| 48 |
|
|---|
| 49 | /**
|
|---|
| 50 | * A generic state for user interaction with the chart.
|
|---|
| 51 | * User interaction can come through multiple channels: mouse events, keyboard events, or hardcoded in props, or synchronised from other charts.
|
|---|
| 52 | *
|
|---|
| 53 | * Each of the interaction states is represented as TooltipInteractionState,
|
|---|
| 54 | * and then the selectors and Tooltip will decide which of the interaction states to use.
|
|---|
| 55 | */
|
|---|
| 56 |
|
|---|
| 57 | export var noInteraction = {
|
|---|
| 58 | active: false,
|
|---|
| 59 | index: null,
|
|---|
| 60 | dataKey: undefined,
|
|---|
| 61 | graphicalItemId: undefined,
|
|---|
| 62 | coordinate: undefined
|
|---|
| 63 | };
|
|---|
| 64 |
|
|---|
| 65 | /**
|
|---|
| 66 | * The tooltip interaction state stores:
|
|---|
| 67 | *
|
|---|
| 68 | * - Which graphical item is user interacting with at the moment,
|
|---|
| 69 | * - which axis (or, which part of chart background) is user interacting with at the moment
|
|---|
| 70 | * - The data that individual graphical items wish to be displayed in case the tooltip gets activated
|
|---|
| 71 | */
|
|---|
| 72 |
|
|---|
| 73 | export var initialState = {
|
|---|
| 74 | itemInteraction: {
|
|---|
| 75 | click: noInteraction,
|
|---|
| 76 | hover: noInteraction
|
|---|
| 77 | },
|
|---|
| 78 | axisInteraction: {
|
|---|
| 79 | click: noInteraction,
|
|---|
| 80 | hover: noInteraction
|
|---|
| 81 | },
|
|---|
| 82 | keyboardInteraction: noInteraction,
|
|---|
| 83 | syncInteraction: {
|
|---|
| 84 | active: false,
|
|---|
| 85 | index: null,
|
|---|
| 86 | dataKey: undefined,
|
|---|
| 87 | label: undefined,
|
|---|
| 88 | coordinate: undefined,
|
|---|
| 89 | sourceViewBox: undefined,
|
|---|
| 90 | graphicalItemId: undefined
|
|---|
| 91 | },
|
|---|
| 92 | tooltipItemPayloads: [],
|
|---|
| 93 | settings: {
|
|---|
| 94 | shared: undefined,
|
|---|
| 95 | trigger: 'hover',
|
|---|
| 96 | axisId: 0,
|
|---|
| 97 | active: false,
|
|---|
| 98 | defaultIndex: undefined
|
|---|
| 99 | }
|
|---|
| 100 | };
|
|---|
| 101 |
|
|---|
| 102 | /**
|
|---|
| 103 | * This is the event we get when user is interacting with a specific graphical item.
|
|---|
| 104 | */
|
|---|
| 105 |
|
|---|
| 106 | /**
|
|---|
| 107 | * Keyboard interaction payload has no graphical item ID,
|
|---|
| 108 | * and no dataKey, because keyboard interaction is always
|
|---|
| 109 | * with the whole chart, not with a specific graphical item.
|
|---|
| 110 | */
|
|---|
| 111 |
|
|---|
| 112 | var tooltipSlice = createSlice({
|
|---|
| 113 | name: 'tooltip',
|
|---|
| 114 | initialState,
|
|---|
| 115 | reducers: {
|
|---|
| 116 | addTooltipEntrySettings: {
|
|---|
| 117 | reducer(state, action) {
|
|---|
| 118 | state.tooltipItemPayloads.push(castDraft(action.payload));
|
|---|
| 119 | },
|
|---|
| 120 | prepare: prepareAutoBatched()
|
|---|
| 121 | },
|
|---|
| 122 | replaceTooltipEntrySettings: {
|
|---|
| 123 | reducer(state, action) {
|
|---|
| 124 | var {
|
|---|
| 125 | prev,
|
|---|
| 126 | next
|
|---|
| 127 | } = action.payload;
|
|---|
| 128 | var index = current(state).tooltipItemPayloads.indexOf(castDraft(prev));
|
|---|
| 129 | if (index > -1) {
|
|---|
| 130 | state.tooltipItemPayloads[index] = castDraft(next);
|
|---|
| 131 | }
|
|---|
| 132 | },
|
|---|
| 133 | prepare: prepareAutoBatched()
|
|---|
| 134 | },
|
|---|
| 135 | removeTooltipEntrySettings: {
|
|---|
| 136 | reducer(state, action) {
|
|---|
| 137 | var index = current(state).tooltipItemPayloads.indexOf(castDraft(action.payload));
|
|---|
| 138 | if (index > -1) {
|
|---|
| 139 | state.tooltipItemPayloads.splice(index, 1);
|
|---|
| 140 | }
|
|---|
| 141 | },
|
|---|
| 142 | prepare: prepareAutoBatched()
|
|---|
| 143 | },
|
|---|
| 144 | setTooltipSettingsState(state, action) {
|
|---|
| 145 | state.settings = action.payload;
|
|---|
| 146 | },
|
|---|
| 147 | setActiveMouseOverItemIndex(state, action) {
|
|---|
| 148 | state.syncInteraction.active = false;
|
|---|
| 149 | state.keyboardInteraction.active = false;
|
|---|
| 150 | state.itemInteraction.hover.active = true;
|
|---|
| 151 | state.itemInteraction.hover.index = action.payload.activeIndex;
|
|---|
| 152 | state.itemInteraction.hover.dataKey = action.payload.activeDataKey;
|
|---|
| 153 | state.itemInteraction.hover.graphicalItemId = action.payload.activeGraphicalItemId;
|
|---|
| 154 | state.itemInteraction.hover.coordinate = action.payload.activeCoordinate;
|
|---|
| 155 | },
|
|---|
| 156 | mouseLeaveChart(state) {
|
|---|
| 157 | /*
|
|---|
| 158 | * Clear only the active flags. Why?
|
|---|
| 159 | * 1. Keep Coordinate to preserve animation - next time the Tooltip appears, we want to render it from
|
|---|
| 160 | * the last place where it was when it disappeared.
|
|---|
| 161 | * 2. We want to keep all the properties anyway just in case the tooltip has `active=true` prop
|
|---|
| 162 | * and continues being visible even after the mouse has left the chart.
|
|---|
| 163 | */
|
|---|
| 164 | state.itemInteraction.hover.active = false;
|
|---|
| 165 | state.axisInteraction.hover.active = false;
|
|---|
| 166 | },
|
|---|
| 167 | mouseLeaveItem(state) {
|
|---|
| 168 | state.itemInteraction.hover.active = false;
|
|---|
| 169 | },
|
|---|
| 170 | setActiveClickItemIndex(state, action) {
|
|---|
| 171 | state.syncInteraction.active = false;
|
|---|
| 172 | state.itemInteraction.click.active = true;
|
|---|
| 173 | state.keyboardInteraction.active = false;
|
|---|
| 174 | state.itemInteraction.click.index = action.payload.activeIndex;
|
|---|
| 175 | state.itemInteraction.click.dataKey = action.payload.activeDataKey;
|
|---|
| 176 | state.itemInteraction.click.graphicalItemId = action.payload.activeGraphicalItemId;
|
|---|
| 177 | state.itemInteraction.click.coordinate = action.payload.activeCoordinate;
|
|---|
| 178 | },
|
|---|
| 179 | setMouseOverAxisIndex(state, action) {
|
|---|
| 180 | state.syncInteraction.active = false;
|
|---|
| 181 | state.axisInteraction.hover.active = true;
|
|---|
| 182 | state.keyboardInteraction.active = false;
|
|---|
| 183 | state.axisInteraction.hover.index = action.payload.activeIndex;
|
|---|
| 184 | state.axisInteraction.hover.dataKey = action.payload.activeDataKey;
|
|---|
| 185 | state.axisInteraction.hover.coordinate = action.payload.activeCoordinate;
|
|---|
| 186 | },
|
|---|
| 187 | setMouseClickAxisIndex(state, action) {
|
|---|
| 188 | state.syncInteraction.active = false;
|
|---|
| 189 | state.keyboardInteraction.active = false;
|
|---|
| 190 | state.axisInteraction.click.active = true;
|
|---|
| 191 | state.axisInteraction.click.index = action.payload.activeIndex;
|
|---|
| 192 | state.axisInteraction.click.dataKey = action.payload.activeDataKey;
|
|---|
| 193 | state.axisInteraction.click.coordinate = action.payload.activeCoordinate;
|
|---|
| 194 | },
|
|---|
| 195 | setSyncInteraction(state, action) {
|
|---|
| 196 | state.syncInteraction = action.payload;
|
|---|
| 197 | },
|
|---|
| 198 | setKeyboardInteraction(state, action) {
|
|---|
| 199 | state.keyboardInteraction.active = action.payload.active;
|
|---|
| 200 | state.keyboardInteraction.index = action.payload.activeIndex;
|
|---|
| 201 | state.keyboardInteraction.coordinate = action.payload.activeCoordinate;
|
|---|
| 202 | }
|
|---|
| 203 | }
|
|---|
| 204 | });
|
|---|
| 205 | export var {
|
|---|
| 206 | addTooltipEntrySettings,
|
|---|
| 207 | replaceTooltipEntrySettings,
|
|---|
| 208 | removeTooltipEntrySettings,
|
|---|
| 209 | setTooltipSettingsState,
|
|---|
| 210 | setActiveMouseOverItemIndex,
|
|---|
| 211 | mouseLeaveItem,
|
|---|
| 212 | mouseLeaveChart,
|
|---|
| 213 | setActiveClickItemIndex,
|
|---|
| 214 | setMouseOverAxisIndex,
|
|---|
| 215 | setMouseClickAxisIndex,
|
|---|
| 216 | setSyncInteraction,
|
|---|
| 217 | setKeyboardInteraction
|
|---|
| 218 | } = tooltipSlice.actions;
|
|---|
| 219 | export var tooltipReducer = tooltipSlice.reducer; |
|---|