@oliasoft-open-source/charts-library 2.0.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.
Files changed (79) hide show
  1. package/.eslintignore +2 -0
  2. package/.eslintrc.js +129 -0
  3. package/.gitlab-ci.yml +77 -0
  4. package/.husky/pre-commit +4 -0
  5. package/.prettierignore +3 -0
  6. package/.prettierrc +4 -0
  7. package/.storybook/main.js +40 -0
  8. package/LICENSE +21 -0
  9. package/README.md +5 -0
  10. package/babel.config.js +29 -0
  11. package/index.js +9 -0
  12. package/jest.config.js +9 -0
  13. package/package.json +96 -0
  14. package/src/components/bar-chart/bar-chart-prop-types.js +181 -0
  15. package/src/components/bar-chart/bar-chart.interface.ts +83 -0
  16. package/src/components/bar-chart/bar-chart.jsx +247 -0
  17. package/src/components/bar-chart/bar-chart.module.less +56 -0
  18. package/src/components/bar-chart/basic.stories.jsx +752 -0
  19. package/src/components/bar-chart/charts.stories.jsx +119 -0
  20. package/src/components/bar-chart/get-bar-chart-data-labels.js +45 -0
  21. package/src/components/bar-chart/get-bar-chart-scales.js +147 -0
  22. package/src/components/bar-chart/get-bar-chart-tooltips.js +100 -0
  23. package/src/components/line-chart/Controls/Controls.jsx +59 -0
  24. package/src/components/line-chart/Controls/Controls.module.less +21 -0
  25. package/src/components/line-chart/Controls/Layer.jsx +169 -0
  26. package/src/components/line-chart/basic.stories.jsx +735 -0
  27. package/src/components/line-chart/charts.stories.jsx +264 -0
  28. package/src/components/line-chart/get-line-chart-data-labels.js +24 -0
  29. package/src/components/line-chart/get-line-chart-scales.js +131 -0
  30. package/src/components/line-chart/get-line-chart-tooltips.js +91 -0
  31. package/src/components/line-chart/line-chart-consts.js +6 -0
  32. package/src/components/line-chart/line-chart-prop-types.js +187 -0
  33. package/src/components/line-chart/line-chart-utils.js +163 -0
  34. package/src/components/line-chart/line-chart.interface.ts +103 -0
  35. package/src/components/line-chart/line-chart.jsx +423 -0
  36. package/src/components/line-chart/line-chart.minor-gridlines-plugin.js +78 -0
  37. package/src/components/line-chart/line-chart.minor-gridlines-plugin.test.js +34 -0
  38. package/src/components/line-chart/line-chart.module.less +56 -0
  39. package/src/components/line-chart/state/action-types.js +9 -0
  40. package/src/components/line-chart/state/initial-state.js +51 -0
  41. package/src/components/line-chart/state/line-chart-reducer.js +115 -0
  42. package/src/components/line-chart/stories/shapes/cubes.stories.jsx +326 -0
  43. package/src/components/line-chart/stories/shapes/pyramid.stories.jsx +189 -0
  44. package/src/components/line-chart/stories/shapes/round.stories.jsx +339 -0
  45. package/src/components/line-chart/stories/shapes/triangle.stories.jsx +166 -0
  46. package/src/components/pie-chart/basic.stories.jsx +390 -0
  47. package/src/components/pie-chart/charts.stories.jsx +66 -0
  48. package/src/components/pie-chart/pie-chart-prop-types.js +111 -0
  49. package/src/components/pie-chart/pie-chart-utils.js +55 -0
  50. package/src/components/pie-chart/pie-chart.interface.ts +61 -0
  51. package/src/components/pie-chart/pie-chart.jsx +477 -0
  52. package/src/components/pie-chart/pie-chart.module.less +56 -0
  53. package/src/components/scatter-chart/scatter-chart.intefrace.ts +32 -0
  54. package/src/components/scatter-chart/scatter-chart.jsx +13 -0
  55. package/src/components/scatter-chart/scatter.stories.jsx +196 -0
  56. package/src/helpers/chart-consts.js +82 -0
  57. package/src/helpers/chart-interface.ts +54 -0
  58. package/src/helpers/chart-utils.js +178 -0
  59. package/src/helpers/container.jsx +60 -0
  60. package/src/helpers/disabled-context.js +8 -0
  61. package/src/helpers/enums.js +84 -0
  62. package/src/helpers/get-chart-annotation.js +91 -0
  63. package/src/helpers/styles.js +68 -0
  64. package/src/helpers/text.js +6 -0
  65. package/src/style/external.less +4 -0
  66. package/src/style/fonts/lato/Lato-Bold.woff2 +0 -0
  67. package/src/style/fonts/lato/Lato-BoldItalic.woff2 +0 -0
  68. package/src/style/fonts/lato/Lato-Italic.woff2 +0 -0
  69. package/src/style/fonts/lato/Lato-Regular.woff2 +0 -0
  70. package/src/style/fonts.less +27 -0
  71. package/src/style/global.less +43 -0
  72. package/src/style/reset/reset.less +28 -0
  73. package/src/style/shared.less +24 -0
  74. package/src/style/variables.less +91 -0
  75. package/webpack/webpack.common.js +39 -0
  76. package/webpack/webpack.common.rules.js +107 -0
  77. package/webpack/webpack.dev.js +22 -0
  78. package/webpack/webpack.prod.js +23 -0
  79. package/webpack/webpack.resolve.js +22 -0
