@oliasoft-open-source/charts-library 2.12.0 → 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.12.0",
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,9 @@
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
+
3
7
  ## 2.12.0
4
8
 
5
9
  - Add unit selector and depth selector to axes options ([OW-9496](https://oliasoft.atlassian.net/browse/OW-9496))
@@ -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({
@@ -148,6 +149,7 @@ export const getDefaultProps = (props) => {
148
149
  props.chart.options.dragData = props.chart.options.dragData || {};
149
150
  // Set defaults for missing properties
150
151
  const chart = {
152
+ persistenceId: props.chart.persistenceId ?? '',
151
153
  testId: props.chart.testId ?? null,
152
154
  data: props.chart.data,
153
155
  options: {
@@ -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
+ };
@@ -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,13 +108,12 @@ 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,
@@ -116,7 +121,6 @@ const LineChart = (props) => {
116
121
  depthType,
117
122
  dragData,
118
123
  } = options;
119
- const { showLine, showPoints, enableZoom, enablePan } = chartOptions;
120
124
 
121
125
  /**
122
126
  * @type {[object, import('react').Dispatch<{type: String, payload: any}>]} useReducer
@@ -124,19 +128,13 @@ const LineChart = (props) => {
124
128
  const [state, dispatch] = useReducer(
125
129
  reducer,
126
130
  {
127
- axes,
128
- enableZoom: chartOptions.enableZoom,
129
- enablePan: chartOptions.enablePan,
130
- showPoints: chartOptions.showPoints,
131
- showLine: chartOptions.showLine,
132
- legendDisplay: legend.display,
133
- annotationsData: annotations.annotationsData,
134
- customAxesRange: additionalAxesOptions?.range,
131
+ options,
132
+ persistenceId,
135
133
  },
136
134
  initialState,
137
135
  );
138
136
 
139
- useEffect(() => {
137
+ useLayoutEffect(() => {
140
138
  const { range } = props.chart.options.additionalAxesOptions;
141
139
  if (range?.x && range?.y) {
142
140
  const axes = Object.entries(range).map(([key, { min, max }]) => {
@@ -155,9 +153,6 @@ const LineChart = (props) => {
155
153
  }, [props.chart.options]);
156
154
 
157
155
  useEffect(() => {
158
- if (chartOptions.enablePan !== true) {
159
- dispatch({ type: TOGGLE_PAN });
160
- }
161
156
  if (chartRef) {
162
157
  //save the initial axis ranges in state (we need this for resetting ranges)
163
158
  dispatch({
@@ -167,29 +162,8 @@ const LineChart = (props) => {
167
162
  }
168
163
  }, []);
169
164
 
170
- useEffect(() => {
171
- dispatch({
172
- type: SET_POINTS_ZOOM_DEFAULTS,
173
- payload: { showLine, showPoints, enableZoom, enablePan },
174
- });
175
- }, [showLine, showPoints, enableZoom, enablePan]);
176
-
177
- /**
178
- * Toggle custom legends visibility.
179
- * Needed because they are rendered in a html element separate from the chart.
180
- */
181
- useEffect(() => {
182
- if (options.legend.customLegend.customLegendPlugin) {
183
- const parent = document.getElementById(
184
- options.legend.customLegend.customLegendContainerID,
185
- );
186
- if (state.legendEnabled) {
187
- parent.style.visibility = 'visible';
188
- } else {
189
- parent.style.visibility = 'hidden';
190
- }
191
- }
192
- }, [state.legendEnabled]);
165
+ // Call the custom hook.
166
+ useChartState(options, state, persistenceId);
193
167
 
194
168
  const generateLineChartDatasets = (datasets) => {
195
169
  const copyDataset = [...datasets];
@@ -471,7 +445,11 @@ const LineChart = (props) => {
471
445
  ) : (
472
446
  <div className={styles.canvas}>
473
447
  <Line
474
- key={state.enableDragPoints}
448
+ key={generateKey([
449
+ state.enableDragPoints,
450
+ state.zoomEnabled,
451
+ state.panEnabled,
452
+ ])}
475
453
  ref={chartRef}
476
454
  data={{
477
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,20 +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
- selectedDepthTypes,
15
- selectedUnits,
16
- setSelectedDepthTypes,
17
- }) => {
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;
18
22
  /**
19
23
  * getStateAxesByType
20
24
  * @param {'x'|'y'} axisType
@@ -61,19 +65,25 @@ const initialState = ({
61
65
  const yStateAxes = getStateAxesByType(AxisType.Y, customAxesRange);
62
66
  const stateAxes = [...xStateAxes, ...yStateAxes];
63
67
 
68
+ const {
69
+ zoomEnabled,
70
+ panEnabled,
71
+ pointsEnabled,
72
+ lineEnabled,
73
+ legendEnabled,
74
+ enableDragPoints,
75
+ } = getChartStateFromStorage(persistenceId) || {};
76
+
64
77
  return {
65
- zoomEnabled: enableZoom,
66
- panEnabled: true, // TODO: work out why this logic doesn't work, workaround wih dispatch is currently used
67
- pointsEnabled: showPoints,
68
- lineEnabled: showLine,
69
- 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,
70
83
  axes: stateAxes,
71
- showAnnotationLineIndex: setAnnotations(annotationsData),
84
+ showAnnotationLineIndex: setAnnotations(annotations.annotationsData),
72
85
  showTable: false,
73
- selectedDepthTypes,
74
- selectedUnits,
75
- setSelectedDepthTypes,
76
- enableDragPoints: false,
86
+ enableDragPoints: dragData.enableDragData && enableDragPoints,
77
87
  };
78
88
  };
79
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;