@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.
- package/.eslintignore +2 -0
- package/.eslintrc.js +129 -0
- package/.gitlab-ci.yml +77 -0
- package/.husky/pre-commit +4 -0
- package/.prettierignore +3 -0
- package/.prettierrc +4 -0
- package/.storybook/main.js +40 -0
- package/LICENSE +21 -0
- package/README.md +5 -0
- package/babel.config.js +29 -0
- package/index.js +9 -0
- package/jest.config.js +9 -0
- package/package.json +96 -0
- package/src/components/bar-chart/bar-chart-prop-types.js +181 -0
- package/src/components/bar-chart/bar-chart.interface.ts +83 -0
- package/src/components/bar-chart/bar-chart.jsx +247 -0
- package/src/components/bar-chart/bar-chart.module.less +56 -0
- package/src/components/bar-chart/basic.stories.jsx +752 -0
- package/src/components/bar-chart/charts.stories.jsx +119 -0
- package/src/components/bar-chart/get-bar-chart-data-labels.js +45 -0
- package/src/components/bar-chart/get-bar-chart-scales.js +147 -0
- package/src/components/bar-chart/get-bar-chart-tooltips.js +100 -0
- package/src/components/line-chart/Controls/Controls.jsx +59 -0
- package/src/components/line-chart/Controls/Controls.module.less +21 -0
- package/src/components/line-chart/Controls/Layer.jsx +169 -0
- package/src/components/line-chart/basic.stories.jsx +735 -0
- package/src/components/line-chart/charts.stories.jsx +264 -0
- package/src/components/line-chart/get-line-chart-data-labels.js +24 -0
- package/src/components/line-chart/get-line-chart-scales.js +131 -0
- package/src/components/line-chart/get-line-chart-tooltips.js +91 -0
- package/src/components/line-chart/line-chart-consts.js +6 -0
- package/src/components/line-chart/line-chart-prop-types.js +187 -0
- package/src/components/line-chart/line-chart-utils.js +163 -0
- package/src/components/line-chart/line-chart.interface.ts +103 -0
- package/src/components/line-chart/line-chart.jsx +423 -0
- package/src/components/line-chart/line-chart.minor-gridlines-plugin.js +78 -0
- package/src/components/line-chart/line-chart.minor-gridlines-plugin.test.js +34 -0
- package/src/components/line-chart/line-chart.module.less +56 -0
- package/src/components/line-chart/state/action-types.js +9 -0
- package/src/components/line-chart/state/initial-state.js +51 -0
- package/src/components/line-chart/state/line-chart-reducer.js +115 -0
- package/src/components/line-chart/stories/shapes/cubes.stories.jsx +326 -0
- package/src/components/line-chart/stories/shapes/pyramid.stories.jsx +189 -0
- package/src/components/line-chart/stories/shapes/round.stories.jsx +339 -0
- package/src/components/line-chart/stories/shapes/triangle.stories.jsx +166 -0
- package/src/components/pie-chart/basic.stories.jsx +390 -0
- package/src/components/pie-chart/charts.stories.jsx +66 -0
- package/src/components/pie-chart/pie-chart-prop-types.js +111 -0
- package/src/components/pie-chart/pie-chart-utils.js +55 -0
- package/src/components/pie-chart/pie-chart.interface.ts +61 -0
- package/src/components/pie-chart/pie-chart.jsx +477 -0
- package/src/components/pie-chart/pie-chart.module.less +56 -0
- package/src/components/scatter-chart/scatter-chart.intefrace.ts +32 -0
- package/src/components/scatter-chart/scatter-chart.jsx +13 -0
- package/src/components/scatter-chart/scatter.stories.jsx +196 -0
- package/src/helpers/chart-consts.js +82 -0
- package/src/helpers/chart-interface.ts +54 -0
- package/src/helpers/chart-utils.js +178 -0
- package/src/helpers/container.jsx +60 -0
- package/src/helpers/disabled-context.js +8 -0
- package/src/helpers/enums.js +84 -0
- package/src/helpers/get-chart-annotation.js +91 -0
- package/src/helpers/styles.js +68 -0
- package/src/helpers/text.js +6 -0
- package/src/style/external.less +4 -0
- package/src/style/fonts/lato/Lato-Bold.woff2 +0 -0
- package/src/style/fonts/lato/Lato-BoldItalic.woff2 +0 -0
- package/src/style/fonts/lato/Lato-Italic.woff2 +0 -0
- package/src/style/fonts/lato/Lato-Regular.woff2 +0 -0
- package/src/style/fonts.less +27 -0
- package/src/style/global.less +43 -0
- package/src/style/reset/reset.less +28 -0
- package/src/style/shared.less +24 -0
- package/src/style/variables.less +91 -0
- package/webpack/webpack.common.js +39 -0
- package/webpack/webpack.common.rules.js +107 -0
- package/webpack/webpack.dev.js +22 -0
- package/webpack/webpack.prod.js +23 -0
- 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 };
|