@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.
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/index.js +13 -0
- package/package.json +100 -0
- package/release-notes.md +178 -0
- package/src/assets/icons/line-and-point.svg +1 -0
- package/src/assets/icons/line-only.svg +1 -0
- package/src/assets/icons/list-hide.svg +1 -0
- package/src/assets/icons/point-only.svg +1 -0
- package/src/components/bar-chart/bar-chart-prop-types.js +188 -0
- package/src/components/bar-chart/bar-chart.interface.ts +84 -0
- package/src/components/bar-chart/bar-chart.jsx +243 -0
- package/src/components/bar-chart/bar-chart.module.less +61 -0
- package/src/components/bar-chart/get-bar-chart-data-labels.js +42 -0
- package/src/components/bar-chart/get-bar-chart-scales.js +123 -0
- package/src/components/bar-chart/get-bar-chart-tooltips.js +100 -0
- package/src/components/controls/axes-options/axes-options-form-state.js +95 -0
- package/src/components/controls/axes-options/axes-options.jsx +166 -0
- package/src/components/controls/controls.jsx +104 -0
- package/src/components/controls/controls.module.less +12 -0
- package/src/components/controls/drag-options.jsx +77 -0
- package/src/components/controls/legend-options.jsx +25 -0
- package/src/components/controls/line-options.jsx +54 -0
- package/src/components/line-chart/axis-scales/axis-scales.js +165 -0
- package/src/components/line-chart/datalabels-alignment/get-alignment-condition.js +13 -0
- package/src/components/line-chart/datalabels-alignment/get-alignment-data.js +20 -0
- package/src/components/line-chart/datalabels-alignment/get-datalabels-position.js +25 -0
- package/src/components/line-chart/get-axes-ranges-from-chart.js +10 -0
- package/src/components/line-chart/get-line-chart-data-labels.js +21 -0
- package/src/components/line-chart/get-line-chart-scales.js +120 -0
- package/src/components/line-chart/get-line-chart-tooltips.js +91 -0
- package/src/components/line-chart/line-chart-consts.js +7 -0
- package/src/components/line-chart/line-chart-prop-types.js +212 -0
- package/src/components/line-chart/line-chart-utils.js +192 -0
- package/src/components/line-chart/line-chart.interface.ts +107 -0
- package/src/components/line-chart/line-chart.jsx +531 -0
- package/src/components/line-chart/line-chart.minor-gridlines-plugin.js +88 -0
- package/src/components/line-chart/line-chart.module.less +77 -0
- package/src/components/line-chart/state/action-types.js +11 -0
- package/src/components/line-chart/state/initial-state.js +69 -0
- package/src/components/line-chart/state/line-chart-reducer.js +101 -0
- package/src/components/pie-chart/pie-chart-prop-types.js +111 -0
- package/src/components/pie-chart/pie-chart-utils.js +32 -0
- package/src/components/pie-chart/pie-chart.interface.ts +61 -0
- package/src/components/pie-chart/pie-chart.jsx +450 -0
- package/src/components/pie-chart/pie-chart.module.less +61 -0
- package/src/components/scatter-chart/scatter-chart.intefrace.ts +33 -0
- package/src/components/scatter-chart/scatter-chart.jsx +21 -0
- package/src/components/scatter-chart/scatter-chart.module.less +4 -0
- package/src/helpers/chart-border-plugin.js +19 -0
- package/src/helpers/chart-consts.js +62 -0
- package/src/helpers/chart-interface.ts +76 -0
- package/src/helpers/chart-utils.js +183 -0
- package/src/helpers/container.jsx +60 -0
- package/src/helpers/disabled-context.js +8 -0
- package/src/helpers/enums.js +87 -0
- package/src/helpers/get-chart-annotation.js +143 -0
- package/src/helpers/get-custom-legend-plugin-example.js +80 -0
- package/src/helpers/numbers/numbers.js +44 -0
- package/src/helpers/range/estimate-data-series-have-close-values.js +54 -0
- package/src/helpers/range/range.js +95 -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
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Plugin } from "chart.js";
|
|
2
|
+
|
|
3
|
+
export interface IChartAnnotationsData {
|
|
4
|
+
adjustScaleRange: boolean;
|
|
5
|
+
annotationAxis: 'x' | 'y';
|
|
6
|
+
color: string;
|
|
7
|
+
endValue: number;
|
|
8
|
+
label: string;
|
|
9
|
+
type: string;
|
|
10
|
+
value: number;
|
|
11
|
+
xMin: number,
|
|
12
|
+
xMax: number,
|
|
13
|
+
yMin: number,
|
|
14
|
+
yMax: number,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export interface ILabelAnnotation {
|
|
18
|
+
content: string[];
|
|
19
|
+
fontSize: number;
|
|
20
|
+
xAdjust: number;
|
|
21
|
+
yAdjust: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface IChartAnnotations {
|
|
25
|
+
showAnnotations: boolean;
|
|
26
|
+
controlAnnotation: boolean;
|
|
27
|
+
annotationsData: IChartAnnotationsData[];
|
|
28
|
+
labelAnnotation: ILabelAnnotation
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface IChartStyling {
|
|
32
|
+
width: number | string;
|
|
33
|
+
height: number | string;
|
|
34
|
+
maintainAspectRatio: boolean;
|
|
35
|
+
squareAspectRatio?: boolean;
|
|
36
|
+
staticChartHeight: boolean;
|
|
37
|
+
performanceMode: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ICustomLegend {
|
|
41
|
+
customLegendPlugin: Plugin;
|
|
42
|
+
customLegendContainerID: string,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface IChartLegend {
|
|
46
|
+
display: boolean;
|
|
47
|
+
position: 'top' | 'bottom' | 'right';
|
|
48
|
+
align: 'start' | 'center' | 'end';
|
|
49
|
+
customLegend?: ICustomLegend;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface IChartInteractions {
|
|
53
|
+
onLegendClick: () => any;
|
|
54
|
+
onPointHover: () => any;
|
|
55
|
+
onPointUnhover: () => any;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface IInitialState {
|
|
59
|
+
zoomEnabled?: boolean;
|
|
60
|
+
panEnabled?: boolean;
|
|
61
|
+
pointsEnabled?: boolean;
|
|
62
|
+
lineEnabled?: boolean;
|
|
63
|
+
legendEnabled?: boolean;
|
|
64
|
+
axes?: {id: string, label: string | any, min?: {}, max?: {}}[];
|
|
65
|
+
showAnnotationLineIndex: [];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface IChartPlugins {
|
|
69
|
+
legend: {
|
|
70
|
+
position: 'top' | 'right' | 'bottom' | 'left';
|
|
71
|
+
}
|
|
72
|
+
title: {
|
|
73
|
+
display: boolean;
|
|
74
|
+
text: string;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { defaults } from 'chart.js';
|
|
2
|
+
import cx from 'classnames';
|
|
3
|
+
import { chartMinorGridlinesPlugin } from '../components/line-chart/line-chart.minor-gridlines-plugin';
|
|
4
|
+
import {
|
|
5
|
+
CUSTOM_LEGEND_PLUGIN_NAME,
|
|
6
|
+
BORDER_COLOR,
|
|
7
|
+
DEFAULT_CHART_NAME,
|
|
8
|
+
DEFAULT_COLOR,
|
|
9
|
+
DEFAULT_FONT_FAMILY,
|
|
10
|
+
DEFAULT_FONT_SIZE,
|
|
11
|
+
LEGEND_LABEL_BOX_SIZE,
|
|
12
|
+
WHITE_COLOR_AS_DECIMAL,
|
|
13
|
+
} from './chart-consts';
|
|
14
|
+
import { AxisType, ChartDirection, Position } from './enums';
|
|
15
|
+
import { chartAreaBorderPlugin } from './chart-border-plugin';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {import('../components/bar-chart/bar-chart.interface').IBarChartGraph |
|
|
19
|
+
* import('../components/line-chart/line-chart.interface').ILineChartGraph } graph - graph data from chart options
|
|
20
|
+
* @param {import('../helpers/chart-interface').IChartLegend} legend
|
|
21
|
+
* @param {import('./chart-interface').IInitialState} state - chart state object controlled by useReducer or similar
|
|
22
|
+
* @return {[]}
|
|
23
|
+
*/
|
|
24
|
+
export const getPlugins = (graph, legend, state = null) => {
|
|
25
|
+
let plugins = [];
|
|
26
|
+
if (graph.showMinorGridlines) {
|
|
27
|
+
plugins.push(chartMinorGridlinesPlugin);
|
|
28
|
+
}
|
|
29
|
+
const customLegend = legend?.customLegend;
|
|
30
|
+
if (
|
|
31
|
+
customLegend?.customLegendPlugin &&
|
|
32
|
+
customLegend?.customLegendContainerID
|
|
33
|
+
) {
|
|
34
|
+
plugins.push({
|
|
35
|
+
id: CUSTOM_LEGEND_PLUGIN_NAME,
|
|
36
|
+
...legend.customLegend.customLegendPlugin,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
plugins.push(chartAreaBorderPlugin);
|
|
40
|
+
return plugins;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {string[]} colors - color schema
|
|
45
|
+
* @return {string} - random color
|
|
46
|
+
*/
|
|
47
|
+
export const generateRandomColor = (colors) => {
|
|
48
|
+
const color = `#${Math.floor(Math.random() * WHITE_COLOR_AS_DECIMAL).toString(
|
|
49
|
+
16,
|
|
50
|
+
)}`;
|
|
51
|
+
if (colors.includes(color)) {
|
|
52
|
+
return generateRandomColor(colors);
|
|
53
|
+
} else {
|
|
54
|
+
colors.push(color);
|
|
55
|
+
return color;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @param {import('./chart-interface').IChartAnnotationsData[]} annotationsData
|
|
61
|
+
* @return {number[]|*[]}
|
|
62
|
+
*/
|
|
63
|
+
export const setAnnotations = (annotationsData) => {
|
|
64
|
+
return annotationsData?.length ? annotationsData.map((v, i) => i) : [];
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @param {import('../components/bar-chart/bar-chart.interface').IBarChartOptions |
|
|
69
|
+
* import('../components/line-chart/line-chart.interface').ILineChartOptions} options - chart options object
|
|
70
|
+
* @return {{color: (string|undefined), display: boolean, text}|{}}
|
|
71
|
+
*/
|
|
72
|
+
export const getTitle = (options) => {
|
|
73
|
+
return options.title !== ''
|
|
74
|
+
? {
|
|
75
|
+
display: true,
|
|
76
|
+
text: options.title,
|
|
77
|
+
}
|
|
78
|
+
: {};
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @function isVertical
|
|
83
|
+
* @param {'vertical'|'horizontal'|string} direction
|
|
84
|
+
* @return {boolean} returns true if chart direction is vertical
|
|
85
|
+
*/
|
|
86
|
+
export const isVertical = (direction) => {
|
|
87
|
+
return direction === ChartDirection.Vertical;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {'x'|'y'} axisType
|
|
92
|
+
* @param {number} i - index
|
|
93
|
+
* @return {'top'|'bottom'|'left'|'right'|*}
|
|
94
|
+
*/
|
|
95
|
+
export const getAxisPosition = (axisType, i) => {
|
|
96
|
+
const [positionA, positionB] =
|
|
97
|
+
axisType === AxisType.Y
|
|
98
|
+
? [Position.Left, Position.Right]
|
|
99
|
+
: [Position.Top, Position.Bottom];
|
|
100
|
+
return i % 2 === 0 ? positionA : positionB;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @param {import('../helpers/chart-interface').IChartStyling} chartStyling
|
|
105
|
+
* @param {Object} styles - styles imported form .less file
|
|
106
|
+
* @return {string} - class name
|
|
107
|
+
*/
|
|
108
|
+
export const getClassName = (chartStyling, styles) => {
|
|
109
|
+
const { width, height, staticChartHeight, squareAspectRatio } = chartStyling;
|
|
110
|
+
const squareAspectRatioStyle = squareAspectRatio
|
|
111
|
+
? styles.squareAspectRatio
|
|
112
|
+
: '';
|
|
113
|
+
let heightStyles = '';
|
|
114
|
+
if (width || height) {
|
|
115
|
+
heightStyles = '';
|
|
116
|
+
} else {
|
|
117
|
+
heightStyles = staticChartHeight
|
|
118
|
+
? styles?.fixedHeight
|
|
119
|
+
: styles?.stretchHeight;
|
|
120
|
+
}
|
|
121
|
+
return cx(styles.chart, heightStyles, squareAspectRatioStyle);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
*
|
|
126
|
+
* @param {import('../components/bar-chart/bar-chart.interface').IBarChartOptions |
|
|
127
|
+
* import('../components/line-chart/line-chart.interface').ILineChartOptions} options - chart options object
|
|
128
|
+
* @param {function} clickHandler - on click callback
|
|
129
|
+
* @param {import('./chart-interface').IInitialState} state - chart state object controlled by useReducer or similar
|
|
130
|
+
* @returns {*}
|
|
131
|
+
*/
|
|
132
|
+
export const getLegend = (options, clickHandler, state = null) => {
|
|
133
|
+
const { legend, chartStyling } = options;
|
|
134
|
+
return {
|
|
135
|
+
position: legend.position || Position.Top,
|
|
136
|
+
display: !legend?.customLegend?.customLegendPlugin
|
|
137
|
+
? state
|
|
138
|
+
? state.legendEnabled
|
|
139
|
+
: legend.display
|
|
140
|
+
: false,
|
|
141
|
+
align: legend.align,
|
|
142
|
+
labels: {
|
|
143
|
+
boxHeight: LEGEND_LABEL_BOX_SIZE,
|
|
144
|
+
boxWidth: LEGEND_LABEL_BOX_SIZE,
|
|
145
|
+
usePointStyle: true,
|
|
146
|
+
filter: (item, data) => !data.datasets[item.datasetIndex].hideLegend,
|
|
147
|
+
},
|
|
148
|
+
onClick: clickHandler,
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export const afterLabelCallback = (tooltipItem) => {
|
|
153
|
+
const { error } = tooltipItem.dataset.data[tooltipItem?.dataIndex];
|
|
154
|
+
return error ? `Error: ${Math.round(error * 10000) / 10000}` : '';
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export const getTooltipLabel = (tooltipItem, showLabelsInTooltips) => {
|
|
158
|
+
const datasetDataLabel =
|
|
159
|
+
tooltipItem.dataset.data[tooltipItem.dataIndex]?.label;
|
|
160
|
+
const dataLabel = Array.isArray(datasetDataLabel)
|
|
161
|
+
? datasetDataLabel.join(' , ')
|
|
162
|
+
: datasetDataLabel;
|
|
163
|
+
return showLabelsInTooltips && dataLabel?.length ? ` (${dataLabel})` : '';
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
//TODO: consider returning chart name instead of axis names
|
|
167
|
+
export const getChartFileName = (axes) => {
|
|
168
|
+
if (!axes) {
|
|
169
|
+
return DEFAULT_CHART_NAME;
|
|
170
|
+
}
|
|
171
|
+
const axesLabels = axes.reduce((acc, cur, index) => {
|
|
172
|
+
const labelWithNoSpace = cur.label?.replace(/\s/g, '_') || index;
|
|
173
|
+
return [...acc, labelWithNoSpace];
|
|
174
|
+
}, []);
|
|
175
|
+
return axesLabels.join('_');
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export const setDefaultTheme = () => {
|
|
179
|
+
defaults.font.size = DEFAULT_FONT_SIZE;
|
|
180
|
+
defaults.font.family = DEFAULT_FONT_FAMILY;
|
|
181
|
+
defaults.color = DEFAULT_COLOR;
|
|
182
|
+
defaults.borderColor = BORDER_COLOR;
|
|
183
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
|
|
4
|
+
import '../style/reset/reset.less';
|
|
5
|
+
import '../style/global.less';
|
|
6
|
+
|
|
7
|
+
const outerContainerStyle = {
|
|
8
|
+
margin: '50px',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const Container = ({
|
|
12
|
+
style,
|
|
13
|
+
children,
|
|
14
|
+
margin,
|
|
15
|
+
deprecatedMessage,
|
|
16
|
+
warningMessage,
|
|
17
|
+
}) => {
|
|
18
|
+
return (
|
|
19
|
+
<div style={margin ? outerContainerStyle : {}}>
|
|
20
|
+
{deprecatedMessage ? (
|
|
21
|
+
<div
|
|
22
|
+
style={{
|
|
23
|
+
border: '1px solid red',
|
|
24
|
+
color: 'red',
|
|
25
|
+
padding: '10px',
|
|
26
|
+
marginBottom: '20px',
|
|
27
|
+
}}
|
|
28
|
+
>
|
|
29
|
+
{deprecatedMessage}
|
|
30
|
+
</div>
|
|
31
|
+
) : null}
|
|
32
|
+
{warningMessage ? (
|
|
33
|
+
<div
|
|
34
|
+
style={{
|
|
35
|
+
border: '1px solid orange',
|
|
36
|
+
color: 'orange',
|
|
37
|
+
padding: '10px',
|
|
38
|
+
marginBottom: '20px',
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
{warningMessage}
|
|
42
|
+
</div>
|
|
43
|
+
) : null}
|
|
44
|
+
<div style={style}>{children}</div>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
Container.defaultProps = {
|
|
50
|
+
style: {},
|
|
51
|
+
margin: true,
|
|
52
|
+
deprecatedMessage: null,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
Container.propTypes = {
|
|
56
|
+
style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
|
|
57
|
+
children: PropTypes.node.isRequired,
|
|
58
|
+
margin: PropTypes.bool,
|
|
59
|
+
deprecatedMessage: PropTypes.string,
|
|
60
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @enum {string}
|
|
3
|
+
*/
|
|
4
|
+
export const AxisType = Object.freeze({
|
|
5
|
+
X: 'x',
|
|
6
|
+
Y: 'y',
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @enum {string}
|
|
11
|
+
*/
|
|
12
|
+
export const Position = Object.freeze({
|
|
13
|
+
Bottom: 'bottom',
|
|
14
|
+
Top: 'top',
|
|
15
|
+
Left: 'left',
|
|
16
|
+
Right: 'right',
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @enum {string}
|
|
21
|
+
*/
|
|
22
|
+
export const ChartType = Object.freeze({
|
|
23
|
+
Line: 'line',
|
|
24
|
+
Bar: 'bar',
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @enum {string}
|
|
29
|
+
*/
|
|
30
|
+
export const CursorStyle = Object.freeze({
|
|
31
|
+
Pointer: 'pointer',
|
|
32
|
+
Initial: 'initial',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @enum {string}
|
|
37
|
+
*/
|
|
38
|
+
export const ScaleType = Object.freeze({
|
|
39
|
+
Category: 'category', //TODO: verify
|
|
40
|
+
Linear: 'linear',
|
|
41
|
+
Logarithmic: 'logarithmic',
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @enum {string}
|
|
46
|
+
*/
|
|
47
|
+
export const ChartDirection = Object.freeze({
|
|
48
|
+
Vertical: 'vertical',
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @enum {string}
|
|
53
|
+
*/
|
|
54
|
+
export const TooltipLabel = Object.freeze({
|
|
55
|
+
Y: 'yLabel',
|
|
56
|
+
X: 'xLabel',
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export const AlignOptions = Object.freeze({
|
|
60
|
+
End: 'end',
|
|
61
|
+
Start: 'start',
|
|
62
|
+
Center: 'center',
|
|
63
|
+
Right: 'right',
|
|
64
|
+
Left: 'left',
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export const PointType = Object.freeze({
|
|
68
|
+
Casing: 'casing',
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
export const PointStyle = Object.freeze({
|
|
72
|
+
Circle: 'circle',
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
export const ChartHoverMode = Object.freeze({
|
|
76
|
+
Nearest: 'nearest',
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
export const PanZoomMode = Object.freeze({
|
|
80
|
+
X: 'x',
|
|
81
|
+
Y: 'y',
|
|
82
|
+
XY: 'xy',
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
export const Key = Object.freeze({
|
|
86
|
+
Shift: 'Shift',
|
|
87
|
+
});
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { AxisType, ChartType, CursorStyle, Position } from './enums';
|
|
2
|
+
import { ANNOTATION_DASH, BORDER_WIDTH, COLORS } from './chart-consts';
|
|
3
|
+
|
|
4
|
+
const annotationEnter = ({ element }, { chart }) => {
|
|
5
|
+
if (element.options.scaleID?.includes(AxisType.X)) {
|
|
6
|
+
element.options.label.xAdjust = chart.chartArea.left;
|
|
7
|
+
}
|
|
8
|
+
element.options.borderWidth = BORDER_WIDTH.HOVERED;
|
|
9
|
+
if (element.options.label) element.options.label.enabled = true;
|
|
10
|
+
chart.draw();
|
|
11
|
+
chart.canvas.style.cursor = CursorStyle.Pointer;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const annotationLeave = ({ element }, { chart }) => {
|
|
15
|
+
element.options.borderWidth = BORDER_WIDTH.INITIAL;
|
|
16
|
+
if (element.options.label) element.options.label.enabled = false;
|
|
17
|
+
chart.draw();
|
|
18
|
+
chart.canvas.style.cursor = CursorStyle.Initial;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {import('../components/bar-chart/bar-chart.interface').IBarChartOptions |
|
|
23
|
+
* import('../components/line-chart/line-chart.interface').ILineChartOptions} options - chart options object
|
|
24
|
+
* @param {import('./chart-interface').IInitialState} state - chart state object controlled by useReducer or similar
|
|
25
|
+
*/
|
|
26
|
+
const generateAnnotations = (options, state) => {
|
|
27
|
+
const { annotationsData } = options.annotations;
|
|
28
|
+
|
|
29
|
+
const annotations = annotationsData?.reduce((acc, curr, i) => {
|
|
30
|
+
if (!state?.showAnnotationLineIndex?.includes(i)) {
|
|
31
|
+
return { ...acc };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const getScaleId = () => {
|
|
35
|
+
const axisType = curr.annotationAxis;
|
|
36
|
+
return options.axes[axisType]?.length > 1 ? `${axisType}1` : axisType;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const color = curr?.color || COLORS[i];
|
|
40
|
+
const type = curr?.type || 'line';
|
|
41
|
+
const adjustScaleRange = curr?.adjustScaleRange;
|
|
42
|
+
const borderColor = type === 'line' ? color : 'transparent';
|
|
43
|
+
const borderWidth = type === 'line' ? BORDER_WIDTH.INITIAL : 0;
|
|
44
|
+
const borderDash = ANNOTATION_DASH;
|
|
45
|
+
|
|
46
|
+
const label =
|
|
47
|
+
type === 'line'
|
|
48
|
+
? {
|
|
49
|
+
backgroundColor: color,
|
|
50
|
+
content: curr?.label,
|
|
51
|
+
enabled: false,
|
|
52
|
+
position: Position.Top,
|
|
53
|
+
}
|
|
54
|
+
: {
|
|
55
|
+
content: curr?.label,
|
|
56
|
+
enabled: true,
|
|
57
|
+
font: { weight: 'normal' },
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const enter = (context, event) => {
|
|
61
|
+
if (type !== 'line') return;
|
|
62
|
+
annotationEnter(context, event);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const leave = (context, event) => {
|
|
66
|
+
if (type !== 'line') return;
|
|
67
|
+
annotationLeave(context, event);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const annotation = {
|
|
71
|
+
...curr,
|
|
72
|
+
id: `${curr?.label}-${curr?.value}-${i}`,
|
|
73
|
+
scaleID: getScaleId(),
|
|
74
|
+
label,
|
|
75
|
+
backgroundColor: color,
|
|
76
|
+
borderColor,
|
|
77
|
+
borderWidth,
|
|
78
|
+
borderDash,
|
|
79
|
+
type,
|
|
80
|
+
adjustScaleRange,
|
|
81
|
+
enter,
|
|
82
|
+
leave,
|
|
83
|
+
};
|
|
84
|
+
return { ...acc, [`annotation${i + 1}`]: annotation };
|
|
85
|
+
}, {});
|
|
86
|
+
return annotations;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @param {import('../components/bar-chart/bar-chart.interface').IBarChartOptions |
|
|
91
|
+
* import('../components/line-chart/line-chart.interface').ILineChartOptions} options - chart options object
|
|
92
|
+
* @return {{label1: {}}}
|
|
93
|
+
*/
|
|
94
|
+
const getLabelAnnotation = (options) => {
|
|
95
|
+
const { annotations = {} } = options;
|
|
96
|
+
const { labelAnnotation = {} } = annotations;
|
|
97
|
+
const isDarkModeOn = options.chartStyling.darkMode || false;
|
|
98
|
+
const {
|
|
99
|
+
content = [],
|
|
100
|
+
xAdjust = -200,
|
|
101
|
+
yAdjust = 120,
|
|
102
|
+
fontSize = 12,
|
|
103
|
+
} = labelAnnotation;
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
label1: {
|
|
107
|
+
type: 'label',
|
|
108
|
+
xAdjust,
|
|
109
|
+
yAdjust,
|
|
110
|
+
backgroundColor: 'rgba(0, 0, 0, 0)',
|
|
111
|
+
color: isDarkModeOn && 'rgba(255, 255, 255, 1)',
|
|
112
|
+
content,
|
|
113
|
+
font: {
|
|
114
|
+
size: fontSize,
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @param {import('../components/bar-chart/bar-chart.interface').IBarChartOptions |
|
|
122
|
+
* import('../components/line-chart/line-chart.interface').ILineChartOptions} options - chart options object
|
|
123
|
+
* @param {import('./chart-interface').IInitialState} state - chart state object controlled by useReducer or similar
|
|
124
|
+
* @return {{annotations: []}}
|
|
125
|
+
*/
|
|
126
|
+
const getAnnotation = (options, state) => {
|
|
127
|
+
const { annotations } = options;
|
|
128
|
+
const isAnnotationShown = annotations?.showAnnotations;
|
|
129
|
+
const isAnnotationDataProvided = annotations?.annotationsData?.length;
|
|
130
|
+
|
|
131
|
+
const formAnnotation =
|
|
132
|
+
isAnnotationShown && isAnnotationDataProvided
|
|
133
|
+
? {
|
|
134
|
+
...getLabelAnnotation(options),
|
|
135
|
+
...generateAnnotations(options, state),
|
|
136
|
+
}
|
|
137
|
+
: null;
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
annotations: formAnnotation,
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
export default getAnnotation;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const getOrCreateLegendList = (chart, id) => {
|
|
2
|
+
const legendContainer = document.getElementById(id);
|
|
3
|
+
let listContainer = legendContainer.querySelector('ul');
|
|
4
|
+
|
|
5
|
+
if (!listContainer) {
|
|
6
|
+
listContainer = document.createElement('ul');
|
|
7
|
+
listContainer.style.display = 'flex';
|
|
8
|
+
listContainer.style.flexDirection = 'row';
|
|
9
|
+
listContainer.style.margin = 0;
|
|
10
|
+
listContainer.style.padding = 0;
|
|
11
|
+
|
|
12
|
+
legendContainer.appendChild(listContainer);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return listContainer;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Gets an example custom legend plugin for use in storybook.
|
|
20
|
+
* @param {string} customLegendContainerID - the id of the div container to put the generated legend in
|
|
21
|
+
*/
|
|
22
|
+
export const getCustomLegendPlugin = (customLegendContainerID) => ({
|
|
23
|
+
afterUpdate(chart, _args, _options) {
|
|
24
|
+
const ul = getOrCreateLegendList(chart, customLegendContainerID);
|
|
25
|
+
|
|
26
|
+
// Remove old legend items
|
|
27
|
+
while (ul.firstChild) {
|
|
28
|
+
ul.firstChild.remove();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Reuse the built-in legendItems generator
|
|
32
|
+
const items = chart.options.plugins.legend.labels.generateLabels(chart);
|
|
33
|
+
|
|
34
|
+
items.forEach((item) => {
|
|
35
|
+
const li = document.createElement('li');
|
|
36
|
+
li.style.alignItems = 'center';
|
|
37
|
+
li.style.cursor = 'pointer';
|
|
38
|
+
li.style.display = 'flex';
|
|
39
|
+
li.style.flexDirection = 'row';
|
|
40
|
+
li.style.marginLeft = '10px';
|
|
41
|
+
|
|
42
|
+
li.onclick = () => {
|
|
43
|
+
const { type } = chart.config;
|
|
44
|
+
if (type === 'pie' || type === 'doughnut') {
|
|
45
|
+
// Pie and doughnut charts only have a single dataset and visibility is per item
|
|
46
|
+
chart.toggleDataVisibility(item.index);
|
|
47
|
+
} else {
|
|
48
|
+
chart.setDatasetVisibility(
|
|
49
|
+
item.datasetIndex,
|
|
50
|
+
!chart.isDatasetVisible(item.datasetIndex),
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
chart.update();
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Color box
|
|
57
|
+
const boxSpan = document.createElement('span');
|
|
58
|
+
boxSpan.style.background = item.fillStyle;
|
|
59
|
+
boxSpan.style.borderColor = item.strokeStyle;
|
|
60
|
+
boxSpan.style.borderWidth = item.lineWidth + 'px';
|
|
61
|
+
boxSpan.style.display = 'inline-block';
|
|
62
|
+
boxSpan.style.height = '20px';
|
|
63
|
+
boxSpan.style.marginRight = '10px';
|
|
64
|
+
boxSpan.style.width = '20px';
|
|
65
|
+
|
|
66
|
+
// Text
|
|
67
|
+
const textContainer = document.createElement('p');
|
|
68
|
+
textContainer.style.margin = 0;
|
|
69
|
+
textContainer.style.padding = 0;
|
|
70
|
+
textContainer.style.textDecoration = item.hidden ? 'line-through' : '';
|
|
71
|
+
|
|
72
|
+
const text = document.createTextNode(item.text);
|
|
73
|
+
textContainer.appendChild(text);
|
|
74
|
+
|
|
75
|
+
li.appendChild(boxSpan);
|
|
76
|
+
li.appendChild(textContainer);
|
|
77
|
+
ul.appendChild(li);
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { DECIMAL_POINT_TOLERANCE } from '../chart-consts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Rounds a number to N decimal places
|
|
5
|
+
*
|
|
6
|
+
* @param {number} v value
|
|
7
|
+
* @param {number} n decimal count
|
|
8
|
+
* @returns {number}
|
|
9
|
+
*/
|
|
10
|
+
export const roundN = (v, n) => {
|
|
11
|
+
const factor = 10 ** n;
|
|
12
|
+
return Math.round(v * factor) / factor;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Rounds a number to DECIMAL_POINT_TOLERANCE decimal places
|
|
17
|
+
*
|
|
18
|
+
* @param {number} v value
|
|
19
|
+
* @returns {number}
|
|
20
|
+
*/
|
|
21
|
+
export const round = (v) => roundN(v, DECIMAL_POINT_TOLERANCE);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Determines whether two numbers are close in value with a tolerance
|
|
25
|
+
* (mitigates excess JavaScript floating point precision quirks)
|
|
26
|
+
*
|
|
27
|
+
* Inspired by WellDesign implementations:
|
|
28
|
+
* - isCloseTo in rounding.js
|
|
29
|
+
* - isEqual in TDHYDutils.js (recommended by Truls, but quirks comparing values close to 0 need testing)
|
|
30
|
+
* - TODO: replace this with a universal Oliasoft implementation when ready
|
|
31
|
+
*
|
|
32
|
+
* @param {number} a
|
|
33
|
+
* @param {number} b
|
|
34
|
+
* @returns {boolean}
|
|
35
|
+
*/
|
|
36
|
+
export const isEqualWithTolerance = (a, b) => {
|
|
37
|
+
if (typeof a == 'number' && typeof b === typeof a) {
|
|
38
|
+
const tolerance = 10 ** -DECIMAL_POINT_TOLERANCE;
|
|
39
|
+
const difference = Math.abs(b - a);
|
|
40
|
+
const roundedDifference = roundN(difference, DECIMAL_POINT_TOLERANCE);
|
|
41
|
+
return roundedDifference <= tolerance;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
};
|