@oliasoft-open-source/charts-library 2.5.21 → 2.5.22
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 +2 -1
- package/release-notes.md +4 -0
- package/src/components/controls/axes-options/axes-options-form-state.js +95 -0
- package/src/components/controls/{axes-options.jsx → axes-options/axes-options.jsx} +73 -56
- package/src/components/controls/controls.jsx +6 -10
- package/src/components/line-chart/get-axes-ranges-from-chart.js +10 -0
- package/src/components/line-chart/get-line-chart-scales.js +3 -13
- package/src/components/line-chart/line-chart.jsx +36 -45
- package/src/components/line-chart/state/action-types.js +3 -3
- package/src/components/line-chart/state/initial-state.js +19 -5
- package/src/components/line-chart/state/line-chart-reducer.js +22 -70
- package/src/helpers/chart-utils.js +0 -24
- package/src/components/line-chart/get-scales-min-max.js +0 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oliasoft-open-source/charts-library",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.22",
|
|
4
4
|
"description": "React Chart Library (based on Chart.js and react-chart-js-2)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -90,6 +90,7 @@
|
|
|
90
90
|
"chartjs-plugin-zoom": "^1.2.1",
|
|
91
91
|
"classnames": "^2.3.1",
|
|
92
92
|
"fraction.js": "^4.2.0",
|
|
93
|
+
"immer": "^9.0.17",
|
|
93
94
|
"less-vars-to-js": "^1.3.0",
|
|
94
95
|
"prop-types": "^15.8.1",
|
|
95
96
|
"react-base64-downloader": "^2.1.7",
|
package/release-notes.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Charts Library Release Notes
|
|
2
2
|
|
|
3
|
+
## 2.5.22
|
|
4
|
+
|
|
5
|
+
- Improve UX and input validation for axes range inputs ([OW-10305](https://oliasoft.atlassian.net/browse/OW-10305))
|
|
6
|
+
|
|
3
7
|
## 2.5.21
|
|
4
8
|
|
|
5
9
|
- Revert changes from `2.5.12` / [OW-10320](https://oliasoft.atlassian.net/browse/OW-10320)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { produce } from 'immer';
|
|
2
|
+
import {
|
|
3
|
+
isGreaterThanMin,
|
|
4
|
+
isLessThanMax,
|
|
5
|
+
validNumber,
|
|
6
|
+
} from '../../line-chart/line-chart-utils';
|
|
7
|
+
|
|
8
|
+
export const actionTypes = Object.freeze({
|
|
9
|
+
reset: 'reset',
|
|
10
|
+
valueUpdated: 'valueUpdated',
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Initialize local component form state for a custom loads density table
|
|
15
|
+
*
|
|
16
|
+
* @param {Object} args
|
|
17
|
+
* @param {Object} args.initialAxesRanges
|
|
18
|
+
* @param {Array} [args.axes]
|
|
19
|
+
* @returns {Object} formState
|
|
20
|
+
*/
|
|
21
|
+
export const initializeFormState = ({ initialAxesRanges, axes = [] }) => {
|
|
22
|
+
return initialAxesRanges.map((initialAxisRange) => {
|
|
23
|
+
const currentAxisRange = axes.find((a) => a.id === initialAxisRange.id);
|
|
24
|
+
return {
|
|
25
|
+
id: initialAxisRange.id,
|
|
26
|
+
min: currentAxisRange?.min ?? initialAxisRange?.min,
|
|
27
|
+
max: currentAxisRange?.max ?? initialAxisRange?.max,
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @typedef {Object} Action
|
|
34
|
+
* @property {String} type Action type
|
|
35
|
+
* @property {Object} [payload] Action payload (optional)
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
const isEmptyString = (value) => value === '';
|
|
39
|
+
|
|
40
|
+
export const validateAxes = (formState) => {
|
|
41
|
+
return formState.reduce(
|
|
42
|
+
(acc, { id, min, max }) => {
|
|
43
|
+
return produce(acc, (draft) => {
|
|
44
|
+
const errors = {
|
|
45
|
+
min: [
|
|
46
|
+
...(isEmptyString(min) ? ['Must have a value'] : []),
|
|
47
|
+
...(!validNumber(min) ? ['Must be a number'] : []),
|
|
48
|
+
...(!isLessThanMax(min, max) ? ['Must be less than max'] : []),
|
|
49
|
+
],
|
|
50
|
+
max: [
|
|
51
|
+
...(isEmptyString(max) ? ['Must have a value'] : []),
|
|
52
|
+
...(!validNumber(max) ? ['Must be a number'] : []),
|
|
53
|
+
...(!isGreaterThanMin(max, min)
|
|
54
|
+
? ['Must be greater than min']
|
|
55
|
+
: []),
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
draft.errors.push({
|
|
59
|
+
id,
|
|
60
|
+
...errors,
|
|
61
|
+
});
|
|
62
|
+
draft.valid = !(
|
|
63
|
+
!acc.valid ||
|
|
64
|
+
!!errors.min.length ||
|
|
65
|
+
!!errors.max.length
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
{ errors: [], valid: true },
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Local component form state reducer for a axes options form
|
|
75
|
+
*
|
|
76
|
+
* @param {Object} state Local component form state
|
|
77
|
+
* @param {Action} action Action with type and payload
|
|
78
|
+
* @returns {Object} FormState
|
|
79
|
+
*/
|
|
80
|
+
export const reducer = (state, action) => {
|
|
81
|
+
switch (action.type) {
|
|
82
|
+
case actionTypes.reset: {
|
|
83
|
+
const { initialAxesRanges } = action.payload;
|
|
84
|
+
return initializeFormState({ initialAxesRanges });
|
|
85
|
+
}
|
|
86
|
+
case actionTypes.valueUpdated:
|
|
87
|
+
return produce(state, (draft) => {
|
|
88
|
+
const { name, value, id } = action.payload;
|
|
89
|
+
const axis = draft.find((a) => a.id === id);
|
|
90
|
+
axis[name] = value;
|
|
91
|
+
});
|
|
92
|
+
default:
|
|
93
|
+
return state;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useReducer } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
Button,
|
|
4
4
|
Field,
|
|
@@ -11,101 +11,122 @@ import {
|
|
|
11
11
|
Tooltip,
|
|
12
12
|
} from '@oliasoft-open-source/react-ui-library';
|
|
13
13
|
import { RiRuler2Line } from 'react-icons/ri';
|
|
14
|
+
import { toNum } from '../../line-chart/line-chart-utils';
|
|
15
|
+
import {
|
|
16
|
+
actionTypes,
|
|
17
|
+
reducer,
|
|
18
|
+
initializeFormState,
|
|
19
|
+
validateAxes,
|
|
20
|
+
} from './axes-options-form-state';
|
|
14
21
|
|
|
15
22
|
const AxesOptionsPopover = ({
|
|
16
|
-
|
|
23
|
+
initialAxesRanges,
|
|
17
24
|
axes,
|
|
18
25
|
controlsAxesLabels,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
scalesMaxMin,
|
|
22
|
-
zoomEnabled,
|
|
26
|
+
onUpdateAxes,
|
|
27
|
+
onResetAxes,
|
|
23
28
|
close,
|
|
24
29
|
}) => {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
const [formState, dispatch] = useReducer(
|
|
31
|
+
reducer,
|
|
32
|
+
{
|
|
33
|
+
axes,
|
|
34
|
+
initialAxesRanges,
|
|
35
|
+
},
|
|
36
|
+
initializeFormState,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const { errors, valid } = validateAxes(formState);
|
|
40
|
+
|
|
41
|
+
const onEditValue = ({ name, value, id }) => {
|
|
42
|
+
dispatch({
|
|
43
|
+
type: actionTypes.valueUpdated,
|
|
44
|
+
payload: { name, value, id },
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const onDone = () => {
|
|
49
|
+
if (valid) {
|
|
50
|
+
const sanitizedFormState = formState.map((axis) => ({
|
|
51
|
+
...axis,
|
|
52
|
+
min: toNum(axis.min),
|
|
53
|
+
max: toNum(axis.max),
|
|
54
|
+
}));
|
|
55
|
+
onUpdateAxes({
|
|
56
|
+
axes: sanitizedFormState,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
close();
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const onReset = () => {
|
|
63
|
+
//reset local form
|
|
64
|
+
dispatch({
|
|
65
|
+
type: actionTypes.reset,
|
|
66
|
+
payload: { axes, initialAxesRanges },
|
|
67
|
+
});
|
|
68
|
+
//reset parent state
|
|
69
|
+
onResetAxes();
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const isCustomValue = axes.filter((axis) => axis.max || axis.min).length > 0;
|
|
28
73
|
const handleInputFocus = (e) => e.target.select();
|
|
29
|
-
const zoomOrPanEnabled = zoomEnabled || panEnabled;
|
|
30
74
|
return (
|
|
31
75
|
<>
|
|
32
76
|
{axes.map((axis, i) => {
|
|
33
|
-
// TODO: Translate strings
|
|
34
|
-
if (!axis.min || !axis.max) return null;
|
|
35
|
-
|
|
36
77
|
const axisLabel = controlsAxesLabels.find(
|
|
37
78
|
(el) => el.id === axis.id,
|
|
38
79
|
)?.label;
|
|
39
80
|
|
|
81
|
+
const min = formState.find((a) => a.id === axis.id)?.min;
|
|
82
|
+
const max = formState.find((a) => a.id === axis.id)?.max;
|
|
83
|
+
const minError = errors?.find((a) => a.id === axis.id)?.min?.[0];
|
|
84
|
+
const maxError = errors?.find((a) => a.id === axis.id)?.max?.[0];
|
|
40
85
|
return (
|
|
41
86
|
<Field key={i} label={axisLabel || axis.id || ''}>
|
|
42
87
|
<InputGroup small>
|
|
43
88
|
<Input
|
|
44
89
|
name="min"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
(zoomOrPanEnabled
|
|
48
|
-
? axis.min.displayValue
|
|
49
|
-
: axis.min.inputValue) || scalesMaxMin[axis?.id]?.min
|
|
50
|
-
}
|
|
51
|
-
error={
|
|
52
|
-
!axis.min.valid
|
|
53
|
-
? 'Invalid value' //t(InputWarningType.MustBeNumericAndLessThanMax)
|
|
54
|
-
: undefined
|
|
55
|
-
}
|
|
90
|
+
value={min}
|
|
91
|
+
error={minError}
|
|
56
92
|
size={5}
|
|
57
93
|
width="100%"
|
|
58
94
|
onChange={(evt) =>
|
|
59
|
-
|
|
95
|
+
onEditValue({
|
|
60
96
|
name: evt.target.name,
|
|
61
97
|
value: evt.target.value,
|
|
62
98
|
id: axis.id,
|
|
63
99
|
})
|
|
64
100
|
}
|
|
65
101
|
onFocus={handleInputFocus}
|
|
66
|
-
disabled={zoomEnabled || panEnabled}
|
|
67
102
|
/>
|
|
68
|
-
<InputGroupAddon>
|
|
69
|
-
to
|
|
70
|
-
{/*t(translations.to)*/}
|
|
71
|
-
</InputGroupAddon>
|
|
103
|
+
<InputGroupAddon>to</InputGroupAddon>
|
|
72
104
|
<Input
|
|
73
105
|
name="max"
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
(zoomOrPanEnabled
|
|
77
|
-
? axis.max.displayValue
|
|
78
|
-
: axis.max.inputValue) || scalesMaxMin[axis?.id]?.max
|
|
79
|
-
}
|
|
80
|
-
error={
|
|
81
|
-
!axis.max.valid
|
|
82
|
-
? 'Invalid value' //t(InputWarningType.MustBeNumericAndGreaterThanMin)
|
|
83
|
-
: undefined
|
|
84
|
-
}
|
|
106
|
+
value={max}
|
|
107
|
+
error={maxError}
|
|
85
108
|
size={5}
|
|
86
109
|
width="100%"
|
|
87
110
|
onChange={(evt) =>
|
|
88
|
-
|
|
111
|
+
onEditValue({
|
|
89
112
|
name: evt.target.name,
|
|
90
113
|
value: evt.target.value,
|
|
91
114
|
id: axis.id,
|
|
92
115
|
})
|
|
93
116
|
}
|
|
94
117
|
onFocus={handleInputFocus}
|
|
95
|
-
disabled={zoomEnabled || panEnabled}
|
|
96
118
|
/>
|
|
97
119
|
</InputGroup>
|
|
98
|
-
{/* TODO: Add units */}
|
|
99
120
|
</Field>
|
|
100
121
|
);
|
|
101
122
|
})}
|
|
102
123
|
<Flex gap="8px" alignItems="center">
|
|
103
|
-
<Button small colored label="Done" onClick={
|
|
124
|
+
<Button small colored label="Done" onClick={onDone} disabled={!valid} />
|
|
104
125
|
<Button
|
|
105
126
|
small
|
|
106
127
|
name="resetAxes"
|
|
107
128
|
label="Reset Axes"
|
|
108
|
-
onClick={
|
|
129
|
+
onClick={onReset}
|
|
109
130
|
disabled={!isCustomValue}
|
|
110
131
|
/>
|
|
111
132
|
<Text small muted>
|
|
@@ -117,13 +138,11 @@ const AxesOptionsPopover = ({
|
|
|
117
138
|
};
|
|
118
139
|
|
|
119
140
|
export const AxesOptions = ({
|
|
120
|
-
|
|
141
|
+
initialAxesRanges,
|
|
121
142
|
axes,
|
|
122
143
|
controlsAxesLabels,
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
scalesMaxMin,
|
|
126
|
-
zoomEnabled,
|
|
144
|
+
onUpdateAxes,
|
|
145
|
+
onResetAxes,
|
|
127
146
|
}) => {
|
|
128
147
|
return (
|
|
129
148
|
<Popover
|
|
@@ -131,13 +150,11 @@ export const AxesOptions = ({
|
|
|
131
150
|
overflowContainer
|
|
132
151
|
content={
|
|
133
152
|
<AxesOptionsPopover
|
|
134
|
-
|
|
153
|
+
initialAxesRanges={initialAxesRanges}
|
|
135
154
|
axes={axes}
|
|
136
155
|
controlsAxesLabels={controlsAxesLabels}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
scalesMaxMin={scalesMaxMin}
|
|
140
|
-
zoomEnabled={zoomEnabled}
|
|
156
|
+
onUpdateAxes={onUpdateAxes}
|
|
157
|
+
onResetAxes={onResetAxes}
|
|
141
158
|
/>
|
|
142
159
|
}
|
|
143
160
|
>
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from 'react-icons/ri';
|
|
8
8
|
import { LineOptions } from './line-options';
|
|
9
9
|
import { DragOptions } from './drag-options';
|
|
10
|
-
import { AxesOptions } from './axes-options';
|
|
10
|
+
import { AxesOptions } from './axes-options/axes-options';
|
|
11
11
|
import { LegendOptions } from './legend-options';
|
|
12
12
|
import styles from './controls.module.less';
|
|
13
13
|
|
|
@@ -20,7 +20,7 @@ const Controls = ({
|
|
|
20
20
|
lineEnabled,
|
|
21
21
|
onDownload,
|
|
22
22
|
onResetAxes,
|
|
23
|
-
|
|
23
|
+
onUpdateAxes,
|
|
24
24
|
onToggleLegend,
|
|
25
25
|
onToggleLine,
|
|
26
26
|
onTogglePan,
|
|
@@ -29,7 +29,7 @@ const Controls = ({
|
|
|
29
29
|
onToggleZoom,
|
|
30
30
|
panEnabled,
|
|
31
31
|
pointsEnabled,
|
|
32
|
-
|
|
32
|
+
initialAxesRanges,
|
|
33
33
|
showTable,
|
|
34
34
|
subheaderComponent,
|
|
35
35
|
table,
|
|
@@ -44,13 +44,11 @@ const Controls = ({
|
|
|
44
44
|
{!showTable && (
|
|
45
45
|
<>
|
|
46
46
|
<AxesOptions
|
|
47
|
-
|
|
47
|
+
initialAxesRanges={initialAxesRanges}
|
|
48
48
|
axes={axes}
|
|
49
49
|
controlsAxesLabels={controlsAxesLabels}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
scalesMaxMin={scalesMaxMin}
|
|
53
|
-
zoomEnabled={zoomEnabled}
|
|
50
|
+
onUpdateAxes={onUpdateAxes}
|
|
51
|
+
onResetAxes={onResetAxes}
|
|
54
52
|
/>
|
|
55
53
|
<LineOptions
|
|
56
54
|
lineEnabled={lineEnabled}
|
|
@@ -68,7 +66,6 @@ const Controls = ({
|
|
|
68
66
|
onTogglePan={onTogglePan}
|
|
69
67
|
onToggleZoom={onToggleZoom}
|
|
70
68
|
/>
|
|
71
|
-
{/* TODO: implement usetranslation */}
|
|
72
69
|
<Tooltip text="Download as PNG" placement="bottom-end">
|
|
73
70
|
<Button
|
|
74
71
|
small
|
|
@@ -87,7 +84,6 @@ const Controls = ({
|
|
|
87
84
|
text={showTable ? 'Show chart' : 'Show table'}
|
|
88
85
|
placement="bottom-end"
|
|
89
86
|
>
|
|
90
|
-
{/* TODO: implement usetranslation */}
|
|
91
87
|
<Button
|
|
92
88
|
small
|
|
93
89
|
basic
|
|
@@ -3,11 +3,7 @@ import {
|
|
|
3
3
|
getAxisPosition,
|
|
4
4
|
} from '../../helpers/chart-utils';
|
|
5
5
|
import { COLORS, LOGARITHMIC_STEPS } from '../../helpers/chart-consts';
|
|
6
|
-
import {
|
|
7
|
-
generateAxisId,
|
|
8
|
-
truncateDecimals,
|
|
9
|
-
validNumber,
|
|
10
|
-
} from './line-chart-utils';
|
|
6
|
+
import { generateAxisId, truncateDecimals } from './line-chart-utils';
|
|
11
7
|
import { AxisType, ScaleType } from '../../helpers/enums';
|
|
12
8
|
|
|
13
9
|
/**
|
|
@@ -66,14 +62,8 @@ const getLineChartAxis = (options, axisType, state, currentScales, i = 0) => {
|
|
|
66
62
|
reverse: axisType === AxisType.Y ? additionalAxesOptions.reverse : false,
|
|
67
63
|
suggestedMax: additionalAxesOptions.suggestedMax,
|
|
68
64
|
suggestedMin: additionalAxesOptions.suggestedMin,
|
|
69
|
-
min:
|
|
70
|
-
|
|
71
|
-
? Number(stateAxis.min?.value)
|
|
72
|
-
: additionalAxesOptions?.range?.[axisType]?.min,
|
|
73
|
-
max:
|
|
74
|
-
stateAxis.max?.valid && validNumber(stateAxis.max?.value)
|
|
75
|
-
? Number(stateAxis.max?.value)
|
|
76
|
-
: additionalAxesOptions?.range?.[axisType]?.max,
|
|
65
|
+
min: Number(stateAxis.min) ?? additionalAxesOptions?.range?.[axisType]?.min,
|
|
66
|
+
max: Number(stateAxis.max) ?? additionalAxesOptions?.range?.[axisType]?.max,
|
|
77
67
|
title: {
|
|
78
68
|
display: axisData.label?.length,
|
|
79
69
|
text: axisData.label,
|
|
@@ -16,13 +16,10 @@ import zoomPlugin from 'chartjs-plugin-zoom';
|
|
|
16
16
|
import dataLabelsPlugin from 'chartjs-plugin-datalabels';
|
|
17
17
|
import annotationPlugin from 'chartjs-plugin-annotation';
|
|
18
18
|
import { triggerBase64Download } from 'react-base64-downloader';
|
|
19
|
-
|
|
20
19
|
import styles from './line-chart.module.less';
|
|
21
20
|
import { reducer } from './state/line-chart-reducer';
|
|
22
21
|
import initialState from './state/initial-state';
|
|
23
22
|
import {
|
|
24
|
-
SET_AXIS_MIN_MAX,
|
|
25
|
-
SET_AXIS_VALUE,
|
|
26
23
|
SET_POINTS_ZOOM_DEFAULTS,
|
|
27
24
|
TOGGLE_ANNOTATION,
|
|
28
25
|
TOGGLE_LEGEND,
|
|
@@ -31,7 +28,9 @@ import {
|
|
|
31
28
|
TOGGLE_POINTS,
|
|
32
29
|
TOGGLE_TABLE,
|
|
33
30
|
TOGGLE_ZOOM,
|
|
34
|
-
|
|
31
|
+
SAVE_INITIAL_AXES_RANGES,
|
|
32
|
+
UPDATE_AXES_RANGES,
|
|
33
|
+
RESET_AXES_RANGES,
|
|
35
34
|
} from './state/action-types';
|
|
36
35
|
import Controls from '../controls/controls';
|
|
37
36
|
import { getDefaultProps, LineChartPropTypes } from './line-chart-prop-types';
|
|
@@ -51,12 +50,10 @@ import {
|
|
|
51
50
|
import getAnnotation from '../../helpers/get-chart-annotation';
|
|
52
51
|
import {
|
|
53
52
|
generateRandomColor,
|
|
54
|
-
getAxisValue,
|
|
55
53
|
getChartFileName,
|
|
56
54
|
getClassName,
|
|
57
55
|
getLegend,
|
|
58
56
|
getPlugins,
|
|
59
|
-
getTitle,
|
|
60
57
|
setDefaultTheme,
|
|
61
58
|
} from '../../helpers/chart-utils';
|
|
62
59
|
import {
|
|
@@ -73,7 +70,7 @@ import {
|
|
|
73
70
|
PanZoomMode,
|
|
74
71
|
PointStyle,
|
|
75
72
|
} from '../../helpers/enums';
|
|
76
|
-
import {
|
|
73
|
+
import { getAxesRangesFromChart } from './get-axes-ranges-from-chart';
|
|
77
74
|
import { generateAxisId } from './line-chart-utils';
|
|
78
75
|
|
|
79
76
|
ChartJS.register(
|
|
@@ -128,6 +125,7 @@ const LineChart = (props) => {
|
|
|
128
125
|
showLine: chartOptions.showLine,
|
|
129
126
|
legendDisplay: legend.display,
|
|
130
127
|
annotationsData: annotations.annotationsData,
|
|
128
|
+
customAxesRange: additionalAxesOptions?.range,
|
|
131
129
|
},
|
|
132
130
|
initialState,
|
|
133
131
|
);
|
|
@@ -137,7 +135,11 @@ const LineChart = (props) => {
|
|
|
137
135
|
dispatch({ type: TOGGLE_PAN });
|
|
138
136
|
}
|
|
139
137
|
if (chartRef) {
|
|
140
|
-
|
|
138
|
+
//save the initial axis ranges in state (we need this for resetting ranges)
|
|
139
|
+
dispatch({
|
|
140
|
+
type: SAVE_INITIAL_AXES_RANGES,
|
|
141
|
+
payload: { initialAxesRanges: getAxesRangesFromChart(chartRef) },
|
|
142
|
+
});
|
|
141
143
|
}
|
|
142
144
|
}, []);
|
|
143
145
|
|
|
@@ -190,8 +192,8 @@ const LineChart = (props) => {
|
|
|
190
192
|
|
|
191
193
|
const generatedDatasets = copyDataset.map((line, i) => {
|
|
192
194
|
if (line.formation) {
|
|
193
|
-
const axesMin = state.axes[0]?.min
|
|
194
|
-
const axesMax = state.axes[0]?.max
|
|
195
|
+
const axesMin = state.axes[0]?.min;
|
|
196
|
+
const axesMax = state.axes[0]?.max;
|
|
195
197
|
// line with formation flag has 3 points: start point, mid-point with label, and end point.
|
|
196
198
|
const [startPoint, midPointWithLabel, endPoint] = line.data;
|
|
197
199
|
|
|
@@ -278,10 +280,10 @@ const LineChart = (props) => {
|
|
|
278
280
|
|
|
279
281
|
const onClick = (evt, elements, chartInstance) => {
|
|
280
282
|
chartInstance.resetZoom();
|
|
281
|
-
dispatch({ type:
|
|
283
|
+
dispatch({ type: RESET_AXES_RANGES });
|
|
282
284
|
};
|
|
283
285
|
|
|
284
|
-
const onHover = (evt, hoveredItems
|
|
286
|
+
const onHover = (evt, hoveredItems) => {
|
|
285
287
|
if (!hoveredItems?.length && interactions.onPointUnhover && hoveredPoint) {
|
|
286
288
|
setHoveredPoint(null);
|
|
287
289
|
interactions.onPointUnhover(evt);
|
|
@@ -336,13 +338,13 @@ const LineChart = (props) => {
|
|
|
336
338
|
const getControlsAxes = () => {
|
|
337
339
|
return state.axes.map((axis, i) => {
|
|
338
340
|
const axisType = i ? AxisType.Y : AxisType.X; // only first element is 'x' - rest is 'y'
|
|
339
|
-
const min = additionalAxesOptions?.range?.[axisType]?.min;
|
|
340
|
-
const max = additionalAxesOptions?.range?.[axisType]?.max;
|
|
341
|
-
|
|
341
|
+
const min = axis.min ?? additionalAxesOptions?.range?.[axisType]?.min;
|
|
342
|
+
const max = axis.max ?? additionalAxesOptions?.range?.[axisType]?.max;
|
|
342
343
|
return {
|
|
343
344
|
...axis,
|
|
344
|
-
|
|
345
|
-
|
|
345
|
+
//only add min and max properties if they are defined:
|
|
346
|
+
...(min ? { min } : {}),
|
|
347
|
+
...(max ? { max } : {}),
|
|
346
348
|
};
|
|
347
349
|
});
|
|
348
350
|
};
|
|
@@ -375,29 +377,18 @@ const LineChart = (props) => {
|
|
|
375
377
|
|
|
376
378
|
const controlsAxesLabels = getControlsAxesLabels(props.chart.options.axes);
|
|
377
379
|
|
|
378
|
-
const
|
|
380
|
+
const updateAxesRangesFromChart = (chart) => {
|
|
379
381
|
const { scales = {} } = chart || {};
|
|
380
|
-
const
|
|
381
|
-
return
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
id: key,
|
|
388
|
-
},
|
|
389
|
-
{
|
|
390
|
-
name: 'max',
|
|
391
|
-
value: scales[key]?.max ? scales[key].max : 0,
|
|
392
|
-
id: key,
|
|
393
|
-
},
|
|
394
|
-
];
|
|
395
|
-
}, []);
|
|
396
|
-
};
|
|
397
|
-
|
|
382
|
+
const axes = Object.entries(scales).map(([key, { min, max }]) => {
|
|
383
|
+
return {
|
|
384
|
+
id: key,
|
|
385
|
+
min: min ?? 0,
|
|
386
|
+
max: max ?? 0,
|
|
387
|
+
};
|
|
388
|
+
});
|
|
398
389
|
dispatch({
|
|
399
|
-
type:
|
|
400
|
-
payload:
|
|
390
|
+
type: UPDATE_AXES_RANGES,
|
|
391
|
+
payload: { axes },
|
|
401
392
|
});
|
|
402
393
|
};
|
|
403
394
|
|
|
@@ -422,11 +413,11 @@ const LineChart = (props) => {
|
|
|
422
413
|
lineEnabled={state.lineEnabled}
|
|
423
414
|
onDownload={handleDownload}
|
|
424
415
|
onResetAxes={() => {
|
|
425
|
-
dispatch({ type:
|
|
416
|
+
dispatch({ type: RESET_AXES_RANGES });
|
|
417
|
+
}}
|
|
418
|
+
onUpdateAxes={({ axes }) => {
|
|
419
|
+
dispatch({ type: UPDATE_AXES_RANGES, payload: { axes } });
|
|
426
420
|
}}
|
|
427
|
-
onSetAxisValue={(evt) =>
|
|
428
|
-
dispatch({ type: SET_AXIS_VALUE, payload: evt })
|
|
429
|
-
}
|
|
430
421
|
onToggleLegend={() => dispatch({ type: TOGGLE_LEGEND })}
|
|
431
422
|
onToggleLine={() => dispatch({ type: TOGGLE_LINE })}
|
|
432
423
|
onTogglePan={() => dispatch({ type: TOGGLE_PAN })}
|
|
@@ -435,7 +426,7 @@ const LineChart = (props) => {
|
|
|
435
426
|
onToggleZoom={() => dispatch({ type: TOGGLE_ZOOM })}
|
|
436
427
|
panEnabled={state.panEnabled}
|
|
437
428
|
pointsEnabled={state.pointsEnabled}
|
|
438
|
-
|
|
429
|
+
initialAxesRanges={state.initialAxesRanges}
|
|
439
430
|
showTable={state.showTable}
|
|
440
431
|
subheaderComponent={subheaderComponent}
|
|
441
432
|
table={table}
|
|
@@ -480,7 +471,7 @@ const LineChart = (props) => {
|
|
|
480
471
|
enabled: state.panEnabled,
|
|
481
472
|
mode: PanZoomMode.XY,
|
|
482
473
|
onPanComplete({ chart }) {
|
|
483
|
-
|
|
474
|
+
updateAxesRangesFromChart(chart);
|
|
484
475
|
},
|
|
485
476
|
},
|
|
486
477
|
zoom: {
|
|
@@ -493,7 +484,7 @@ const LineChart = (props) => {
|
|
|
493
484
|
borderWidth: 1,
|
|
494
485
|
},
|
|
495
486
|
onZoomComplete({ chart }) {
|
|
496
|
-
|
|
487
|
+
updateAxesRangesFromChart(chart);
|
|
497
488
|
},
|
|
498
489
|
},
|
|
499
490
|
},
|
|
@@ -3,9 +3,9 @@ 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 UNSET_AXES_VALUES = 'UNSET_AXES_VALUES';
|
|
7
|
-
export const SET_AXIS_VALUE = 'SET_AXIS_VALUE';
|
|
8
6
|
export const SET_POINTS_ZOOM_DEFAULTS = 'SET_POINTS_ZOOM_DEFAULTS';
|
|
9
7
|
export const TOGGLE_ANNOTATION = 'TOGGLE_ANNOTATION';
|
|
10
8
|
export const TOGGLE_TABLE = 'TOGGLE_TABLE';
|
|
11
|
-
export const
|
|
9
|
+
export const SAVE_INITIAL_AXES_RANGES = 'SAVE_INITIAL_AXES_RANGES';
|
|
10
|
+
export const RESET_AXES_RANGES = 'RESET_AXES_RANGES';
|
|
11
|
+
export const UPDATE_AXES_RANGES = 'UPDATE_AXES_RANGES';
|
|
@@ -10,34 +10,48 @@ const initialState = ({
|
|
|
10
10
|
showLine,
|
|
11
11
|
legendDisplay,
|
|
12
12
|
annotationsData,
|
|
13
|
+
customAxesRange,
|
|
13
14
|
}) => {
|
|
14
15
|
/**
|
|
15
16
|
* getStateAxesByType
|
|
16
17
|
* @param {'x'|'y'} axisType
|
|
18
|
+
* @param {Object} customAxesRange
|
|
17
19
|
* @return {{id: string}[] | []} returns array of objects describing all chart axis or empty array
|
|
18
20
|
*/
|
|
19
|
-
const getStateAxesByType = (axisType) => {
|
|
21
|
+
const getStateAxesByType = (axisType, customAxesRange) => {
|
|
20
22
|
if (!axes[axisType]) {
|
|
21
23
|
return [];
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
if (axes[axisType]?.length > 1) {
|
|
25
27
|
return axes[axisType].map((axisObj, index) => {
|
|
28
|
+
const id = generateAxisId(axisType, index, axes[axisType].length > 1);
|
|
29
|
+
const customMin = customAxesRange?.[id]?.min;
|
|
30
|
+
const customMax = customAxesRange?.[id]?.max;
|
|
26
31
|
return {
|
|
27
|
-
id
|
|
32
|
+
id,
|
|
33
|
+
//only add custom axis ranges if defined:
|
|
34
|
+
...(customMin ? { min: customMin } : {}),
|
|
35
|
+
...(customMax ? { max: customMax } : {}),
|
|
28
36
|
};
|
|
29
37
|
});
|
|
30
38
|
} else {
|
|
39
|
+
const id = generateAxisId(axisType);
|
|
40
|
+
const customMin = customAxesRange?.[id]?.min;
|
|
41
|
+
const customMax = customAxesRange?.[id]?.max;
|
|
31
42
|
return [
|
|
32
43
|
{
|
|
33
|
-
id
|
|
44
|
+
id,
|
|
45
|
+
//only add custom axis ranges if defined:
|
|
46
|
+
...(customMin ? { min: customMin } : {}),
|
|
47
|
+
...(customMax ? { max: customMax } : {}),
|
|
34
48
|
},
|
|
35
49
|
];
|
|
36
50
|
}
|
|
37
51
|
};
|
|
38
52
|
|
|
39
|
-
const xStateAxes = getStateAxesByType(AxisType.X);
|
|
40
|
-
const yStateAxes = getStateAxesByType(AxisType.Y);
|
|
53
|
+
const xStateAxes = getStateAxesByType(AxisType.X, customAxesRange);
|
|
54
|
+
const yStateAxes = getStateAxesByType(AxisType.Y, customAxesRange);
|
|
41
55
|
const stateAxes = [...xStateAxes, ...yStateAxes];
|
|
42
56
|
|
|
43
57
|
return {
|
|
@@ -1,26 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
cleanNumStr,
|
|
3
|
-
isGreaterThanMin,
|
|
4
|
-
isLessThanMax,
|
|
5
|
-
toNum,
|
|
6
|
-
validNumber,
|
|
7
|
-
} from '../line-chart-utils';
|
|
1
|
+
import { produce } from 'immer';
|
|
8
2
|
import {
|
|
9
3
|
TOGGLE_PAN,
|
|
10
4
|
TOGGLE_ZOOM,
|
|
11
5
|
TOGGLE_POINTS,
|
|
12
6
|
TOGGLE_LINE,
|
|
13
7
|
TOGGLE_LEGEND,
|
|
14
|
-
UNSET_AXES_VALUES,
|
|
15
|
-
SET_AXIS_VALUE,
|
|
16
8
|
SET_POINTS_ZOOM_DEFAULTS,
|
|
17
9
|
TOGGLE_ANNOTATION,
|
|
18
10
|
TOGGLE_TABLE,
|
|
19
|
-
|
|
11
|
+
SAVE_INITIAL_AXES_RANGES,
|
|
12
|
+
UPDATE_AXES_RANGES,
|
|
13
|
+
RESET_AXES_RANGES,
|
|
20
14
|
} from './action-types';
|
|
21
|
-
import { getAxisValue } from '../../../helpers/chart-utils';
|
|
22
|
-
|
|
23
|
-
const unsetAxisValues = (axes) => axes.map(({ min, max, ...axis }) => axis);
|
|
24
15
|
|
|
25
16
|
export const reducer = (state, action) => {
|
|
26
17
|
const newState = { ...state };
|
|
@@ -30,11 +21,6 @@ export const reducer = (state, action) => {
|
|
|
30
21
|
if (newState.panEnabled) {
|
|
31
22
|
newState.panEnabled = false;
|
|
32
23
|
}
|
|
33
|
-
// TODO: find out if this logic is needed once panning and zomming works properly
|
|
34
|
-
// if (!newState.zoomEnabled) {
|
|
35
|
-
// //reset the axes when enabling zoom (zoom plugin and manual ranges aren't compatible together yet)
|
|
36
|
-
// newState.axes = unsetAxisValues(newState.axes);
|
|
37
|
-
// }
|
|
38
24
|
return newState;
|
|
39
25
|
}
|
|
40
26
|
case TOGGLE_PAN: {
|
|
@@ -42,10 +28,6 @@ export const reducer = (state, action) => {
|
|
|
42
28
|
if (newState.zoomEnabled) {
|
|
43
29
|
newState.zoomEnabled = false;
|
|
44
30
|
}
|
|
45
|
-
// if (!newState.panEnabled) {
|
|
46
|
-
// //reset the axes when enabling pan (zoom plugin and manual ranges aren't compatible together yet)
|
|
47
|
-
// newState.axes = unsetAxisValues(newState.axes);
|
|
48
|
-
// }
|
|
49
31
|
return newState;
|
|
50
32
|
}
|
|
51
33
|
case TOGGLE_POINTS: {
|
|
@@ -72,50 +54,25 @@ export const reducer = (state, action) => {
|
|
|
72
54
|
showTable: !newState.showTable,
|
|
73
55
|
};
|
|
74
56
|
}
|
|
75
|
-
case
|
|
76
|
-
return {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
};
|
|
57
|
+
case SAVE_INITIAL_AXES_RANGES: {
|
|
58
|
+
return produce(state, (draft) => {
|
|
59
|
+
const { initialAxesRanges } = action.payload;
|
|
60
|
+
draft.initialAxesRanges = initialAxesRanges;
|
|
61
|
+
});
|
|
80
62
|
}
|
|
81
|
-
case
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
axis.max.valid =
|
|
96
|
-
validNumber(axis.max.inputValue ?? '') &&
|
|
97
|
-
isGreaterThanMin(axis.max.value, axis.min.value);
|
|
98
|
-
axis.max.displayValue =
|
|
99
|
-
axis.max.valid && axis.max.value
|
|
100
|
-
? Number(axis.max.value).toFixed(1)
|
|
101
|
-
: undefined;
|
|
102
|
-
|
|
103
|
-
const elementValue = axis[name];
|
|
104
|
-
if (elementValue.valid) {
|
|
105
|
-
elementValue.value = nextValue;
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
if (Array.isArray(action.payload)) {
|
|
109
|
-
action.payload.forEach((element) => {
|
|
110
|
-
const { name, value: nextInputValue, id } = element;
|
|
111
|
-
validateElement(name, nextInputValue, id);
|
|
112
|
-
});
|
|
113
|
-
} else {
|
|
114
|
-
const { name, value: nextInputValue, id } = action.payload;
|
|
115
|
-
validateElement(name, nextInputValue, id);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return newState;
|
|
63
|
+
case UPDATE_AXES_RANGES: {
|
|
64
|
+
return produce(state, (draft) => {
|
|
65
|
+
const { axes } = action.payload;
|
|
66
|
+
draft.axes = axes;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
case RESET_AXES_RANGES: {
|
|
70
|
+
return produce(state, (draft) => {
|
|
71
|
+
const { axes } = state;
|
|
72
|
+
draft.axes = axes.map((axis) => ({
|
|
73
|
+
id: axis.id,
|
|
74
|
+
}));
|
|
75
|
+
});
|
|
119
76
|
}
|
|
120
77
|
case SET_POINTS_ZOOM_DEFAULTS:
|
|
121
78
|
const { showPoints, enableZoom, enablePan, showLine } = action.payload;
|
|
@@ -137,11 +94,6 @@ export const reducer = (state, action) => {
|
|
|
137
94
|
...newState,
|
|
138
95
|
showAnnotationLineIndex: updatedIndexes,
|
|
139
96
|
};
|
|
140
|
-
case SET_AXIS_MIN_MAX:
|
|
141
|
-
return {
|
|
142
|
-
...newState,
|
|
143
|
-
scalesMinMax: action.payload,
|
|
144
|
-
};
|
|
145
97
|
default: {
|
|
146
98
|
return newState;
|
|
147
99
|
}
|
|
@@ -40,30 +40,6 @@ export const getPlugins = (graph, legend, state = null) => {
|
|
|
40
40
|
return plugins;
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
export const getAxisValue = (value, setValue = false) => {
|
|
44
|
-
if (value !== undefined) {
|
|
45
|
-
if (value === '-') {
|
|
46
|
-
return {
|
|
47
|
-
inputValue: value,
|
|
48
|
-
displayValue: '',
|
|
49
|
-
value: '',
|
|
50
|
-
valid: false,
|
|
51
|
-
};
|
|
52
|
-
} else {
|
|
53
|
-
return {
|
|
54
|
-
inputValue: value.toString(),
|
|
55
|
-
displayValue: value,
|
|
56
|
-
value: setValue ? undefined : value,
|
|
57
|
-
valid: true,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
} else {
|
|
61
|
-
return {
|
|
62
|
-
valid: true /* default always valid, for unknowable reasons */,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
|
|
67
43
|
/**
|
|
68
44
|
* @param {string[]} colors - color schema
|
|
69
45
|
* @return {string} - random color
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export const getScalesMinMax = (chartRef) => {
|
|
2
|
-
const { scales = {} } = chartRef.current || {};
|
|
3
|
-
return Object.keys(scales).reduce((acc, key) => {
|
|
4
|
-
return {
|
|
5
|
-
...acc,
|
|
6
|
-
[key]: {
|
|
7
|
-
min: scales[key]?.min,
|
|
8
|
-
max: scales[key]?.max,
|
|
9
|
-
},
|
|
10
|
-
};
|
|
11
|
-
}, {});
|
|
12
|
-
};
|