@@ -0,0 +1,163 @@
1
+ import Fraction from 'fraction.js';
2
+
3
+ const isNull = (str) => str === null;
4
+ const isUndefined = (str) => str === undefined;
5
+ const isArray = (str) => str && str.constructor === Array;
6
+ const isObject = (str) => str && str.constructor === Object;
7
+ const hasDivisor = (str) =>
8
+ str && typeof str.includes === 'function' && str.includes('/');
9
+ export const isEmptyString = (str) => str === '';
10
+ const isTrailingPeriodSeparator = (str) => str && str[str.length - 1] === '.';
11
+ const isTrailingCommaSeparator = (str) => str && str[str.length - 1] === ',';
12
+
13
+ export const charCount = (chr, str) => {
14
+ let total = 0,
15
+ last_location = 0,
16
+ single_char = (chr + '')[0];
17
+ while ((last_location = str.indexOf(single_char, last_location) + 1)) {
18
+ total += 1;
19
+ }
20
+ return total;
21
+ };
22
+
23
+ export const cleanNumStr = (str) => {
24
+ str += '';
25
+ let slashCount = charCount('/', str);
26
+ let spaceCount = charCount(' ', str) + charCount(' ', str);
27
+ let dotcount = charCount('.', str);
28
+ let commacount = charCount(',', str);
29
+ if (slashCount === 0 && spaceCount > 0) str = str.replace(/\s/g, '');
30
+ if (commacount > 1) str = str.replace(/,/g, '');
31
+ if (dotcount > 1) str = str.replace(/\./g, '');
32
+ commacount = charCount(',', str);
33
+ dotcount = charCount('.', str);
34
+ if (dotcount === 1 && commacount === 1) {
35
+ // One of each, make the rightmost act as decimal separator
36
+ if (str.indexOf(',') > str.indexOf('.')) {
37
+ str = str.replace('.', '');
38
+ str = str.replace(',', '.');
39
+ } else {
40
+ str = str.replace(',', '');
41
+ }
42
+ if (str.indexOf('.') === 0) {
43
+ str = 0 + str;
44
+ }
45
+ return str;
46
+ }
47
+ if (dotcount === 1 && commacount) str = str.replace(',', '');
48
+ if (!dotcount && commacount) str = str.replace(',', '.');
49
+ if (str.indexOf('.') === 0) {
50
+ str = 0 + str;
51
+ }
52
+ return str;
53
+ };
54
+
55
+ export const numFraction = (str) => {
56
+ if (str instanceof Array || str === null || str === undefined) {
57
+ return str;
58
+ }
59
+ if (typeof str === 'string') {
60
+ str = str.trim(); //trailing whitespace causes InvalidParameter error in fraction.js
61
+ }
62
+ let result = str;
63
+ try {
64
+ result = Fraction(str);
65
+ } catch (e) {
66
+ //do nothing
67
+ }
68
+ return result.valueOf();
69
+ };
70
+
71
+ export const isValidNum = (input) => {
72
+ let result = false;
73
+ if (isEmptyString(input)) {
74
+ result = true;
75
+ } else {
76
+ if (
77
+ !(
78
+ isNull(input) ||
79
+ isUndefined(input) ||
80
+ isTrailingPeriodSeparator(input) ||
81
+ isTrailingCommaSeparator(input) ||
82
+ isArray(input) ||
83
+ isObject(input)
84
+ )
85
+ ) {
86
+ let number;
87
+ const cleaned = cleanNumStr(input);
88
+ if (hasDivisor(cleaned)) {
89
+ //numFraction is slow, so only call it if needed
90
+ number = numFraction(cleaned);
91
+ } else {
92
+ number = parseFloat(cleaned);
93
+ }
94
+ if (!isNaN(number)) {
95
+ result = true;
96
+ }
97
+ }
98
+ }
99
+ return result;
100
+ };
101
+
102
+ export const toNumber = (input, defaultValue, minimum) => {
103
+ let result;
104
+ defaultValue = defaultValue === undefined ? input : defaultValue;
105
+ if (
106
+ isNull(input) ||
107
+ isUndefined(input) ||
108
+ isEmptyString(input) ||
109
+ isTrailingPeriodSeparator(input) ||
110
+ isTrailingCommaSeparator(input) ||
111
+ isArray(input) ||
112
+ isObject(input)
113
+ ) {
114
+ result = defaultValue;
115
+ } else {
116
+ let number;
117
+ const cleaned = cleanNumStr(input);
118
+ if (hasDivisor(cleaned)) {
119
+ //numFraction is slow, so only call it if needed
120
+ number = numFraction(cleaned);
121
+ } else {
122
+ number = parseFloat(cleaned);
123
+ }
124
+ if (isNaN(number)) {
125
+ result = defaultValue;
126
+ } else if (minimum && number < minimum) {
127
+ result = minimum;
128
+ } else {
129
+ result = number;
130
+ }
131
+ }
132
+ return result;
133
+ };
134
+
135
+ export const validNumber = (value) => isValidNum(value);
136
+
137
+ export const toNum = (value) => {
138
+ const asNumber = toNumber(value);
139
+ return value === '' || isNaN(asNumber) ? undefined : asNumber;
140
+ };
141
+
142
+ export const isLessThanMax = (value, max) => {
143
+ return value === undefined || max === undefined || value < max;
144
+ };
145
+
146
+ export const isGreaterThanMin = (value, min) => {
147
+ return value === undefined || min === undefined || value > min;
148
+ };
149
+
150
+ /**
151
+ * Truncates a number to the specified decimal digits.
152
+ * Truncate refers to shaving digits off a number without rounding it.
153
+ * @param {Number} number
154
+ * @param {Number} digits
155
+ * @returns {Number}
156
+ */
157
+ export const truncateDecimals = (number, digits) => {
158
+ const multiplier = 10 ** digits;
159
+ const adjustedNum = number * multiplier;
160
+ const truncatedNum = Math[adjustedNum < 0 ? 'ceil' : 'floor'](adjustedNum);
161
+
162
+ return truncatedNum / multiplier;
163
+ };
@@ -0,0 +1,103 @@
1
+ import {
2
+ IChartAnnotations,
3
+ IChartInteractions,
4
+ IChartLegend,
5
+ IChartStyling,
6
+ } from "../../helpers/chart-interface";
7
+
8
+ export interface IChartOptions {
9
+ showPoints: boolean;
10
+ enableZoom: boolean;
11
+ enablePan: boolean;
12
+ closeOnOutsideClick: boolean;
13
+ }
14
+
15
+ export interface ILineChartGraph {
16
+ lineTension: number;
17
+ spanGaps: boolean;
18
+ showDataLabels: boolean;
19
+ showMinorGridlines: boolean;
20
+ }
21
+
22
+ export interface ILineChartTooltip {
23
+ tooltips: boolean;
24
+ showLabelsInTooltips: boolean;
25
+ hideSimulationName: boolean;
26
+ }
27
+
28
+
29
+ export interface IChartRange {
30
+ min: number;
31
+ max: number;
32
+ }
33
+
34
+ export interface ILineChartAdditionalAxesOptions {
35
+ chartScaleType: 'linear' | 'logarithmic';
36
+ reverse: boolean;
37
+ beginAtZero: boolean;
38
+ stepSize: number;
39
+ truncateAxisNumbersToDigitsCallback: number;
40
+ suggestedMin: number;
41
+ suggestedMax:number;
42
+ range: {
43
+ x: IChartRange;
44
+ y: IChartRange;
45
+ };
46
+ }
47
+
48
+ export interface ILineChartAxes<PositionType> {
49
+ label: string;
50
+ position: PositionType;
51
+ color: string;
52
+ }
53
+
54
+ export interface ILineChartOptions {
55
+ title: string | string[];
56
+ axes: {
57
+ x: ILineChartAxes<'top' | 'bottom'>[];
58
+ y: ILineChartAxes<'left' | 'right'>[];
59
+ };
60
+ additionalAxesOptions: ILineChartAdditionalAxesOptions;
61
+ chartStyling: IChartStyling;
62
+ tooltip: ILineChartTooltip;
63
+ graph: ILineChartGraph;
64
+ annotations: IChartAnnotations;
65
+ legend: IChartLegend;
66
+ chartOptions: IChartOptions;
67
+ interactions: IChartInteractions;
68
+ }
69
+
70
+ export interface IDataPoint {
71
+ x: number,
72
+ y: number,
73
+ label?: string[]
74
+ }
75
+
76
+ export interface ILineChartDataset {
77
+ label?: string,
78
+ lineTension?: number,
79
+ borderColor?: string,
80
+ pointBackgroundColor?: string,
81
+ backgroundColor?: string,
82
+ pointRadius?: number,
83
+ pointHoverRadius?: number,
84
+ pointHitRadius?: number,
85
+ borderWidth?: number,
86
+ fill?: boolean,
87
+ yAxisID?: string,
88
+ formation?: boolean,
89
+ data: IDataPoint[],
90
+ }
91
+
92
+ export interface ILineChartData {
93
+ //TODO: revisit data interface definition after project is more stable
94
+ data: {
95
+ labels?: string[],
96
+ datasets: ILineChartDataset[]
97
+ } | any;
98
+ options: ILineChartOptions;
99
+ }
100
+
101
+ export interface ILineChartProps {
102
+ chart: ILineChartData;
103
+ }
@@ -0,0 +1,423 @@
1
+ import React, { useEffect, useReducer, useRef, useState } from 'react';
2
+ import {
3
+ CategoryScale,
4
+ Chart as ChartJS,
5
+ defaults,
6
+ Filler,
7
+ Legend,
8
+ LinearScale,
9
+ LineElement,
10
+ LogarithmicScale,
11
+ PointElement,
12
+ Title,
13
+ Tooltip,
14
+ } from 'chart.js';
15
+ import { Line } from 'react-chartjs-2';
16
+ import zoomPlugin from 'chartjs-plugin-zoom';
17
+ import dataLabelsPlugin from 'chartjs-plugin-datalabels';
18
+ import annotationPlugin from 'chartjs-plugin-annotation';
19
+ import { triggerBase64Download } from 'react-base64-downloader';
20
+
21
+ import styles from './line-chart.module.less';
22
+ import { reducer } from './state/line-chart-reducer';
23
+ import initialState from './state/initial-state';
24
+ import {
25
+ TOGGLE_PAN,
26
+ TOGGLE_ZOOM,
27
+ TOGGLE_POINTS,
28
+ TOGGLE_LINE,
29
+ TOGGLE_LEGEND,
30
+ UNSET_AXES_VALUES,
31
+ SET_AXIS_VALUE,
32
+ TOGGLE_ANNOTATION,
33
+ SET_POINTS_ZOOM_DEFAULTS,
34
+ } from './state/action-types';
35
+ import { Controls } from './Controls/Controls';
36
+ import { getDefaultProps, LineChartPropTypes } from './line-chart-prop-types';
37
+ import getLineChartScales from './get-line-chart-scales';
38
+ import getLineChartToolTips from './get-line-chart-tooltips';
39
+ import getLineChartDataLabels from './get-line-chart-data-labels';
40
+ import {
41
+ DEFAULT_BORDER_WIDTH,
42
+ DEFAULT_HOVER_RADIUS,
43
+ DEFAULT_POINT_RADIUS,
44
+ BORDER_JOIN_STYLE,
45
+ DEFAULT_LINE_POINT_RADIUS,
46
+ DEFAULT_BACKGROUND_COLOR,
47
+ } from './line-chart-consts';
48
+
49
+ import getAnnotation from '../../helpers/get-chart-annotation';
50
+ import {
51
+ generateRandomColor,
52
+ getClassName,
53
+ getTitle,
54
+ getAxisValue,
55
+ getPlugins,
56
+ getLegend,
57
+ getChartFileName,
58
+ } from '../../helpers/chart-utils';
59
+ import {
60
+ ANIMATION_DURATION,
61
+ AUTO,
62
+ COLORS,
63
+ DARK_MODE_COLORS,
64
+ DEFAULT_COLOR,
65
+ DEFAULT_DARK_MODE_BORDER_COLOR,
66
+ DEFAULT_DARK_MODE_COLOR,
67
+ DEFAULT_FONT_FAMILY,
68
+ DEFAULT_FONT_SIZE,
69
+ } from '../../helpers/chart-consts';
70
+ import {
71
+ AxisType,
72
+ ChartHoverMode,
73
+ Key,
74
+ PanZoomMode,
75
+ PointStyle,
76
+ } from '../../helpers/enums';
77
+
78
+ ChartJS.register(
79
+ LinearScale,
80
+ PointElement,
81
+ LineElement,
82
+ CategoryScale,
83
+ LogarithmicScale,
84
+ Legend,
85
+ Tooltip,
86
+ Title,
87
+ Filler,
88
+ zoomPlugin,
89
+ dataLabelsPlugin,
90
+ annotationPlugin,
91
+ );
92
+
93
+ defaults.font.size = DEFAULT_FONT_SIZE;
94
+ defaults.color = DEFAULT_COLOR;
95
+ defaults.font.family = DEFAULT_FONT_FAMILY;
96
+ defaults.darkModeColor = DEFAULT_DARK_MODE_COLOR;
97
+ defaults.darkModeBorderColor = DEFAULT_DARK_MODE_BORDER_COLOR;
98
+
99
+ /**
100
+ * this is a line chart component
101
+ * @param {import('./line-chart.interface').ILineChartProps} props
102
+ */
103
+ const LineChart = (props) => {
104
+ const chartRef = useRef(null);
105
+ const [shiftPressed, setShiftPressed] = useState(false);
106
+ let pointHover = false;
107
+ const chart = getDefaultProps(props);
108
+ const { options } = chart;
109
+
110
+ const {
111
+ additionalAxesOptions,
112
+ annotations,
113
+ axes,
114
+ chartOptions,
115
+ chartStyling,
116
+ graph,
117
+ interactions,
118
+ legend,
119
+ } = options;
120
+ const { showLine, showPoints, enableZoom, enablePan, closeOnOutsideClick } =
121
+ chartOptions;
122
+
123
+ /**
124
+ * @type {[object, import('react').Dispatch<{type: String, payload: any}>]} useReducer
125
+ */
126
+ const [state, dispatch] = useReducer(
127
+ reducer,
128
+ {
129
+ axes,
130
+ enableZoom: chartOptions.enableZoom,
131
+ enablePan: chartOptions.enablePan,
132
+ showPoints: chartOptions.showPoints,
133
+ showLine: chartOptions.showLine,
134
+ legendDisplay: legend.display,
135
+ annotationsData: annotations.annotationsData,
136
+ },
137
+ initialState,
138
+ );
139
+
140
+ useEffect(() => {
141
+ if (chartOptions.enablePan !== true) {
142
+ dispatch({ type: TOGGLE_PAN });
143
+ }
144
+ }, []);
145
+
146
+ useEffect(() => {
147
+ dispatch({
148
+ type: SET_POINTS_ZOOM_DEFAULTS,
149
+ payload: { showLine, showPoints, enableZoom, enablePan },
150
+ });
151
+ }, [showLine, showPoints, enableZoom, enablePan]);
152
+
153
+ const generateLineChartDatasets = (datasets) => {
154
+ const copyDataset = [...datasets];
155
+
156
+ // Add annotations to dataset to have them appear in legend.
157
+ if (
158
+ annotations.controlAnnotation &&
159
+ annotations.showAnnotations &&
160
+ annotations.annotationsData?.length
161
+ ) {
162
+ annotations.annotationsData.forEach((annotation, index) => {
163
+ const color =
164
+ annotation.color ||
165
+ (chartStyling.darkMode ? DARK_MODE_COLORS[index] : COLORS[index]);
166
+ copyDataset.push({
167
+ isAnnotation: true,
168
+ label: annotation.label,
169
+ annotationIndex: index,
170
+ backgroundColor: color,
171
+ pointBackgroundColor: color,
172
+ borderColor: color,
173
+ data: [{}],
174
+ });
175
+ });
176
+ }
177
+
178
+ const generatedDatasets = copyDataset.map((line, i) => {
179
+ if (line.formation) {
180
+ const axesMin = state.axes[0]?.min?.value;
181
+ const axesMax = state.axes[0]?.max?.value;
182
+ // line with formation flag has 3 points: start point, mid-point with label, and end point.
183
+ const [startPoint, midPointWithLabel, endPoint] = line.data;
184
+
185
+ if (axesMin && startPoint?.x) {
186
+ line.data[0].x = axesMin < startPoint?.x ? axesMin : startPoint?.x;
187
+ }
188
+
189
+ if (axesMax && endPoint?.x) {
190
+ line.data[2].x = axesMax > endPoint?.x ? axesMax : endPoint?.x;
191
+ }
192
+ }
193
+
194
+ // line does not render if first datapoints are null
195
+ if (line.data[0] === null) {
196
+ line.data.shift();
197
+ }
198
+
199
+ line.showLine = state.lineEnabled;
200
+ const linePointRadius = line.pointRadius
201
+ ? parseFloat(line.pointRadius)
202
+ : DEFAULT_POINT_RADIUS;
203
+ const pointHoverRadius = line.pointHoverRadius
204
+ ? parseFloat(line.pointHoverRadius)
205
+ : DEFAULT_HOVER_RADIUS;
206
+ const indexedColor = chartStyling.darkMode
207
+ ? DARK_MODE_COLORS[i]
208
+ : COLORS[i];
209
+
210
+ return {
211
+ ...line,
212
+ lineTension: graph.lineTension,
213
+ spanGaps: graph.spanGaps,
214
+ borderWidth: parseFloat(line.borderWidth) || DEFAULT_BORDER_WIDTH,
215
+ borderDash: line.borderDash || [],
216
+ borderJoinStyle: BORDER_JOIN_STYLE,
217
+ borderColor:
218
+ line.borderColor || indexedColor || generateRandomColor(COLORS),
219
+ backgroundColor: line.backgroundColor || DEFAULT_BACKGROUND_COLOR,
220
+ pointBackgroundColor:
221
+ line.pointBackgroundColor ||
222
+ indexedColor ||
223
+ generateRandomColor(COLORS),
224
+ pointRadius:
225
+ state.pointsEnabled === true
226
+ ? linePointRadius
227
+ : DEFAULT_LINE_POINT_RADIUS,
228
+ pointHoverRadius,
229
+ pointHitRadius: line.pointHitRadius || pointHoverRadius,
230
+ };
231
+ });
232
+ return generatedDatasets;
233
+ };
234
+
235
+ const generatedDatasets = generateLineChartDatasets(chart.data.datasets);
236
+
237
+ const legendClick = (e, legendItem) => {
238
+ const index = legendItem.datasetIndex;
239
+ const chartInstance = chartRef.current;
240
+ const { datasets } = chartInstance.data;
241
+ const dataset = datasets[index];
242
+ const meta = chartInstance.getDatasetMeta(index);
243
+ meta.hidden = meta.hidden === null ? !dataset.hidden : null;
244
+
245
+ if (annotations.controlAnnotation && dataset.isAnnotation) {
246
+ const { annotationIndex } = dataset;
247
+ dispatch({ type: TOGGLE_ANNOTATION, payload: { annotationIndex } });
248
+ }
249
+
250
+ // Show/hide entire display group
251
+ if (dataset.displayGroup) {
252
+ datasets.forEach((ds, ix) => {
253
+ if (ds.displayGroup !== dataset.displayGroup) {
254
+ return;
255
+ }
256
+ chartInstance.getDatasetMeta(ix).hidden = meta.hidden;
257
+ });
258
+ }
259
+
260
+ if (interactions.onLegendClick) {
261
+ interactions.onLegendClick(legendItem?.text, legendItem.hidden);
262
+ }
263
+
264
+ chartInstance.update();
265
+ };
266
+
267
+ const onClick = (evt, elements, chartInstance) => {
268
+ chartInstance.resetZoom();
269
+ dispatch({ type: UNSET_AXES_VALUES });
270
+ };
271
+
272
+ const onHover = (evt, hoveredItems, chartInstance) => {
273
+ if (pointHover && !hoveredItems?.length) {
274
+ pointHover = false;
275
+ if (interactions.onPointUnhover) {
276
+ interactions.onPointUnhover(evt);
277
+ }
278
+ }
279
+ if (!pointHover && hoveredItems?.length) {
280
+ pointHover = true;
281
+ if (interactions.onPointHover) {
282
+ const { index, datasetIndex } = hoveredItems[0];
283
+ const generatedDataset = generatedDatasets;
284
+ interactions.onPointHover(evt, datasetIndex, index, generatedDataset);
285
+ }
286
+ }
287
+ };
288
+
289
+ const handleDownload = () => {
290
+ const chart = chartRef.current;
291
+ // Add temporary canvas background
292
+ const { ctx } = chart;
293
+ ctx.save();
294
+ ctx.globalCompositeOperation = 'destination-over';
295
+ ctx.fillStyle = chartStyling.darkMode ? 'black' : 'white';
296
+ ctx.fillRect(0, 0, chart.width, chart.height);
297
+ ctx.restore();
298
+
299
+ const base64Image = chart.toBase64Image();
300
+ const fileName = getChartFileName(state.axes);
301
+
302
+ triggerBase64Download(base64Image, fileName);
303
+ };
304
+
305
+ const handleKeyDown = (evt) => {
306
+ if (evt.key === Key.Shift && !shiftPressed) {
307
+ setShiftPressed(true);
308
+ }
309
+ };
310
+
311
+ const handleKeyUp = (evt) => {
312
+ if (evt.key === Key.Shift) {
313
+ setShiftPressed(false);
314
+ }
315
+ };
316
+
317
+ const getControlsAxes = () => {
318
+ return state.axes.map((axis, i) => {
319
+ const axisType = i ? AxisType.Y : AxisType.X; // only first element is 'x' - rest is 'y'
320
+ const min = additionalAxesOptions?.range?.[axisType]?.min;
321
+ const max = additionalAxesOptions?.range?.[axisType]?.max;
322
+
323
+ return {
324
+ ...axis,
325
+ min: axis.min ?? getAxisValue(min),
326
+ max: axis.max ?? getAxisValue(max),
327
+ };
328
+ });
329
+ };
330
+ const controlsAxes = getControlsAxes();
331
+
332
+ return (
333
+ <div
334
+ className={getClassName(chartStyling, styles)}
335
+ style={{
336
+ width: chartStyling.width || AUTO,
337
+ height: chartStyling.height || AUTO,
338
+ }}
339
+ tabIndex={0} //eslint-disable-line jsx-a11y/no-noninteractive-tabindex
340
+ onKeyDown={handleKeyDown}
341
+ onKeyUp={handleKeyUp}
342
+ >
343
+ <div className={styles.zoomForm}>
344
+ <Controls
345
+ axes={controlsAxes}
346
+ legendEnabled={state.legendEnabled}
347
+ lineEnabled={state.lineEnabled}
348
+ panEnabled={state.panEnabled}
349
+ pointsEnabled={state.pointsEnabled}
350
+ zoomEnabled={state.zoomEnabled}
351
+ onSetAxisValue={(evt) =>
352
+ dispatch({ type: SET_AXIS_VALUE, payload: evt })
353
+ }
354
+ onResetAxes={() => {
355
+ dispatch({ type: UNSET_AXES_VALUES });
356
+ }}
357
+ onToggleLegend={() => dispatch({ type: TOGGLE_LEGEND })}
358
+ onToggleLine={() => dispatch({ type: TOGGLE_LINE })}
359
+ onTogglePan={() => dispatch({ type: TOGGLE_PAN })}
360
+ onTogglePoints={() => dispatch({ type: TOGGLE_POINTS })}
361
+ onToggleZoom={() => dispatch({ type: TOGGLE_ZOOM })}
362
+ onDownload={handleDownload}
363
+ closeOnOutsideClick={closeOnOutsideClick}
364
+ />
365
+ </div>
366
+ <Line
367
+ ref={chartRef}
368
+ data={{
369
+ datasets: generatedDatasets,
370
+ }}
371
+ options={{
372
+ onClick,
373
+ onHover,
374
+ maintainAspectRatio: chartStyling.maintainAspectRatio,
375
+ animation: {
376
+ duration: chartStyling.performanceMode
377
+ ? ANIMATION_DURATION.NO
378
+ : ANIMATION_DURATION.FAST,
379
+ },
380
+ hover: {
381
+ mode: ChartHoverMode.Nearest,
382
+ intersect: true,
383
+ animationDuration: chartStyling.performanceMode
384
+ ? ANIMATION_DURATION.NO
385
+ : ANIMATION_DURATION.SLOW,
386
+ },
387
+ elements: {
388
+ line: {
389
+ pointStyle: PointStyle.Circle,
390
+ showLine: state.lineEnabled,
391
+ },
392
+ },
393
+ scales: getLineChartScales(options, state),
394
+ plugins: {
395
+ title: getTitle(options),
396
+ datalabels: getLineChartDataLabels(options),
397
+ annotation: getAnnotation(options, state),
398
+ zoom: {
399
+ pan: {
400
+ enabled: state.panEnabled,
401
+ mode: shiftPressed ? PanZoomMode.Y : PanZoomMode.X,
402
+ },
403
+ zoom: {
404
+ mode: PanZoomMode.X,
405
+ drag: {
406
+ enabled: state.zoomEnabled,
407
+ threshold: 3,
408
+ },
409
+ },
410
+ },
411
+ tooltip: getLineChartToolTips(options),
412
+ legend: getLegend(options, legendClick, state),
413
+ },
414
+ }}
415
+ plugins={getPlugins(graph)}
416
+ />
417
+ </div>
418
+ );
419
+ };
420
+
421
+ LineChart.propTypes = LineChartPropTypes;
422
+
423
+ export { LineChart };