@oliasoft-open-source/charts-library 2.11.3 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oliasoft-open-source/charts-library",
3
- "version": "2.11.3",
3
+ "version": "2.13.0",
4
4
  "description": "React Chart Library (based on Chart.js and react-chart-js-2)",
5
5
  "homepage": "https://gitlab.com/oliasoft-open-source/charts-library",
6
6
  "bugs": {
package/release-notes.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Charts Library Release Notes
2
2
 
3
+ ## 2.13.0
4
+
5
+ - Added feature to save state to localStorage and clean when expired, fixed panEnable(state) bug.
6
+
7
+ ## 2.12.0
8
+
9
+ - Add unit selector and depth selector to axes options ([OW-9496](https://oliasoft.atlassian.net/browse/OW-9496))
10
+
3
11
  ## 2.11.3
4
12
 
5
13
  - Submit Axes option on keyboard Enter ([OW-10995](https://oliasoft.atlassian.net/browse/OW-10995))
@@ -8,6 +8,7 @@ import {
8
8
  export const actionTypes = Object.freeze({
9
9
  reset: 'reset',
10
10
  valueUpdated: 'valueUpdated',
11
+ unitUpdated: 'unitUpdated',
11
12
  });
12
13
 
13
14
  /**
@@ -25,6 +26,7 @@ export const initializeFormState = ({ initialAxesRanges, axes = [] }) => {
25
26
  id: initialAxisRange.id,
26
27
  min: currentAxisRange?.min ?? initialAxisRange?.min,
27
28
  max: currentAxisRange?.max ?? initialAxisRange?.max,
29
+ unit: currentAxisRange?.unit,
28
30
  };
29
31
  });
30
32
  };
@@ -80,8 +82,8 @@ export const validateAxes = (formState) => {
80
82
  export const reducer = (state, action) => {
81
83
  switch (action.type) {
82
84
  case actionTypes.reset: {
83
- const { initialAxesRanges } = action.payload;
84
- return initializeFormState({ initialAxesRanges });
85
+ const { initialAxesRanges, axes } = action.payload;
86
+ return initializeFormState({ initialAxesRanges, axes });
85
87
  }
86
88
  case actionTypes.valueUpdated:
87
89
  return produce(state, (draft) => {
@@ -89,6 +91,12 @@ export const reducer = (state, action) => {
89
91
  const axis = draft.find((a) => a.id === id);
90
92
  axis[name] = value;
91
93
  });
94
+ case actionTypes.unitUpdated:
95
+ return produce(state, (draft) => {
96
+ const { name, value, id } = action.payload;
97
+ const axis = draft.find((a) => a.id === id);
98
+ axis.unit[name] = value;
99
+ });
92
100
  default:
93
101
  return state;
94
102
  }
@@ -1,12 +1,15 @@
1
- import React, { useReducer } from 'react';
1
+ import React, { useReducer, useState } from 'react';
2
2
  import {
3
3
  Button,
4
+ ButtonGroup,
4
5
  Field,
5
6
  Flex,
6
7
  Input,
7
8
  InputGroup,
8
9
  InputGroupAddon,
9
10
  Popover,
11
+ Select,
12
+ Spacer,
10
13
  Text,
11
14
  Tooltip,
12
15
  } from '@oliasoft-open-source/react-ui-library';
@@ -26,7 +29,11 @@ const AxesOptionsPopover = ({
26
29
  onUpdateAxes,
27
30
  onResetAxes,
28
31
  close,
32
+ depthType,
29
33
  }) => {
34
+ const [depthTypeState, setDepthTypeState] = useState(
35
+ depthType?.selectedDepthType,
36
+ );
30
37
  const [formState, dispatch] = useReducer(
31
38
  reducer,
32
39
  {
@@ -45,6 +52,13 @@ const AxesOptionsPopover = ({
45
52
  });
46
53
  };
47
54
 
55
+ const onEditUnit = ({ name, value, id }) => {
56
+ dispatch({
57
+ type: actionTypes.unitUpdated,
58
+ payload: { name, value, id },
59
+ });
60
+ };
61
+
48
62
  const onDone = (e) => {
49
63
  e.preventDefault();
50
64
  if (valid) {
@@ -56,6 +70,17 @@ const AxesOptionsPopover = ({
56
70
  onUpdateAxes({
57
71
  axes: sanitizedFormState,
58
72
  });
73
+
74
+ //update units
75
+ sanitizedFormState.map((el, i) => {
76
+ if (el.unit?.selectedUnit) {
77
+ axes[i].unit.setSelectedUnit(el.unit.selectedUnit);
78
+ }
79
+ });
80
+ //update depthType
81
+ if (depthType) {
82
+ depthType.setSelectedDepthType(depthTypeState);
83
+ }
59
84
  }
60
85
  close();
61
86
  };
@@ -66,8 +91,16 @@ const AxesOptionsPopover = ({
66
91
  type: actionTypes.reset,
67
92
  payload: { axes, initialAxesRanges },
68
93
  });
94
+ //update units
95
+ initialAxesRanges.map((el, i) => {
96
+ if (el?.unit?.selectedUnit) {
97
+ axes[i].unit.setSelectedUnit(el.unit.selectedUnit);
98
+ }
99
+ });
100
+
69
101
  //reset parent state
70
102
  onResetAxes();
103
+ close();
71
104
  };
72
105
 
73
106
  const isCustomValue = axes.filter((axis) => axis.max || axis.min).length > 0;
@@ -83,6 +116,7 @@ const AxesOptionsPopover = ({
83
116
  const max = formState.find((a) => a.id === axis.id)?.max;
84
117
  const minError = errors?.find((a) => a.id === axis.id)?.min?.[0];
85
118
  const maxError = errors?.find((a) => a.id === axis.id)?.max?.[0];
119
+ const unit = formState.find((a) => a.id === axis.id)?.unit;
86
120
  return (
87
121
  <Field key={i} label={axisLabel || axis.id || ''}>
88
122
  <InputGroup small>
@@ -117,10 +151,43 @@ const AxesOptionsPopover = ({
117
151
  }
118
152
  onFocus={handleInputFocus}
119
153
  />
154
+ {axis.unit ? (
155
+ <Select
156
+ name="selectedUnit"
157
+ options={axis?.unit?.options}
158
+ value={unit?.selectedUnit}
159
+ onChange={(e) => {
160
+ onEditUnit({
161
+ name: e.target.name,
162
+ value: e.target.value,
163
+ id: axis.id,
164
+ });
165
+ }}
166
+ width="auto"
167
+ />
168
+ ) : null}
120
169
  </InputGroup>
121
170
  </Field>
122
171
  );
123
172
  })}
173
+
174
+ {depthType?.options?.length > 0 ? (
175
+ <>
176
+ <ButtonGroup
177
+ items={depthType.options.map((depth, i) => ({
178
+ key: i,
179
+ label: depth,
180
+ }))}
181
+ onSelected={(key) => {
182
+ setDepthTypeState(depthType.options[key]);
183
+ }}
184
+ small
185
+ value={depthType.options.indexOf(depthTypeState)}
186
+ />
187
+ <Spacer />
188
+ </>
189
+ ) : null}
190
+
124
191
  <Flex gap="8px" alignItems="center">
125
192
  <Button type="submit" small colored label="Done" disabled={!valid} />
126
193
  <Button
@@ -144,6 +211,7 @@ export const AxesOptions = ({
144
211
  controlsAxesLabels,
145
212
  onUpdateAxes,
146
213
  onResetAxes,
214
+ depthType,
147
215
  }) => {
148
216
  return (
149
217
  <Popover
@@ -156,6 +224,7 @@ export const AxesOptions = ({
156
224
  controlsAxesLabels={controlsAxesLabels}
157
225
  onUpdateAxes={onUpdateAxes}
158
226
  onResetAxes={onResetAxes}
227
+ depthType={depthType}
159
228
  />
160
229
  }
161
230
  >
@@ -34,6 +34,7 @@ const Controls = ({
34
34
  subheaderComponent,
35
35
  table,
36
36
  zoomEnabled,
37
+ depthType,
37
38
  enableDragPoints,
38
39
  isDragDataAllowed,
39
40
  onToggleDragPoints,
@@ -53,6 +54,7 @@ const Controls = ({
53
54
  controlsAxesLabels={controlsAxesLabels}
54
55
  onUpdateAxes={onUpdateAxes}
55
56
  onResetAxes={onResetAxes}
57
+ depthType={depthType}
56
58
  />
57
59
  <LineOptions
58
60
  lineEnabled={lineEnabled}
@@ -1,10 +1,13 @@
1
- export const getAxesRangesFromChart = (chartRef) => {
1
+ export const getAxesRangesFromChart = (chartRef, axes) => {
2
2
  const { scales = {} } = chartRef.current || {};
3
- return Object.entries(scales).map(([key, { min, max }]) => {
3
+ return Object.entries(scales).map(([key, { min, max }], i) => {
4
+ const axesArray = axes.x.concat(axes.y);
5
+ const unit = axesArray?.[i]?.unit;
4
6
  return {
5
7
  id: key,
6
8
  min,
7
9
  max,
10
+ ...(unit ? { unit } : {}),
8
11
  };
9
12
  });
10
13
  };
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
3
3
  export const LineChartPropTypes = {
4
4
  table: PropTypes.node,
5
5
  chart: PropTypes.shape({
6
+ persistenceId: PropTypes.string,
6
7
  testId: PropTypes.string,
7
8
  data: PropTypes.object.isRequired,
8
9
  options: PropTypes.shape({
@@ -16,6 +17,11 @@ export const LineChartPropTypes = {
16
17
  label: PropTypes.string,
17
18
  position: PropTypes.oneOf(['top', 'bottom']),
18
19
  color: PropTypes.string,
20
+ unit: PropTypes.shape({
21
+ options: PropTypes.arrayOf(PropTypes.string),
22
+ selectedUnit: PropTypes.string,
23
+ setSelectedUnit: PropTypes.func,
24
+ }),
19
25
  }),
20
26
  ),
21
27
  y: PropTypes.arrayOf(
@@ -106,6 +112,11 @@ export const LineChartPropTypes = {
106
112
  onPointUnhover: PropTypes.func,
107
113
  onAnimationComplete: PropTypes.func,
108
114
  }),
115
+ depthType: PropTypes.shape({
116
+ options: PropTypes.arrayOf(PropTypes.string),
117
+ selectedDepthType: PropTypes.string,
118
+ setSelectedDepthType: PropTypes.func,
119
+ }),
109
120
  dragData: PropTypes.shape({
110
121
  enableDragData: PropTypes.bool,
111
122
  showTooltip: PropTypes.bool,
@@ -138,6 +149,7 @@ export const getDefaultProps = (props) => {
138
149
  props.chart.options.dragData = props.chart.options.dragData || {};
139
150
  // Set defaults for missing properties
140
151
  const chart = {
152
+ persistenceId: props.chart.persistenceId ?? '',
141
153
  testId: props.chart.testId ?? null,
142
154
  data: props.chart.data,
143
155
  options: {
@@ -217,6 +229,7 @@ export const getDefaultProps = (props) => {
217
229
  onAnimationComplete:
218
230
  props.chart.options.interactions.onAnimationComplete,
219
231
  },
232
+ depthType: props.chart.options.depthType,
220
233
  dragData: {
221
234
  enableDragData: props.chart.options.dragData.enableDragData || false,
222
235
  showTooltip: props.chart.options.dragData.showTooltip || true,
@@ -56,3 +56,15 @@ export const generateAxisId = (axisType, index = 0, hasMultiAxes = false) => {
56
56
  export const getAxisTypeFromKey = (string) => {
57
57
  return string.match(/[^0-9/]+/gi)[0];
58
58
  };
59
+
60
+ /**
61
+ * Generates a key based on an array of values. The key changes
62
+ * if any of the values in the array change.
63
+ *
64
+ * @param {Array} values - The array of values to base the key on.
65
+ * @returns {String} The key.
66
+ */
67
+ export const generateKey = (values) => {
68
+ const key = values.join('');
69
+ return key;
70
+ };
@@ -46,10 +46,17 @@ export interface ILineChartAdditionalAxesOptions {
46
46
  autoAxisPadding: boolean;
47
47
  }
48
48
 
49
+ export interface IUnitOptions {
50
+ options: string[],
51
+ selectedUnit: string,
52
+ setSelectedUnit: () => void,
53
+ }
54
+
49
55
  export interface ILineChartAxis<PositionType> {
50
56
  label: string;
51
57
  position: PositionType;
52
58
  color: string;
59
+ unit: IUnitOptions
53
60
  }
54
61
 
55
62
  export interface ILineChartAxes {
@@ -57,6 +64,11 @@ export interface ILineChartAxes {
57
64
  y: ILineChartAxis<'left' | 'right'>[];
58
65
  }
59
66
 
67
+ export interface IDepthType {
68
+ options: string[],
69
+ selectedUnit: string,
70
+ setSelectedUnit: () => void,
71
+ }
60
72
  export interface ILineChartOptions {
61
73
  title: string | string[];
62
74
  axes: ILineChartAxes;
@@ -68,6 +80,7 @@ export interface ILineChartOptions {
68
80
  legend: IChartLegend;
69
81
  chartOptions: IChartOptions;
70
82
  interactions: IChartInteractions;
83
+ depthType: IDepthType
71
84
  draggableData :IChartDraggableData;
72
85
  }
73
86
 
@@ -1,4 +1,10 @@
1
- import React, { useEffect, useReducer, useRef, useState } from 'react';
1
+ import React, {
2
+ useEffect,
3
+ useLayoutEffect,
4
+ useReducer,
5
+ useRef,
6
+ useState,
7
+ } from 'react';
2
8
  import {
3
9
  CategoryScale,
4
10
  Chart as ChartJS,
@@ -21,19 +27,18 @@ import styles from './line-chart.module.less';
21
27
  import { reducer } from './state/line-chart-reducer';
22
28
  import initialState from './state/initial-state';
23
29
  import {
24
- SET_POINTS_ZOOM_DEFAULTS,
30
+ DISABLE_DRAG_OPTIONS,
31
+ RESET_AXES_RANGES,
32
+ SAVE_INITIAL_AXES_RANGES,
25
33
  TOGGLE_ANNOTATION,
34
+ TOGGLE_DRAG_POINTS,
26
35
  TOGGLE_LEGEND,
27
36
  TOGGLE_LINE,
28
37
  TOGGLE_PAN,
29
38
  TOGGLE_POINTS,
30
39
  TOGGLE_TABLE,
31
40
  TOGGLE_ZOOM,
32
- SAVE_INITIAL_AXES_RANGES,
33
41
  UPDATE_AXES_RANGES,
34
- RESET_AXES_RANGES,
35
- TOGGLE_DRAG_POINTS,
36
- DISABLE_DRAG_OPTIONS,
37
42
  } from './state/action-types';
38
43
  import Controls from '../controls/controls';
39
44
  import { getDefaultProps, LineChartPropTypes } from './line-chart-prop-types';
@@ -61,9 +66,9 @@ import {
61
66
  import {
62
67
  ANIMATION_DURATION,
63
68
  AUTO,
69
+ BORDER_COLOR,
64
70
  COLORS,
65
71
  CUSTOM_LEGEND_PLUGIN_NAME,
66
- BORDER_COLOR,
67
72
  } from '../../helpers/chart-consts';
68
73
  import {
69
74
  AxisType,
@@ -73,9 +78,10 @@ import {
73
78
  PointStyle,
74
79
  } from '../../helpers/enums';
75
80
  import getDraggableData from '../../helpers/get-draggableData';
76
- import { getAxesRangesFromChart } from './get-axes-ranges-from-chart';
77
- import { generateAxisId } from './line-chart-utils';
81
+ import { generateAxisId, generateKey } from './line-chart-utils';
78
82
  import { autoScale } from './axis-scales/axis-scales';
83
+ import useChartState from './state/use-chart-state';
84
+ import { getAxesRangesFromChart } from './get-axes-ranges-from-chart';
79
85
 
80
86
  ChartJS.register(
81
87
  LinearScale,
@@ -102,20 +108,19 @@ const LineChart = (props) => {
102
108
  const chartRef = useRef(null);
103
109
  const [hoveredPoint, setHoveredPoint] = useState(null);
104
110
  const chart = getDefaultProps(props);
105
- const { options, testId } = chart;
111
+ const { options, testId, persistenceId } = chart;
106
112
  const { headerComponent, subheaderComponent, table } = props;
107
113
  const {
108
114
  additionalAxesOptions,
109
115
  annotations,
110
116
  axes,
111
- chartOptions,
112
117
  chartStyling,
113
118
  graph,
114
119
  interactions,
115
120
  legend,
121
+ depthType,
116
122
  dragData,
117
123
  } = options;
118
- const { showLine, showPoints, enableZoom, enablePan } = chartOptions;
119
124
 
120
125
  /**
121
126
  * @type {[object, import('react').Dispatch<{type: String, payload: any}>]} useReducer
@@ -123,19 +128,13 @@ const LineChart = (props) => {
123
128
  const [state, dispatch] = useReducer(
124
129
  reducer,
125
130
  {
126
- axes,
127
- enableZoom: chartOptions.enableZoom,
128
- enablePan: chartOptions.enablePan,
129
- showPoints: chartOptions.showPoints,
130
- showLine: chartOptions.showLine,
131
- legendDisplay: legend.display,
132
- annotationsData: annotations.annotationsData,
133
- customAxesRange: additionalAxesOptions?.range,
131
+ options,
132
+ persistenceId,
134
133
  },
135
134
  initialState,
136
135
  );
137
136
 
138
- useEffect(() => {
137
+ useLayoutEffect(() => {
139
138
  const { range } = props.chart.options.additionalAxesOptions;
140
139
  if (range?.x && range?.y) {
141
140
  const axes = Object.entries(range).map(([key, { min, max }]) => {
@@ -154,41 +153,17 @@ const LineChart = (props) => {
154
153
  }, [props.chart.options]);
155
154
 
156
155
  useEffect(() => {
157
- if (chartOptions.enablePan !== true) {
158
- dispatch({ type: TOGGLE_PAN });
159
- }
160
156
  if (chartRef) {
161
157
  //save the initial axis ranges in state (we need this for resetting ranges)
162
158
  dispatch({
163
159
  type: SAVE_INITIAL_AXES_RANGES,
164
- payload: { initialAxesRanges: getAxesRangesFromChart(chartRef) },
160
+ payload: { initialAxesRanges: getAxesRangesFromChart(chartRef, axes) },
165
161
  });
166
162
  }
167
163
  }, []);
168
164
 
169
- useEffect(() => {
170
- dispatch({
171
- type: SET_POINTS_ZOOM_DEFAULTS,
172
- payload: { showLine, showPoints, enableZoom, enablePan },
173
- });
174
- }, [showLine, showPoints, enableZoom, enablePan]);
175
-
176
- /**
177
- * Toggle custom legends visibility.
178
- * Needed because they are rendered in a html element separate from the chart.
179
- */
180
- useEffect(() => {
181
- if (options.legend.customLegend.customLegendPlugin) {
182
- const parent = document.getElementById(
183
- options.legend.customLegend.customLegendContainerID,
184
- );
185
- if (state.legendEnabled) {
186
- parent.style.visibility = 'visible';
187
- } else {
188
- parent.style.visibility = 'hidden';
189
- }
190
- }
191
- }, [state.legendEnabled]);
165
+ // Call the custom hook.
166
+ useChartState(options, state, persistenceId);
192
167
 
193
168
  const generateLineChartDatasets = (datasets) => {
194
169
  const copyDataset = [...datasets];
@@ -364,11 +339,15 @@ const LineChart = (props) => {
364
339
  const axisType = i ? AxisType.Y : AxisType.X; // only first element is 'x' - rest is 'y'
365
340
  const min = axis.min ?? additionalAxesOptions?.range?.[axisType]?.min;
366
341
  const max = axis.max ?? additionalAxesOptions?.range?.[axisType]?.max;
342
+ // we need to get all axes element to array to set unit.
343
+ const axesArray = axes.x.concat(axes.y);
344
+ const unit = axesArray?.[i]?.unit;
367
345
  return {
368
346
  ...axis,
369
347
  //only add min and max properties if they are defined:
370
348
  ...(min ? { min } : {}),
371
349
  ...(max ? { max } : {}),
350
+ ...(unit ? { unit } : {}),
372
351
  };
373
352
  });
374
353
  };
@@ -455,6 +434,7 @@ const LineChart = (props) => {
455
434
  subheaderComponent={subheaderComponent}
456
435
  table={table}
457
436
  zoomEnabled={state.zoomEnabled}
437
+ depthType={depthType}
458
438
  enableDragPoints={state.enableDragPoints}
459
439
  isDragDataAllowed={dragData.enableDragData}
460
440
  onToggleDragPoints={() => dispatch({ type: TOGGLE_DRAG_POINTS })}
@@ -465,7 +445,11 @@ const LineChart = (props) => {
465
445
  ) : (
466
446
  <div className={styles.canvas}>
467
447
  <Line
468
- key={state.enableDragPoints}
448
+ key={generateKey([
449
+ state.enableDragPoints,
450
+ state.zoomEnabled,
451
+ state.panEnabled,
452
+ ])}
469
453
  ref={chartRef}
470
454
  data={{
471
455
  datasets: generatedDatasets,
@@ -3,7 +3,6 @@ export const TOGGLE_PAN = 'TOGGLE_PAN';
3
3
  export const TOGGLE_POINTS = 'TOGGLE_POINTS';
4
4
  export const TOGGLE_LINE = 'TOGGLE_LINE';
5
5
  export const TOGGLE_LEGEND = 'TOGGLE_LEGEND';
6
- export const SET_POINTS_ZOOM_DEFAULTS = 'SET_POINTS_ZOOM_DEFAULTS';
7
6
  export const TOGGLE_ANNOTATION = 'TOGGLE_ANNOTATION';
8
7
  export const TOGGLE_TABLE = 'TOGGLE_TABLE';
9
8
  export const SAVE_INITIAL_AXES_RANGES = 'SAVE_INITIAL_AXES_RANGES';
@@ -1,17 +1,24 @@
1
1
  import { AxisType } from '../../../helpers/enums';
2
2
  import { setAnnotations } from '../../../helpers/chart-utils';
3
3
  import { generateAxisId } from '../line-chart-utils';
4
+ import { getChartStateFromStorage } from './manage-state-in-local-storage';
4
5
 
5
- const initialState = ({
6
- axes,
7
- enableZoom,
8
- enablePan,
9
- showPoints,
10
- showLine,
11
- legendDisplay,
12
- annotationsData,
13
- customAxesRange,
14
- }) => {
6
+ /**
7
+
8
+ Initial chart state for the line chart.
9
+ @param {Object} options - The chart options.
10
+ @return {Object} The initial chart state.
11
+ */
12
+ const initialState = ({ options, persistenceId }) => {
13
+ const {
14
+ additionalAxesOptions: { range: customAxesRange },
15
+ annotations,
16
+ axes,
17
+ chartOptions,
18
+ legend,
19
+ dragData,
20
+ } = options;
21
+ const { enableZoom, enablePan, showPoints, showLine } = chartOptions;
15
22
  /**
16
23
  * getStateAxesByType
17
24
  * @param {'x'|'y'} axisType
@@ -28,23 +35,27 @@ const initialState = ({
28
35
  const id = generateAxisId(axisType, index, axes[axisType].length > 1);
29
36
  const customMin = customAxesRange?.[id]?.min;
30
37
  const customMax = customAxesRange?.[id]?.max;
38
+ const { unit } = axisObj;
31
39
  return {
32
40
  id,
33
41
  //only add custom axis ranges if defined:
34
42
  ...(customMin ? { min: customMin } : {}),
35
43
  ...(customMax ? { max: customMax } : {}),
44
+ ...(unit ? { unit } : {}),
36
45
  };
37
46
  });
38
47
  } else {
39
48
  const id = generateAxisId(axisType);
40
49
  const customMin = customAxesRange?.[id]?.min;
41
50
  const customMax = customAxesRange?.[id]?.max;
51
+ const unit = axes?.[id][0].unit;
42
52
  return [
43
53
  {
44
54
  id,
45
55
  //only add custom axis ranges if defined:
46
56
  ...(customMin ? { min: customMin } : {}),
47
57
  ...(customMax ? { max: customMax } : {}),
58
+ ...(unit ? { unit } : {}),
48
59
  },
49
60
  ];
50
61
  }
@@ -54,16 +65,25 @@ const initialState = ({
54
65
  const yStateAxes = getStateAxesByType(AxisType.Y, customAxesRange);
55
66
  const stateAxes = [...xStateAxes, ...yStateAxes];
56
67
 
68
+ const {
69
+ zoomEnabled,
70
+ panEnabled,
71
+ pointsEnabled,
72
+ lineEnabled,
73
+ legendEnabled,
74
+ enableDragPoints,
75
+ } = getChartStateFromStorage(persistenceId) || {};
76
+
57
77
  return {
58
- zoomEnabled: enableZoom,
59
- panEnabled: true, // TODO: work out why this logic doesn't work, workaround wih dispatch is currently used
60
- pointsEnabled: showPoints,
61
- lineEnabled: showLine,
62
- legendEnabled: legendDisplay !== false,
78
+ zoomEnabled: zoomEnabled ?? enableZoom,
79
+ panEnabled: panEnabled ?? enablePan,
80
+ pointsEnabled: pointsEnabled ?? showPoints,
81
+ lineEnabled: lineEnabled ?? showLine,
82
+ legendEnabled: legendEnabled !== false ?? legend.display !== false,
63
83
  axes: stateAxes,
64
- showAnnotationLineIndex: setAnnotations(annotationsData),
84
+ showAnnotationLineIndex: setAnnotations(annotations.annotationsData),
65
85
  showTable: false,
66
- enableDragPoints: false,
86
+ enableDragPoints: dragData.enableDragData && enableDragPoints,
67
87
  };
68
88
  };
69
89
 
@@ -3,7 +3,6 @@ import {
3
3
  DISABLE_DRAG_OPTIONS,
4
4
  RESET_AXES_RANGES,
5
5
  SAVE_INITIAL_AXES_RANGES,
6
- SET_POINTS_ZOOM_DEFAULTS,
7
6
  TOGGLE_ANNOTATION,
8
7
  TOGGLE_DRAG_POINTS,
9
8
  TOGGLE_LEGEND,
@@ -79,15 +78,6 @@ export const reducer = (state, action) => {
79
78
  }));
80
79
  });
81
80
  }
82
- case SET_POINTS_ZOOM_DEFAULTS:
83
- const { showPoints, enableZoom, enablePan, showLine } = action.payload;
84
- return {
85
- ...newState,
86
- zoomEnabled: enableZoom,
87
- pointsEnabled: showPoints,
88
- lineEnabled: showLine,
89
- panEnabled: enablePan,
90
- };
91
81
  case TOGGLE_ANNOTATION:
92
82
  const { annotationIndex } = action.payload;
93
83
  const updatedIndexes = newState.showAnnotationLineIndex.includes(
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Retrieves the chart state from local storage.
3
+ *
4
+ * @param {string|''} persistenceId - The chart persistenceId.
5
+ * @returns {object|null} The chart state object or null if not found.
6
+ */
7
+ export const getChartStateFromStorage = (persistenceId) => {
8
+ if (!persistenceId) return null;
9
+
10
+ // Retrieve the chart state object from local storage
11
+ const chartStateKey = `line-chart-state-${persistenceId}`;
12
+ const chartStateObjectJSON = localStorage.getItem(chartStateKey);
13
+
14
+ if (chartStateObjectJSON) {
15
+ // If the chart state object was found, parse it
16
+ const chartStateObject = JSON.parse(chartStateObjectJSON);
17
+
18
+ // Return the state property of the parsed chart state object
19
+ return chartStateObject.state;
20
+ }
21
+
22
+ // If the chart state object was not found, return null
23
+ return null;
24
+ };
25
+
26
+ /**
27
+ * Remove expired chart states from local storage.
28
+ *
29
+ * @param {number} maxAgeInHours - The maximum age of chart states to keep in local storage (in hours). Default is 72 hours.
30
+ */
31
+ const removeExpiredChartStates = (maxAgeInHours = 72) => {
32
+ const currentTime = new Date().getTime();
33
+ const maxAgeInMilliseconds = maxAgeInHours * 60 * 60 * 1000;
34
+
35
+ // Iterate through all keys in local storage
36
+ for (let i = 0; i < localStorage.length; i++) {
37
+ const key = localStorage.key(i);
38
+
39
+ // Check if the key is related to a line-chart-state
40
+ if (key.includes('line-chart-state-')) {
41
+ const chartStateObjectJSON = localStorage.getItem(key);
42
+
43
+ // If a valid chart state object JSON is found
44
+ if (chartStateObjectJSON) {
45
+ const chartStateObject = JSON.parse(chartStateObjectJSON);
46
+ const storedTime = chartStateObject.timestamp;
47
+
48
+ // If a valid timestamp is found
49
+ if (storedTime) {
50
+ const ageInMilliseconds = currentTime - storedTime;
51
+
52
+ // If the age of the chart state is older than the specified maxAgeInHours
53
+ if (ageInMilliseconds > maxAgeInMilliseconds) {
54
+ // Remove the expired chart state from local storage
55
+ localStorage.removeItem(key);
56
+ }
57
+ }
58
+ }
59
+ }
60
+ }
61
+ };
62
+
63
+ /**
64
+ * Stores the chart state in local storage.
65
+ *
66
+ * @param {object} state - The chart state object to store.
67
+ * @param {string|''} persistenceId - The chart persistenceId.
68
+ */
69
+ export const storeChartStateInStorage = (state, persistenceId) => {
70
+ if (!persistenceId) return;
71
+
72
+ const currentTime = new Date().getTime();
73
+ const chartStateKey = `line-chart-state-${persistenceId}`;
74
+
75
+ // Create an object containing the chart state and the timestamp
76
+ const chartStateObject = {
77
+ state,
78
+ timestamp: currentTime,
79
+ };
80
+
81
+ // Serialize the chart state object as a JSON string and store it in local storage
82
+ localStorage.setItem(chartStateKey, JSON.stringify(chartStateObject));
83
+
84
+ // Remove expired chart states from local storage
85
+ removeExpiredChartStates();
86
+ };
@@ -0,0 +1,66 @@
1
+ import { useEffect, useMemo } from 'react';
2
+ import { storeChartStateInStorage } from './manage-state-in-local-storage';
3
+
4
+ /**
5
+ * Hook for toggling the visibility of the custom legend.
6
+ *
7
+ * @param memoState - The memoized state object.
8
+ * @param memoOptions - The memoized options object.
9
+ */
10
+ const useToggleCustomLegendVisibility = (memoState, memoOptions) => {
11
+ useEffect(() => {
12
+ if (memoOptions.legend.customLegend.customLegendPlugin) {
13
+ // Get the parent element of the custom legend container and toggle its visibility based on the state.
14
+ const parent = document.getElementById(
15
+ memoOptions.legend.customLegend.customLegendContainerID,
16
+ );
17
+ parent.style.visibility = memoState.legendEnabled ? 'visible' : 'hidden';
18
+ }
19
+ }, [
20
+ memoOptions.legend.customLegend.customLegendPlugin,
21
+ memoState.legendEnabled,
22
+ ]);
23
+ };
24
+
25
+ /**
26
+ * Hook for storing the chart state in local storage.
27
+ *
28
+ * @param memoState - The memoized state object.
29
+ * @param persistenceId - The chart persistenceId.
30
+ */
31
+ const useStoreChartStateInStorage = (memoState, persistenceId) => {
32
+ useEffect(() => {
33
+ // Store the chart state in local storage.
34
+ storeChartStateInStorage(memoState, persistenceId);
35
+ }, [
36
+ memoState.panEnabled,
37
+ memoState.lineEnabled,
38
+ memoState.pointsEnabled,
39
+ memoState.legendEnabled,
40
+ memoState.enableDragPoints,
41
+ memoState.zoomEnabled,
42
+ ]);
43
+ };
44
+
45
+ /**
46
+ * Hook for managing the state of the chart.
47
+ *
48
+ * @param options - The chart options.
49
+ * @param state - The chart state.
50
+ * @param persistenceId - The chart persistenceId.
51
+ */
52
+ const useChartState = (options, state, persistenceId) => {
53
+ const memoized = useMemo(() => {
54
+ return {
55
+ state,
56
+ options,
57
+ };
58
+ }, [state, options]);
59
+
60
+ const { state: memoState, options: memoOptions } = memoized ?? {};
61
+
62
+ useStoreChartStateInStorage(memoState, persistenceId);
63
+ useToggleCustomLegendVisibility(memoState, memoOptions);
64
+ };
65
+
66
+ export default useChartState;