@oliasoft-open-source/charts-library 2.5.21 → 2.5.23

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.5.21",
3
+ "version": "2.5.23",
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,13 @@
1
1
  # Charts Library Release Notes
2
2
 
3
+ ## 2.5.23
4
+
5
+ - Patch minor regression in axis limits introduced in 2.5.22
6
+
7
+ ## 2.5.22
8
+
9
+ - Improve UX and input validation for axes range inputs ([OW-10305](https://oliasoft.atlassian.net/browse/OW-10305))
10
+
3
11
  ## 2.5.21
4
12
 
5
13
  - 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
- onResetAxes,
23
+ initialAxesRanges,
17
24
  axes,
18
25
  controlsAxesLabels,
19
- onSetAxisValue,
20
- panEnabled,
21
- scalesMaxMin,
22
- zoomEnabled,
26
+ onUpdateAxes,
27
+ onResetAxes,
23
28
  close,
24
29
  }) => {
25
- const isCustomValue =
26
- axes.filter((axis) => axis.max.displayValue || axis.min.displayValue)
27
- .length > 0;
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
- // TODO: Fix input values not updating first time when scales reset
46
- value={
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
- onSetAxisValue({
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
- // TODO: Fix input values not updating first time when scales reset
75
- value={
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
- onSetAxisValue({
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={close} />
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={onResetAxes}
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
- onResetAxes,
141
+ initialAxesRanges,
121
142
  axes,
122
143
  controlsAxesLabels,
123
- onSetAxisValue,
124
- panEnabled,
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
- onResetAxes={onResetAxes}
153
+ initialAxesRanges={initialAxesRanges}
135
154
  axes={axes}
136
155
  controlsAxesLabels={controlsAxesLabels}
137
- onSetAxisValue={onSetAxisValue}
138
- panEnabled={panEnabled}
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
- onSetAxisValue,
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
- scalesMaxMin,
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
- onResetAxes={onResetAxes}
47
+ initialAxesRanges={initialAxesRanges}
48
48
  axes={axes}
49
49
  controlsAxesLabels={controlsAxesLabels}
50
- onSetAxisValue={onSetAxisValue}
51
- panEnabled={panEnabled}
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
@@ -0,0 +1,10 @@
1
+ export const getAxesRangesFromChart = (chartRef) => {
2
+ const { scales = {} } = chartRef.current || {};
3
+ return Object.entries(scales).map(([key, { min, max }]) => {
4
+ return {
5
+ id: key,
6
+ min,
7
+ max,
8
+ };
9
+ });
10
+ };
@@ -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
- stateAxis.min?.valid && validNumber(stateAxis.min?.value)
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: stateAxis.min ?? additionalAxesOptions?.range?.[axisType]?.min,
66
+ max: 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
- UNSET_AXES_VALUES,
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 { getScalesMinMax } from './get-scales-min-max';
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
- dispatch({ type: SET_AXIS_MIN_MAX, payload: getScalesMinMax(chartRef) });
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?.value;
194
- const axesMax = state.axes[0]?.max?.value;
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: UNSET_AXES_VALUES });
283
+ dispatch({ type: RESET_AXES_RANGES });
282
284
  };
283
285
 
284
- const onHover = (evt, hoveredItems, chartInstance) => {
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
- min: axis.min ?? getAxisValue(min, true),
345
- max: axis.max ?? getAxisValue(max, true),
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 setAxisValuesInSettings = (chart) => {
380
+ const updateAxesRangesFromChart = (chart) => {
379
381
  const { scales = {} } = chart || {};
380
- const getPayload = (scales) => {
381
- return Object.keys(scales).reduce((acc, key) => {
382
- return [
383
- ...acc,
384
- {
385
- name: 'min',
386
- value: scales[key]?.min ? scales[key].min : 0,
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: SET_AXIS_VALUE,
400
- payload: getPayload(scales),
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: UNSET_AXES_VALUES });
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
- scalesMaxMin={state.scalesMinMax}
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
- setAxisValuesInSettings(chart);
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
- setAxisValuesInSettings(chart);
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 SET_AXIS_MIN_MAX = 'SET_AXIS_MIN_MAX';
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: generateAxisId(axisType, index, axes[axisType].length > 1),
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: generateAxisId(axisType),
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
- SET_AXIS_MIN_MAX,
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 UNSET_AXES_VALUES: {
76
- return {
77
- ...newState,
78
- axes: unsetAxisValues(newState.axes),
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 SET_AXIS_VALUE: {
82
- const validateElement = (name, nextInputValue, id) => {
83
- const nextValue = cleanNumStr(nextInputValue);
84
- const axis = newState.axes.find((a) => a.id === id);
85
- axis.min = getAxisValue(name === 'min' ? nextValue : axis.min?.value);
86
- axis.max = getAxisValue(name === 'max' ? nextValue : axis.max?.value);
87
- axis.min.valid =
88
- validNumber(axis.min.inputValue ?? '') &&
89
- isLessThanMax(axis.min.value, axis.max.value);
90
- axis.min.displayValue =
91
- axis.min.valid && axis.min.value
92
- ? Number(axis.min.value).toFixed(1)
93
- : undefined;
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
- };