@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 +1 -1
- package/release-notes.md +8 -0
- package/src/components/controls/axes-options/axes-options-form-state.js +10 -2
- package/src/components/controls/axes-options/axes-options.jsx +70 -1
- package/src/components/controls/controls.jsx +2 -0
- package/src/components/line-chart/get-axes-ranges-from-chart.js +5 -2
- package/src/components/line-chart/line-chart-prop-types.js +13 -0
- package/src/components/line-chart/line-chart-utils.js +12 -0
- package/src/components/line-chart/line-chart.interface.ts +13 -0
- package/src/components/line-chart/line-chart.jsx +33 -49
- package/src/components/line-chart/state/action-types.js +0 -1
- package/src/components/line-chart/state/initial-state.js +37 -17
- package/src/components/line-chart/state/line-chart-reducer.js +0 -10
- package/src/components/line-chart/state/manage-state-in-local-storage.js +86 -0
- package/src/components/line-chart/state/use-chart-state.js +66 -0
package/package.json
CHANGED
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, {
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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
|
-
|
|
170
|
-
|
|
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={
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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:
|
|
60
|
-
pointsEnabled: showPoints,
|
|
61
|
-
lineEnabled: showLine,
|
|
62
|
-
legendEnabled:
|
|
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:
|
|
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;
|