@oliasoft-open-source/charts-library 2.13.4 → 2.14.0-beta-1

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.
Files changed (36) hide show
  1. package/package.json +2 -2
  2. package/release-notes.md +4 -0
  3. package/src/components/controls/axes-options/axes-options.jsx +43 -9
  4. package/src/components/line-chart/constants/default-translations.js +24 -0
  5. package/src/components/line-chart/{line-chart-consts.js → constants/line-chart-consts.js} +1 -0
  6. package/src/components/line-chart/controls/axes-options/action-types.js +5 -0
  7. package/src/components/{controls → line-chart/controls}/axes-options/axes-options-form-state.js +19 -20
  8. package/src/components/line-chart/controls/controls.jsx +174 -0
  9. package/src/components/line-chart/controls/drag-options.jsx +112 -0
  10. package/src/components/line-chart/controls/legend-options.jsx +36 -0
  11. package/src/components/{controls → line-chart/controls}/line-options.jsx +18 -8
  12. package/src/components/line-chart/hooks/use-chart-functions.js +257 -0
  13. package/src/components/line-chart/hooks/use-chart-options.js +155 -0
  14. package/src/components/line-chart/hooks/use-chart-plugins.js +26 -0
  15. package/src/components/line-chart/hooks/use-toggle-handler.js +33 -0
  16. package/src/components/line-chart/line-chart.jsx +54 -447
  17. package/src/components/line-chart/state/initial-state.js +7 -8
  18. package/src/components/line-chart/state/line-chart-reducer.js +68 -86
  19. package/src/components/line-chart/state/use-chart-state.js +73 -12
  20. package/src/components/line-chart/{axis-scales → utils/axis-scales}/axis-scales.js +3 -3
  21. package/src/components/line-chart/{datalabels-alignment → utils/datalabels-alignment}/get-datalabels-position.js +1 -1
  22. package/src/components/line-chart/utils/generate-line-chart-datasets.js +114 -0
  23. package/src/components/line-chart/{get-line-chart-data-labels.js → utils/get-line-chart-data-labels.js} +2 -2
  24. package/src/components/line-chart/{get-line-chart-scales.js → utils/get-line-chart-scales.js} +11 -11
  25. package/src/components/line-chart/{get-line-chart-tooltips.js → utils/get-line-chart-tooltips.js} +6 -3
  26. package/src/components/line-chart/utils/get-translations/get-translations.js +17 -0
  27. package/src/helpers/chart-utils.js +3 -5
  28. package/src/helpers/enums.js +9 -0
  29. package/src/components/controls/controls.jsx +0 -114
  30. package/src/components/controls/drag-options.jsx +0 -98
  31. package/src/components/controls/legend-options.jsx +0 -25
  32. /package/src/components/{controls → line-chart/controls}/controls.module.less +0 -0
  33. /package/src/components/line-chart/{datalabels-alignment → utils/datalabels-alignment}/get-alignment-condition.js +0 -0
  34. /package/src/components/line-chart/{datalabels-alignment → utils/datalabels-alignment}/get-alignment-data.js +0 -0
  35. /package/src/components/line-chart/{get-axes-ranges-from-chart.js → utils/get-axes-ranges-from-chart.js} +0 -0
  36. /package/src/components/line-chart/{line-chart-utils.js → utils/line-chart-utils.js} +0 -0
