@oliasoft-open-source/charts-library 0.0.2-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 (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/index.js +13 -0
  4. package/package.json +100 -0
  5. package/release-notes.md +178 -0
  6. package/src/assets/icons/line-and-point.svg +1 -0
  7. package/src/assets/icons/line-only.svg +1 -0
  8. package/src/assets/icons/list-hide.svg +1 -0
  9. package/src/assets/icons/point-only.svg +1 -0
  10. package/src/components/bar-chart/bar-chart-prop-types.js +188 -0
  11. package/src/components/bar-chart/bar-chart.interface.ts +84 -0
  12. package/src/components/bar-chart/bar-chart.jsx +243 -0
  13. package/src/components/bar-chart/bar-chart.module.less +61 -0
  14. package/src/components/bar-chart/get-bar-chart-data-labels.js +42 -0
  15. package/src/components/bar-chart/get-bar-chart-scales.js +123 -0
  16. package/src/components/bar-chart/get-bar-chart-tooltips.js +100 -0
  17. package/src/components/controls/axes-options/axes-options-form-state.js +95 -0
  18. package/src/components/controls/axes-options/axes-options.jsx +166 -0
  19. package/src/components/controls/controls.jsx +104 -0
  20. package/src/components/controls/controls.module.less +12 -0
  21. package/src/components/controls/drag-options.jsx +77 -0
  22. package/src/components/controls/legend-options.jsx +25 -0
  23. package/src/components/controls/line-options.jsx +54 -0
  24. package/src/components/line-chart/axis-scales/axis-scales.js +165 -0
  25. package/src/components/line-chart/datalabels-alignment/get-alignment-condition.js +13 -0
  26. package/src/components/line-chart/datalabels-alignment/get-alignment-data.js +20 -0
  27. package/src/components/line-chart/datalabels-alignment/get-datalabels-position.js +25 -0
  28. package/src/components/line-chart/get-axes-ranges-from-chart.js +10 -0
  29. package/src/components/line-chart/get-line-chart-data-labels.js +21 -0
  30. package/src/components/line-chart/get-line-chart-scales.js +120 -0
  31. package/src/components/line-chart/get-line-chart-tooltips.js +91 -0
  32. package/src/components/line-chart/line-chart-consts.js +7 -0
  33. package/src/components/line-chart/line-chart-prop-types.js +212 -0
  34. package/src/components/line-chart/line-chart-utils.js +192 -0
  35. package/src/components/line-chart/line-chart.interface.ts +107 -0
  36. package/src/components/line-chart/line-chart.jsx +531 -0
  37. package/src/components/line-chart/line-chart.minor-gridlines-plugin.js +88 -0
  38. package/src/components/line-chart/line-chart.module.less +77 -0
  39. package/src/components/line-chart/state/action-types.js +11 -0
  40. package/src/components/line-chart/state/initial-state.js +69 -0
  41. package/src/components/line-chart/state/line-chart-reducer.js +101 -0
  42. package/src/components/pie-chart/pie-chart-prop-types.js +111 -0
  43. package/src/components/pie-chart/pie-chart-utils.js +32 -0
  44. package/src/components/pie-chart/pie-chart.interface.ts +61 -0
  45. package/src/components/pie-chart/pie-chart.jsx +450 -0
  46. package/src/components/pie-chart/pie-chart.module.less +61 -0
  47. package/src/components/scatter-chart/scatter-chart.intefrace.ts +33 -0
  48. package/src/components/scatter-chart/scatter-chart.jsx +21 -0
  49. package/src/components/scatter-chart/scatter-chart.module.less +4 -0
  50. package/src/helpers/chart-border-plugin.js +19 -0
  51. package/src/helpers/chart-consts.js +62 -0
  52. package/src/helpers/chart-interface.ts +76 -0
  53. package/src/helpers/chart-utils.js +183 -0
  54. package/src/helpers/container.jsx +60 -0
  55. package/src/helpers/disabled-context.js +8 -0
  56. package/src/helpers/enums.js +87 -0
  57. package/src/helpers/get-chart-annotation.js +143 -0
  58. package/src/helpers/get-custom-legend-plugin-example.js +80 -0
  59. package/src/helpers/numbers/numbers.js +44 -0
  60. package/src/helpers/range/estimate-data-series-have-close-values.js +54 -0
  61. package/src/helpers/range/range.js +95 -0
  62. package/src/helpers/styles.js +68 -0
  63. package/src/helpers/text.js +6 -0
  64. package/src/style/external.less +4 -0
  65. package/src/style/fonts/lato/Lato-Bold.woff2 +0 -0
  66. package/src/style/fonts/lato/Lato-BoldItalic.woff2 +0 -0
  67. package/src/style/fonts/lato/Lato-Italic.woff2 +0 -0
  68. package/src/style/fonts/lato/Lato-Regular.woff2 +0 -0
  69. package/src/style/fonts.less +27 -0
  70. package/src/style/global.less +43 -0
  71. package/src/style/reset/reset.less +28 -0
  72. package/src/style/shared.less +24 -0
  73. package/src/style/variables.less +91 -0
@@ -0,0 +1,107 @@
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
+ autoAxisPadding: boolean;
47
+ }
48
+
49
+ export interface ILineChartAxis<PositionType> {
50
+ label: string;
51
+ position: PositionType;
52
+ color: string;
53
+ }
54
+
55
+ export interface ILineChartAxes {
56
+ x: ILineChartAxis<'top' | 'bottom'>[];
57
+ y: ILineChartAxis<'left' | 'right'>[];
58
+ }
59
+
60
+ export interface ILineChartOptions {
61
+ title: string | string[];
62
+ axes: ILineChartAxes;
63
+ additionalAxesOptions: ILineChartAdditionalAxesOptions;
64
+ chartStyling: IChartStyling;
65
+ tooltip: ILineChartTooltip;
66
+ graph: ILineChartGraph;
67
+ annotations: IChartAnnotations;
68
+ legend: IChartLegend;
69
+ chartOptions: IChartOptions;
70
+ interactions: IChartInteractions;
71
+ }
72
+
73
+ export interface IDataPoint {
74
+ x: number,
75
+ y: number,
76
+ label?: string[]
77
+ }
78
+
79
+ export interface ILineChartDataset {
80
+ label?: string,
81
+ lineTension?: number,
82
+ borderColor?: string,
83
+ pointBackgroundColor?: string,
84
+ backgroundColor?: string,
85
+ pointRadius?: number,
86
+ pointHoverRadius?: number,
87
+ pointHitRadius?: number,
88
+ borderWidth?: number,
89
+ fill?: boolean,
90
+ yAxisID?: string,
91
+ formation?: boolean,
92
+ data: IDataPoint[],
93
+ }
94
+
95
+ export interface ILineChartData {
96
+ //TODO: revisit data interface definition after project is more stable
97
+ testId: string | null;
98
+ data: {
99
+ labels?: string[],
100
+ datasets: ILineChartDataset[]
101
+ } | any;
102
+ options: ILineChartOptions;
103
+ }
104
+
105
+ export interface ILineChartProps {
106
+ chart: ILineChartData;
107
+ }
@@ -0,0 +1,531 @@
1
+ import React, { useEffect, useReducer, useRef, useState } from 'react';
2
+ import {
3
+ CategoryScale,
4
+ Chart as ChartJS,
5
+ Filler,
6
+ Legend,
7
+ LinearScale,
8
+ LineElement,
9
+ LogarithmicScale,
10
+ PointElement,
11
+ Title,
12
+ Tooltip,
13
+ } from 'chart.js';
14
+ import { Line } from 'react-chartjs-2';
15
+ import zoomPlugin from 'chartjs-plugin-zoom';
16
+ import dataLabelsPlugin from 'chartjs-plugin-datalabels';
17
+ import annotationPlugin from 'chartjs-plugin-annotation';
18
+ import { triggerBase64Download } from 'react-base64-downloader';
19
+ import styles from './line-chart.module.less';
20
+ import { reducer } from './state/line-chart-reducer';
21
+ import initialState from './state/initial-state';
22
+ import {
23
+ SET_POINTS_ZOOM_DEFAULTS,
24
+ TOGGLE_ANNOTATION,
25
+ TOGGLE_LEGEND,
26
+ TOGGLE_LINE,
27
+ TOGGLE_PAN,
28
+ TOGGLE_POINTS,
29
+ TOGGLE_TABLE,
30
+ TOGGLE_ZOOM,
31
+ SAVE_INITIAL_AXES_RANGES,
32
+ UPDATE_AXES_RANGES,
33
+ RESET_AXES_RANGES,
34
+ } from './state/action-types';
35
+ import Controls from '../controls/controls';
36
+ import { getDefaultProps, LineChartPropTypes } from './line-chart-prop-types';
37
+ import getLineChartToolTips from './get-line-chart-tooltips';
38
+ import getLineChartDataLabels from './get-line-chart-data-labels';
39
+ import {
40
+ BORDER_JOIN_STYLE,
41
+ DEFAULT_BACKGROUND_COLOR,
42
+ DEFAULT_BORDER_WIDTH,
43
+ DEFAULT_HOVER_RADIUS,
44
+ DEFAULT_LINE_POINT_RADIUS,
45
+ DEFAULT_POINT_RADIUS,
46
+ ZOOM_BOX_BACKGROUND_COLOR,
47
+ } from './line-chart-consts';
48
+
49
+ import getAnnotation from '../../helpers/get-chart-annotation';
50
+ import {
51
+ generateRandomColor,
52
+ getChartFileName,
53
+ getClassName,
54
+ getLegend,
55
+ getPlugins,
56
+ setDefaultTheme,
57
+ } from '../../helpers/chart-utils';
58
+ import {
59
+ ANIMATION_DURATION,
60
+ AUTO,
61
+ COLORS,
62
+ CUSTOM_LEGEND_PLUGIN_NAME,
63
+ BORDER_COLOR,
64
+ } from '../../helpers/chart-consts';
65
+ import {
66
+ AxisType,
67
+ ChartHoverMode,
68
+ Key,
69
+ PanZoomMode,
70
+ PointStyle,
71
+ } from '../../helpers/enums';
72
+ import { getAxesRangesFromChart } from './get-axes-ranges-from-chart';
73
+ import { generateAxisId } from './line-chart-utils';
74
+ import { autoScale } from './axis-scales/axis-scales';
75
+
76
+ ChartJS.register(
77
+ LinearScale,
78
+ PointElement,
79
+ LineElement,
80
+ CategoryScale,
81
+ LogarithmicScale,
82
+ Legend,
83
+ Tooltip,
84
+ Title,
85
+ Filler,
86
+ zoomPlugin,
87
+ dataLabelsPlugin,
88
+ annotationPlugin,
89
+ );
90
+
91
+ /**
92
+ * this is a line chart component
93
+ * @param {import('./line-chart.interface').ILineChartProps} props
94
+ */
95
+ const LineChart = (props) => {
96
+ setDefaultTheme();
97
+ const chartRef = useRef(null);
98
+ const [hoveredPoint, setHoveredPoint] = useState(null);
99
+ const chart = getDefaultProps(props);
100
+ const { options, testId } = chart;
101
+ const { headerComponent, subheaderComponent, table } = props;
102
+ const {
103
+ additionalAxesOptions,
104
+ annotations,
105
+ axes,
106
+ chartOptions,
107
+ chartStyling,
108
+ graph,
109
+ interactions,
110
+ legend,
111
+ } = options;
112
+ const { showLine, showPoints, enableZoom, enablePan } = chartOptions;
113
+
114
+ /**
115
+ * @type {[object, import('react').Dispatch<{type: String, payload: any}>]} useReducer
116
+ */
117
+ const [state, dispatch] = useReducer(
118
+ reducer,
119
+ {
120
+ axes,
121
+ enableZoom: chartOptions.enableZoom,
122
+ enablePan: chartOptions.enablePan,
123
+ showPoints: chartOptions.showPoints,
124
+ showLine: chartOptions.showLine,
125
+ legendDisplay: legend.display,
126
+ annotationsData: annotations.annotationsData,
127
+ customAxesRange: additionalAxesOptions?.range,
128
+ },
129
+ initialState,
130
+ );
131
+
132
+ useEffect(() => {
133
+ const { range } = props.chart.options.additionalAxesOptions;
134
+ if (range?.x && range?.y) {
135
+ const axes = Object.entries(range).map(([key, { min, max }]) => {
136
+ return {
137
+ id: key,
138
+ min: min ?? 0,
139
+ max: max ?? 0,
140
+ };
141
+ });
142
+
143
+ dispatch({
144
+ type: UPDATE_AXES_RANGES,
145
+ payload: { axes },
146
+ });
147
+ }
148
+ }, [props.chart.options.additionalAxesOptions.range]);
149
+
150
+ useEffect(() => {
151
+ if (chartOptions.enablePan !== true) {
152
+ dispatch({ type: TOGGLE_PAN });
153
+ }
154
+ if (chartRef) {
155
+ //save the initial axis ranges in state (we need this for resetting ranges)
156
+ dispatch({
157
+ type: SAVE_INITIAL_AXES_RANGES,
158
+ payload: { initialAxesRanges: getAxesRangesFromChart(chartRef) },
159
+ });
160
+ }
161
+ }, []);
162
+
163
+ useEffect(() => {
164
+ dispatch({
165
+ type: SET_POINTS_ZOOM_DEFAULTS,
166
+ payload: { showLine, showPoints, enableZoom, enablePan },
167
+ });
168
+ }, [showLine, showPoints, enableZoom, enablePan]);
169
+
170
+ /**
171
+ * Toggle custom legends visibility.
172
+ * Needed because they are rendered in a html element separate from the chart.
173
+ */
174
+ useEffect(() => {
175
+ if (options.legend.customLegend.customLegendPlugin) {
176
+ const parent = document.getElementById(
177
+ options.legend.customLegend.customLegendContainerID,
178
+ );
179
+ if (state.legendEnabled) {
180
+ parent.style.visibility = 'visible';
181
+ } else {
182
+ parent.style.visibility = 'hidden';
183
+ }
184
+ }
185
+ }, [state.legendEnabled]);
186
+
187
+ const generateLineChartDatasets = (datasets) => {
188
+ const copyDataset = [...datasets];
189
+
190
+ // Add annotations to dataset to have them appear in legend.
191
+ if (
192
+ annotations.controlAnnotation &&
193
+ annotations.showAnnotations &&
194
+ annotations.annotationsData?.length
195
+ ) {
196
+ annotations.annotationsData.forEach((annotation, index) => {
197
+ const color = annotation.color || COLORS[index];
198
+ copyDataset.push({
199
+ isAnnotation: true,
200
+ label: annotation.label,
201
+ annotationIndex: index,
202
+ backgroundColor: color,
203
+ pointBackgroundColor: color,
204
+ borderColor: color,
205
+ data: [{}],
206
+ });
207
+ });
208
+ }
209
+
210
+ const generatedDatasets = copyDataset.map((line, i) => {
211
+ if (line.formation) {
212
+ const axesMin = state.axes[0]?.min;
213
+ const axesMax = state.axes[0]?.max;
214
+ // line with formation flag has 3 points: start point, mid-point with label, and end point.
215
+ const [startPoint, midPointWithLabel, endPoint] = line.data;
216
+
217
+ if (axesMin && startPoint?.x) {
218
+ line.data[0].x = axesMin < startPoint?.x ? axesMin : startPoint?.x;
219
+ }
220
+
221
+ if (axesMax && endPoint?.x) {
222
+ line.data[2].x = axesMax > endPoint?.x ? axesMax : endPoint?.x;
223
+ }
224
+ }
225
+ /*
226
+ Remove invalid falsy data points OW-9855
227
+ Points should be an object of {x, y} pairs
228
+ This is an extra guard to prevent crashes if parent apps pass bad inputs
229
+ */
230
+ line.data = line?.data?.filter(Boolean) || [];
231
+
232
+ line.showLine = state.lineEnabled;
233
+ const linePointRadius = line.pointRadius
234
+ ? parseFloat(line.pointRadius)
235
+ : DEFAULT_POINT_RADIUS;
236
+ const pointHoverRadius = line.pointHoverRadius
237
+ ? parseFloat(line.pointHoverRadius)
238
+ : DEFAULT_HOVER_RADIUS;
239
+ const indexedColor = COLORS[i];
240
+
241
+ return {
242
+ ...line,
243
+ lineTension: graph.lineTension,
244
+ spanGaps: graph.spanGaps,
245
+ borderWidth: parseFloat(line.borderWidth) || DEFAULT_BORDER_WIDTH,
246
+ borderDash: line.borderDash || [],
247
+ borderJoinStyle: BORDER_JOIN_STYLE,
248
+ borderColor:
249
+ line.borderColor || indexedColor || generateRandomColor(COLORS),
250
+ backgroundColor: line.backgroundColor || DEFAULT_BACKGROUND_COLOR,
251
+ pointBackgroundColor:
252
+ line.pointBackgroundColor ||
253
+ indexedColor ||
254
+ generateRandomColor(COLORS),
255
+ pointRadius:
256
+ state.pointsEnabled === true
257
+ ? linePointRadius
258
+ : DEFAULT_LINE_POINT_RADIUS,
259
+ pointHoverRadius,
260
+ pointHitRadius: line.pointHitRadius || pointHoverRadius,
261
+ };
262
+ });
263
+ return generatedDatasets;
264
+ };
265
+
266
+ const generatedDatasets = generateLineChartDatasets(chart.data.datasets);
267
+
268
+ const legendClick = (e, legendItem) => {
269
+ const index = legendItem.datasetIndex;
270
+ const chartInstance = chartRef.current;
271
+ const { datasets } = chartInstance.data;
272
+ const dataset = datasets[index];
273
+ const meta = chartInstance.getDatasetMeta(index);
274
+ meta.hidden = meta.hidden === null ? !dataset.hidden : null;
275
+
276
+ if (annotations.controlAnnotation && dataset.isAnnotation) {
277
+ const { annotationIndex } = dataset;
278
+ dispatch({ type: TOGGLE_ANNOTATION, payload: { annotationIndex } });
279
+ }
280
+
281
+ // Show/hide entire display group
282
+ if (dataset.displayGroup) {
283
+ datasets.forEach((ds, ix) => {
284
+ if (ds.displayGroup !== dataset.displayGroup) {
285
+ return;
286
+ }
287
+ chartInstance.getDatasetMeta(ix).hidden = meta.hidden;
288
+ });
289
+ }
290
+
291
+ if (interactions.onLegendClick) {
292
+ interactions.onLegendClick(legendItem?.text, legendItem.hidden);
293
+ }
294
+
295
+ chartInstance.update();
296
+ };
297
+
298
+ const onClick = (evt, elements, chartInstance) => {
299
+ chartInstance.resetZoom();
300
+ dispatch({ type: RESET_AXES_RANGES });
301
+ };
302
+
303
+ const onHover = (evt, hoveredItems) => {
304
+ if (!hoveredItems?.length && interactions.onPointUnhover && hoveredPoint) {
305
+ setHoveredPoint(null);
306
+ interactions.onPointUnhover(evt);
307
+ }
308
+
309
+ if (hoveredItems?.length && interactions.onPointHover) {
310
+ const { index, datasetIndex } = hoveredItems[0];
311
+ const dataset = generatedDatasets[datasetIndex];
312
+ const point = dataset?.data[index];
313
+
314
+ if (point && hoveredPoint !== point) {
315
+ setHoveredPoint(point);
316
+ interactions.onPointHover(evt, datasetIndex, index, generatedDatasets);
317
+ }
318
+ }
319
+ };
320
+
321
+ const handleDownload = () => {
322
+ const chart = chartRef.current;
323
+ // Add temporary canvas background
324
+ const { ctx } = chart;
325
+ ctx.save();
326
+ ctx.globalCompositeOperation = 'destination-over';
327
+ ctx.fillStyle = 'white';
328
+ ctx.fillRect(0, 0, chart.width, chart.height);
329
+ ctx.restore();
330
+
331
+ const base64Image = chart.toBase64Image();
332
+ const fileName = getChartFileName(state.axes);
333
+
334
+ triggerBase64Download(base64Image, fileName);
335
+ };
336
+
337
+ const handleKeyDown = (evt) => {
338
+ if (evt.key === Key.Shift) {
339
+ const chart = chartRef.current;
340
+ chart.config.options.plugins.zoom.zoom.mode = PanZoomMode.Y;
341
+ chart.config.options.plugins.zoom.pan.mode = PanZoomMode.Y;
342
+ chart.update();
343
+ }
344
+ };
345
+
346
+ const handleKeyUp = (evt) => {
347
+ if (evt.key === Key.Shift) {
348
+ const chart = chartRef.current;
349
+ chart.config.options.plugins.zoom.zoom.mode = PanZoomMode.Z;
350
+ chart.config.options.plugins.zoom.pan.mode = PanZoomMode.Z;
351
+ chart.update();
352
+ }
353
+ };
354
+
355
+ const getControlsAxes = () => {
356
+ return state.axes.map((axis, i) => {
357
+ const axisType = i ? AxisType.Y : AxisType.X; // only first element is 'x' - rest is 'y'
358
+ const min = axis.min ?? additionalAxesOptions?.range?.[axisType]?.min;
359
+ const max = axis.max ?? additionalAxesOptions?.range?.[axisType]?.max;
360
+ return {
361
+ ...axis,
362
+ //only add min and max properties if they are defined:
363
+ ...(min ? { min } : {}),
364
+ ...(max ? { max } : {}),
365
+ };
366
+ });
367
+ };
368
+ const controlsAxes = getControlsAxes(axes);
369
+
370
+ const getControlsAxesLabels = (propsAxes) => {
371
+ if (!Object.keys(propsAxes)?.length) {
372
+ return [];
373
+ }
374
+
375
+ const getAxesLabels = (axes, axisType) => {
376
+ if (!axes[axisType] || !axes[axisType]?.length) {
377
+ return [];
378
+ } else {
379
+ return axes[axisType].map((axisObj, index) => {
380
+ return {
381
+ id: generateAxisId(axisType, index, axes[axisType].length > 1),
382
+ label: axisObj?.label || '',
383
+ };
384
+ });
385
+ }
386
+ };
387
+
388
+ const axesLabels = [
389
+ ...getAxesLabels(propsAxes, AxisType.X),
390
+ ...getAxesLabels(propsAxes, AxisType.Y),
391
+ ];
392
+ return axesLabels;
393
+ };
394
+
395
+ const controlsAxesLabels = getControlsAxesLabels(props.chart.options.axes);
396
+
397
+ const updateAxesRangesFromChart = (chart) => {
398
+ const { scales = {} } = chart || {};
399
+ const axes = Object.entries(scales).map(([key, { min, max }]) => {
400
+ return {
401
+ id: key,
402
+ min: min ?? 0,
403
+ max: max ?? 0,
404
+ };
405
+ });
406
+ dispatch({
407
+ type: UPDATE_AXES_RANGES,
408
+ payload: { axes },
409
+ });
410
+ };
411
+
412
+ return (
413
+ <div
414
+ className={getClassName(chartStyling, styles)}
415
+ style={{
416
+ width: chartStyling.width || AUTO,
417
+ height: chartStyling.height || AUTO,
418
+ }}
419
+ tabIndex={0} //eslint-disable-line jsx-a11y/no-noninteractive-tabindex
420
+ onKeyDown={handleKeyDown}
421
+ onKeyUp={handleKeyUp}
422
+ data-testid={testId}
423
+ >
424
+ <Controls
425
+ axes={controlsAxes}
426
+ controlsAxesLabels={controlsAxesLabels}
427
+ chart={chart}
428
+ headerComponent={headerComponent}
429
+ legendEnabled={state.legendEnabled}
430
+ lineEnabled={state.lineEnabled}
431
+ onDownload={handleDownload}
432
+ onResetAxes={() => {
433
+ dispatch({ type: RESET_AXES_RANGES });
434
+ }}
435
+ onUpdateAxes={({ axes }) => {
436
+ dispatch({ type: UPDATE_AXES_RANGES, payload: { axes } });
437
+ }}
438
+ onToggleLegend={() => dispatch({ type: TOGGLE_LEGEND })}
439
+ onToggleLine={() => dispatch({ type: TOGGLE_LINE })}
440
+ onTogglePan={() => dispatch({ type: TOGGLE_PAN })}
441
+ onTogglePoints={() => dispatch({ type: TOGGLE_POINTS })}
442
+ onToggleTable={() => dispatch({ type: TOGGLE_TABLE })}
443
+ onToggleZoom={() => dispatch({ type: TOGGLE_ZOOM })}
444
+ panEnabled={state.panEnabled}
445
+ pointsEnabled={state.pointsEnabled}
446
+ initialAxesRanges={state.initialAxesRanges}
447
+ showTable={state.showTable}
448
+ subheaderComponent={subheaderComponent}
449
+ table={table}
450
+ zoomEnabled={state.zoomEnabled}
451
+ />
452
+ {table && state.showTable ? (
453
+ <div className={styles.table}>{table}</div>
454
+ ) : (
455
+ <div className={styles.canvas}>
456
+ <Line
457
+ ref={chartRef}
458
+ data={{
459
+ datasets: generatedDatasets,
460
+ }}
461
+ options={{
462
+ onClick,
463
+ onHover,
464
+ maintainAspectRatio: chartStyling.maintainAspectRatio,
465
+ aspectRatio: chartStyling.squareAspectRatio ? 1 : null, // 1 equals square aspect ratio
466
+ animation: chartStyling.performanceMode
467
+ ? false
468
+ : {
469
+ duration: ANIMATION_DURATION.FAST,
470
+ onComplete: interactions.onAnimationComplete,
471
+ },
472
+ hover: {
473
+ mode: ChartHoverMode.Nearest,
474
+ intersect: true,
475
+ },
476
+ elements: {
477
+ line: {
478
+ pointStyle: PointStyle.Circle,
479
+ showLine: state.lineEnabled,
480
+ },
481
+ },
482
+ scales: autoScale(options, state, generatedDatasets),
483
+ plugins: {
484
+ // title: getTitle(options),
485
+ datalabels: getLineChartDataLabels(options),
486
+ annotation: getAnnotation(options, state),
487
+ zoom: {
488
+ pan: {
489
+ enabled: state.panEnabled,
490
+ mode: PanZoomMode.XY,
491
+ onPanComplete({ chart }) {
492
+ updateAxesRangesFromChart(chart);
493
+ },
494
+ },
495
+ zoom: {
496
+ mode: PanZoomMode.XY,
497
+ drag: {
498
+ enabled: state.zoomEnabled,
499
+ threshold: 3,
500
+ backgroundColor: ZOOM_BOX_BACKGROUND_COLOR,
501
+ borderColor: BORDER_COLOR,
502
+ borderWidth: 1,
503
+ },
504
+ onZoomComplete({ chart }) {
505
+ updateAxesRangesFromChart(chart);
506
+ },
507
+ },
508
+ },
509
+ tooltip: getLineChartToolTips(options),
510
+ legend: getLegend(options, legendClick, state),
511
+ [CUSTOM_LEGEND_PLUGIN_NAME]: options.legend.customLegend
512
+ .customLegendPlugin && {
513
+ containerID:
514
+ options.legend.customLegend.customLegendContainerID,
515
+ },
516
+ chartAreaBorder: {
517
+ borderColor: BORDER_COLOR,
518
+ },
519
+ },
520
+ }}
521
+ plugins={getPlugins(graph, legend, state)}
522
+ />
523
+ </div>
524
+ )}
525
+ </div>
526
+ );
527
+ };
528
+
529
+ LineChart.propTypes = LineChartPropTypes;
530
+
531
+ export { LineChart };