@@ -0,0 +1,257 @@
1
+ import { useCallback } from 'react';
2
+ import { triggerBase64Download } from 'react-base64-downloader';
3
+ import { AxisType, Key, PanZoomMode } from '../../../helpers/enums';
4
+ import { getChartFileName } from '../../../helpers/chart-utils';
5
+ import { generateAxisId } from '../utils/line-chart-utils';
6
+ import { RESET_AXES_RANGES, UPDATE_AXES_RANGES } from '../state/action-types';
7
+
8
+ /**
9
+ * Custom hook that encapsulates the chart-related functions and their dependencies
10
+ * @param {Object} chartRef - Chart reference
11
+ * @param {Object} state - The component state
12
+ * @param {Object} options - Chart options
13
+ * @param {Function} dispatch - Dispatch function from useReducer
14
+ * @param {Array} generatedDatasets - Chart datasets
15
+ * @returns {Object} An object containing the chart-related functions
16
+ */
17
+ export const useChartFunctions = ({
18
+ chartRef,
19
+ state,
20
+ options,
21
+ dispatch,
22
+ generatedDatasets,
23
+ }) => {
24
+ const {
25
+ annotations,
26
+ interactions: { onLegendClick, onPointHover, onPointUnhover },
27
+ additionalAxesOptions,
28
+ axes,
29
+ } = options;
30
+
31
+ /**
32
+ * Handles legend click events
33
+ * @param {Event} e - Click event
34
+ * @param {Object} legendItem - Legend item data
35
+ */
36
+ const legendClick = useCallback(
37
+ (e, legendItem) => {
38
+ const { datasetIndex } = legendItem;
39
+ const chartInstance = chartRef.current;
40
+ const { datasets } = chartInstance.data;
41
+ const dataset = datasets[datasetIndex];
42
+ const meta = chartInstance.getDatasetMeta(datasetIndex);
43
+ meta.hidden = meta.hidden === null ? !dataset.hidden : null;
44
+
45
+ if (annotations.controlAnnotation && dataset.isAnnotation) {
46
+ const { annotationIndex } = dataset;
47
+ dispatch({ type: 'TOGGLE_ANNOTATION', payload: { annotationIndex } });
48
+ }
49
+
50
+ // Show/hide entire display group
51
+ if (dataset.displayGroup) {
52
+ datasets.forEach((ds, ix) => {
53
+ if (ds.displayGroup !== dataset.displayGroup) return;
54
+ chartInstance.getDatasetMeta(ix).hidden = meta.hidden;
55
+ });
56
+ }
57
+
58
+ if (onLegendClick) {
59
+ onLegendClick(legendItem?.text, legendItem.hidden);
60
+ }
61
+
62
+ chartInstance.update();
63
+ },
64
+ [onLegendClick, annotations],
65
+ );
66
+
67
+ /**
68
+ * Resets the chart zoom and updates the axes ranges
69
+ * @returns {Function} Memoized resetZoom function
70
+ */
71
+ const resetZoom = useCallback(() => {
72
+ const chartInstance = chartRef.current;
73
+ chartInstance.resetZoom();
74
+ dispatch({ type: 'RESET_AXES_RANGES' });
75
+ }, [chartRef]);
76
+
77
+ /**
78
+ * Handles the hover event on chart points
79
+ * @param {Object} hoveredPoint - Currently hovered point
80
+ * @param {Function} setHoveredPoint - Setter function for hoveredPoint state
81
+ * @returns {Function} Memoized onHover function
82
+ */
83
+ const onHover = useCallback((hoveredPoint, setHoveredPoint) => {
84
+ return (evt, hoveredItems) => {
85
+ if (!hoveredItems?.length && onPointUnhover && hoveredPoint) {
86
+ setHoveredPoint(null);
87
+ onPointUnhover(evt);
88
+ }
89
+
90
+ if (hoveredItems?.length && onPointHover) {
91
+ const { index, datasetIndex } = hoveredItems[0];
92
+ const dataset = generatedDatasets[datasetIndex];
93
+ const point = dataset?.data[index];
94
+
95
+ if (point && hoveredPoint !== point) {
96
+ setHoveredPoint(point);
97
+ onPointHover(evt, datasetIndex, index, generatedDatasets);
98
+ }
99
+ }
100
+ };
101
+ }, []);
102
+
103
+ /**
104
+ * Handles the download of the chart as an image
105
+ * @returns {Function} Memoized handleDownload function
106
+ */
107
+ const handleDownload = useCallback(() => {
108
+ const chart = chartRef.current;
109
+
110
+ // Add temporary canvas background
111
+ const { ctx } = chart;
112
+ ctx.save();
113
+ ctx.globalCompositeOperation = 'destination-over';
114
+ ctx.fillStyle = 'white';
115
+ ctx.fillRect(0, 0, chart.width, chart.height);
116
+ ctx.restore();
117
+
118
+ const base64Image = chart.toBase64Image();
119
+ const fileName = getChartFileName(state.axes);
120
+
121
+ triggerBase64Download(base64Image, fileName);
122
+ }, [chartRef, state.axes]);
123
+
124
+ /**
125
+ * Handles the key down event for the chart
126
+ * @returns {Function} Memoized handleKeyDown function
127
+ */
128
+ const handleKeyDown = useCallback(
129
+ (evt) => {
130
+ if (evt.key === Key.Shift) {
131
+ const chart = chartRef.current;
132
+ chart.config.options.plugins.zoom.zoom.mode = PanZoomMode.Y;
133
+ chart.config.options.plugins.zoom.pan.mode = PanZoomMode.Y;
134
+ chart.update();
135
+ }
136
+ },
137
+ [chartRef],
138
+ );
139
+
140
+ /**
141
+ * Handles the key up event for the chart
142
+ * @returns {Function} Memoized handleKeyUp function
143
+ */
144
+ const handleKeyUp = useCallback(
145
+ (evt) => {
146
+ if (evt.key === Key.Shift) {
147
+ const chart = chartRef.current;
148
+ chart.config.options.plugins.zoom.zoom.mode = PanZoomMode.Z;
149
+ chart.config.options.plugins.zoom.pan.mode = PanZoomMode.Z;
150
+ chart.update();
151
+ }
152
+ },
153
+ [chartRef],
154
+ );
155
+
156
+ /**
157
+ * Get the controls axes based on the state and additionalAxesOptions.
158
+ * @returns {Array} Array of controls axes.
159
+ */
160
+ const controlsAxes = state.axes.map((axis, i) => {
161
+ const axisType = i ? AxisType.Y : AxisType.X; // only first element is 'x' - rest is 'y'
162
+ const min = axis.min ?? additionalAxesOptions?.range?.[axisType]?.min;
163
+ const max = axis.max ?? additionalAxesOptions?.range?.[axisType]?.max;
164
+ return {
165
+ ...axis,
166
+ // only add min and max properties if they are defined:
167
+ ...(min ? { min } : {}),
168
+ ...(max ? { max } : {}),
169
+ };
170
+ });
171
+
172
+ /**
173
+ * Get the controls axes labels based on the propsAxes.
174
+ * @param {Object} propsAxes - Props axes object.
175
+ * @returns {Array} Array of controls axes labels.
176
+ */
177
+ const getControlsAxesLabels = useCallback(
178
+ (propsAxes) => {
179
+ if (!Object.keys(propsAxes)?.length) {
180
+ return [];
181
+ }
182
+
183
+ const getAxesLabels = (axes, axisType) => {
184
+ if (!axes[axisType] || !axes[axisType]?.length) {
185
+ return [];
186
+ } else {
187
+ return axes[axisType].map((axisObj, index) => {
188
+ return {
189
+ id: generateAxisId(axisType, index, axes[axisType].length > 1),
190
+ label: axisObj?.label || '',
191
+ };
192
+ });
193
+ }
194
+ };
195
+
196
+ const axesLabels = [
197
+ ...getAxesLabels(propsAxes, AxisType.X),
198
+ ...getAxesLabels(propsAxes, AxisType.Y),
199
+ ];
200
+ return axesLabels;
201
+ },
202
+ [axes],
203
+ );
204
+
205
+ /**
206
+ * Update axes ranges from the chart.
207
+ * @param {Object} chart - Chart instance.
208
+ */
209
+ const updateAxesRangesFromChart = useCallback(() => {
210
+ const { scales = {} } = chartRef?.current || {};
211
+ const axes = Object.entries(scales).map(([key, { min, max }]) => {
212
+ return {
213
+ id: key,
214
+ min: min ?? 0,
215
+ max: max ?? 0,
216
+ };
217
+ });
218
+ dispatch({
219
+ type: UPDATE_AXES_RANGES,
220
+ payload: { axes },
221
+ });
222
+ }, [axes]);
223
+
224
+ /**
225
+ * Handler for resetting axes ranges.
226
+ * @type {Function}
227
+ */
228
+ const onResetAxes = useCallback(() => {
229
+ // Dispatch the RESET_AXES_RANGES action
230
+ dispatch({ type: RESET_AXES_RANGES });
231
+ }, []);
232
+
233
+ /**
234
+ * Handler for updating axes ranges.
235
+ * @param {Object} axesInfo - Object containing axes information.
236
+ * @param {Array} axesInfo.axes - Array of axes to update.
237
+ * @type {Function}
238
+ */
239
+ const onUpdateAxes = useCallback(({ axes }) => {
240
+ // Dispatch the UPDATE_AXES_RANGES action with payload
241
+ dispatch({ type: UPDATE_AXES_RANGES, payload: { axes } });
242
+ }, []);
243
+
244
+ return {
245
+ legendClick,
246
+ resetZoom,
247
+ onHover,
248
+ handleDownload,
249
+ handleKeyDown,
250
+ handleKeyUp,
251
+ controlsAxes,
252
+ controlsAxesLabels: getControlsAxesLabels(axes),
253
+ updateAxesRangesFromChart,
254
+ onResetAxes,
255
+ onUpdateAxes,
256
+ };
257
+ };
@@ -0,0 +1,155 @@
1
+ import { useMemo, useState } from 'react';
2
+ import {
3
+ ANIMATION_DURATION,
4
+ BORDER_COLOR,
5
+ CUSTOM_LEGEND_PLUGIN_NAME,
6
+ } from '../../../helpers/chart-consts';
7
+ import {
8
+ ChartHoverMode,
9
+ Events,
10
+ PanZoomMode,
11
+ PointStyle,
12
+ } from '../../../helpers/enums';
13
+ import { autoScale } from '../utils/axis-scales/axis-scales';
14
+ import getLineChartDataLabels from '../utils/get-line-chart-data-labels';
15
+ import getAnnotation from '../../../helpers/get-chart-annotation';
16
+ import { ZOOM_BOX_BACKGROUND_COLOR } from '../constants/line-chart-consts';
17
+ import getLineChartToolTips from '../utils/get-line-chart-tooltips';
18
+ import { getLegend } from '../../../helpers/chart-utils';
19
+ import getDraggableData from '../../../helpers/get-draggableData';
20
+ import { useChartFunctions } from './use-chart-functions';
21
+
22
+ /**
23
+ * Custom hook to generate chart options.
24
+ * @function
25
+ * @param {Object} chartRef - The chart reference.
26
+ * @param {Object} state - The chart state.
27
+ * @param {Object} options - The chart options.
28
+ * @param {Function} dispatch - The dispatch function for state management.
29
+ * @param {Array} generatedDatasets - The generated datasets.
30
+ * @returns {Object} The chart options object.
31
+ */
32
+ export const useChartOptions = ({
33
+ chartRef,
34
+ state,
35
+ options,
36
+ dispatch,
37
+ generatedDatasets,
38
+ }) => {
39
+ const {
40
+ interactions: { onAnimationComplete },
41
+ } = options;
42
+
43
+ const {
44
+ enableDragPoints,
45
+ zoomEnabled,
46
+ panEnabled,
47
+ lineEnabled,
48
+ showAnnotationLineIndex,
49
+ legendEnabled,
50
+ } = state;
51
+
52
+ const [hoveredPoint, setHoveredPoint] = useState(null);
53
+ const { legendClick, updateAxesRangesFromChart, onHover } = useChartFunctions(
54
+ {
55
+ chartRef,
56
+ state,
57
+ options,
58
+ dispatch,
59
+ generatedDatasets,
60
+ },
61
+ );
62
+
63
+ const datalabels = getLineChartDataLabels(options);
64
+ const tooltip = getLineChartToolTips(options);
65
+ const annotation = useMemo(
66
+ () => getAnnotation(options, state),
67
+ [showAnnotationLineIndex, legendClick],
68
+ );
69
+ const legend = useMemo(
70
+ () => getLegend(options, legendClick, state),
71
+ [legendEnabled],
72
+ );
73
+
74
+ const scales = useMemo(
75
+ () => autoScale(options, state, generatedDatasets),
76
+ [options, state, generatedDatasets],
77
+ );
78
+
79
+ const dragData = useMemo(
80
+ () => enableDragPoints && getDraggableData(options),
81
+ [enableDragPoints, options],
82
+ );
83
+
84
+ const panOptions = useMemo(
85
+ () => ({
86
+ enabled: panEnabled,
87
+ mode: PanZoomMode.XY,
88
+ onPanComplete({ chart }) {
89
+ updateAxesRangesFromChart(chart);
90
+ },
91
+ }),
92
+ [panEnabled],
93
+ );
94
+
95
+ const zoomOptions = useMemo(
96
+ () => ({
97
+ mode: PanZoomMode.XY,
98
+ drag: {
99
+ enabled: zoomEnabled,
100
+ threshold: 3,
101
+ backgroundColor: ZOOM_BOX_BACKGROUND_COLOR,
102
+ borderColor: BORDER_COLOR,
103
+ borderWidth: 1,
104
+ },
105
+ onZoomComplete({ chart }) {
106
+ updateAxesRangesFromChart(chart);
107
+ },
108
+ }),
109
+ [zoomEnabled],
110
+ );
111
+
112
+ const plugins = {
113
+ datalabels,
114
+ annotation,
115
+ zoom: { pan: panOptions, zoom: zoomOptions },
116
+ tooltip,
117
+ legend,
118
+ [CUSTOM_LEGEND_PLUGIN_NAME]: options.legend.customLegend
119
+ .customLegendPlugin && {
120
+ containerID: options.legend.customLegend.customLegendContainerID,
121
+ },
122
+ chartAreaBorder: {
123
+ borderColor: BORDER_COLOR,
124
+ },
125
+ ...dragData,
126
+ };
127
+
128
+ return useMemo(
129
+ () => ({
130
+ onHover: onHover(hoveredPoint, setHoveredPoint),
131
+ maintainAspectRatio: options.chartStyling.maintainAspectRatio,
132
+ aspectRatio: options.chartStyling.squareAspectRatio ? 1 : null,
133
+ animation: options.chartStyling.performanceMode
134
+ ? false
135
+ : {
136
+ duration: ANIMATION_DURATION.FAST,
137
+ onComplete: onAnimationComplete,
138
+ },
139
+ hover: {
140
+ mode: ChartHoverMode.Nearest,
141
+ intersect: true,
142
+ },
143
+ elements: {
144
+ line: {
145
+ pointStyle: PointStyle.Circle,
146
+ showLine: lineEnabled,
147
+ },
148
+ },
149
+ scales,
150
+ plugins,
151
+ events: Events,
152
+ }),
153
+ [state, options],
154
+ );
155
+ };
@@ -0,0 +1,26 @@
1
+ import { useCallback, useMemo } from 'react';
2
+ import { getPlugins } from '../../../helpers/chart-utils';
3
+ import { DOUBLE_CLICK } from '../constants/line-chart-consts';
4
+
5
+ export const useChartPlugins = ({ options, resetZoom }) => {
6
+ const { graph, legend } = options;
7
+ const handleDoubleClick = useCallback(
8
+ (chart, args) => {
9
+ const { event } = args;
10
+ if (event.type === DOUBLE_CLICK) {
11
+ resetZoom();
12
+ }
13
+ },
14
+ [resetZoom],
15
+ );
16
+
17
+ return useMemo(() => {
18
+ return [
19
+ ...getPlugins(graph, legend),
20
+ {
21
+ id: 'customEventCatcher',
22
+ beforeEvent: handleDoubleClick,
23
+ },
24
+ ];
25
+ }, [handleDoubleClick]);
26
+ };
@@ -0,0 +1,33 @@
1
+ import { useCallback } from 'react';
2
+ import {
3
+ DISABLE_DRAG_OPTIONS,
4
+ TOGGLE_DRAG_POINTS,
5
+ TOGGLE_LEGEND,
6
+ TOGGLE_LINE,
7
+ TOGGLE_PAN,
8
+ TOGGLE_POINTS,
9
+ TOGGLE_TABLE,
10
+ TOGGLE_ZOOM,
11
+ } from '../state/action-types';
12
+
13
+ export const useToggleHandlers = (dispatch) => {
14
+ const createToggleHandler = useCallback(
15
+ (type) => () => {
16
+ dispatch({ type });
17
+ },
18
+ [dispatch],
19
+ );
20
+
21
+ const toggleHandlers = {
22
+ onToggleLegend: createToggleHandler(TOGGLE_LEGEND),
23
+ onToggleLine: createToggleHandler(TOGGLE_LINE),
24
+ onTogglePan: createToggleHandler(TOGGLE_PAN),
25
+ onTogglePoints: createToggleHandler(TOGGLE_POINTS),
26
+ onToggleTable: createToggleHandler(TOGGLE_TABLE),
27
+ onToggleZoom: createToggleHandler(TOGGLE_ZOOM),
28
+ onToggleDragPoints: createToggleHandler(TOGGLE_DRAG_POINTS),
29
+ onDisableDragOptions: createToggleHandler(DISABLE_DRAG_OPTIONS),
30
+ };
31
+
32
+ return toggleHandlers;
33
+ };