@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,120 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateRandomColor,
|
|
3
|
+
getAxisPosition,
|
|
4
|
+
} from '../../helpers/chart-utils';
|
|
5
|
+
import { COLORS, LOGARITHMIC_STEPS } from '../../helpers/chart-consts';
|
|
6
|
+
import { generateAxisId, truncateDecimals } from './line-chart-utils';
|
|
7
|
+
import { AxisType, ScaleType } from '../../helpers/enums';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {import('./line-chart.interface').ILineChartOptions} options - line chart options object
|
|
11
|
+
* @param {'x'|'y'} axisType
|
|
12
|
+
* @param {import('../../helpers/chart-interface').IInitialState} state - chart state object controlled by useReducer or similar
|
|
13
|
+
* @param {import('./line-chart.interface').ILineChartAxis[]} [currentScales]
|
|
14
|
+
* @param {number} [i]
|
|
15
|
+
*/
|
|
16
|
+
const getLineChartAxis = (options, axisType, state, currentScales, i = 0) => {
|
|
17
|
+
const axisData = currentScales || options.axes[axisType][0];
|
|
18
|
+
const stateAxis = state.axes.filter((axis) => axis.id.startsWith(axisType))[
|
|
19
|
+
i
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const { additionalAxesOptions, chartStyling } = options;
|
|
23
|
+
|
|
24
|
+
const getTicks = () => {
|
|
25
|
+
const truncateAxisNumbersToDigitsCallback = Number.isInteger(
|
|
26
|
+
additionalAxesOptions.truncateAxisNumbersToDigitsCallback,
|
|
27
|
+
)
|
|
28
|
+
? {
|
|
29
|
+
callback: (tick) =>
|
|
30
|
+
truncateDecimals(
|
|
31
|
+
tick,
|
|
32
|
+
additionalAxesOptions.truncateAxisNumbersToDigitsCallback,
|
|
33
|
+
),
|
|
34
|
+
}
|
|
35
|
+
: {};
|
|
36
|
+
|
|
37
|
+
const ticks =
|
|
38
|
+
additionalAxesOptions.chartScaleType === ScaleType.Logarithmic
|
|
39
|
+
? {
|
|
40
|
+
callback: (tick) => {
|
|
41
|
+
return LOGARITHMIC_STEPS.includes(tick)
|
|
42
|
+
? tick.toLocaleString()
|
|
43
|
+
: '';
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
: {
|
|
47
|
+
stepSize:
|
|
48
|
+
axisData.stepSize ||
|
|
49
|
+
(axisType === AxisType.Y ? additionalAxesOptions.stepSize : null),
|
|
50
|
+
...truncateAxisNumbersToDigitsCallback,
|
|
51
|
+
};
|
|
52
|
+
return {
|
|
53
|
+
...ticks,
|
|
54
|
+
includeBounds: false, //OW-10088 disable irregular axis ticks
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
type: additionalAxesOptions.chartScaleType,
|
|
60
|
+
position: axisData.position,
|
|
61
|
+
beginAtZero: additionalAxesOptions.beginAtZero,
|
|
62
|
+
reverse: axisType === AxisType.Y ? additionalAxesOptions.reverse : false,
|
|
63
|
+
suggestedMax: additionalAxesOptions.suggestedMax,
|
|
64
|
+
suggestedMin: additionalAxesOptions.suggestedMin,
|
|
65
|
+
min: stateAxis?.min ?? additionalAxesOptions?.range?.[axisType]?.min,
|
|
66
|
+
max: stateAxis?.max ?? additionalAxesOptions?.range?.[axisType]?.max,
|
|
67
|
+
title: {
|
|
68
|
+
display: axisData.label?.length,
|
|
69
|
+
text: axisData.label,
|
|
70
|
+
padding: 0,
|
|
71
|
+
},
|
|
72
|
+
ticks: getTicks(),
|
|
73
|
+
grid: {
|
|
74
|
+
...axisData.gridLines,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @param {import('./line-chart.interface').ILineChartOptions} options - line chart options object
|
|
81
|
+
* @param {'x'|'y'} axisType
|
|
82
|
+
* @param {import('../../helpers/chart-interface').IInitialState} state - chart state object controlled by useReducer or similar
|
|
83
|
+
*/
|
|
84
|
+
const getLineChartAxes = (options, axisType, state) => {
|
|
85
|
+
const axesData = options.axes[axisType];
|
|
86
|
+
const axes = axesData.reduce((acc, curr, i) => {
|
|
87
|
+
const axisData = curr;
|
|
88
|
+
axisData.color = curr.color || COLORS[i] || generateRandomColor(COLORS);
|
|
89
|
+
axisData.position = curr.position || getAxisPosition(axisType, i);
|
|
90
|
+
|
|
91
|
+
const axis = getLineChartAxis(options, axisType, state, axisData, i);
|
|
92
|
+
const axisId = generateAxisId(axisType, i, true);
|
|
93
|
+
return { ...acc, [axisId]: axis };
|
|
94
|
+
}, {});
|
|
95
|
+
return axes;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
*
|
|
100
|
+
* @param {import('./line-chart.interface').ILineChartOptions} options - line chart options object
|
|
101
|
+
* @param {import('../../helpers/chart-interface').IInitialState} state - chart state object controlled by useReducer or similar
|
|
102
|
+
*/
|
|
103
|
+
const getLineChartScales = (options, state) => {
|
|
104
|
+
const hasMultipleXAxes = options.axes.x?.length > 1;
|
|
105
|
+
const hasMultipleYAxes = options.axes.y?.length > 1;
|
|
106
|
+
|
|
107
|
+
const xAxes = hasMultipleXAxes
|
|
108
|
+
? getLineChartAxes(options, AxisType.X, state)
|
|
109
|
+
: { x: getLineChartAxis(options, AxisType.X, state) };
|
|
110
|
+
const yAxes = hasMultipleYAxes
|
|
111
|
+
? getLineChartAxes(options, AxisType.Y, state)
|
|
112
|
+
: { y: getLineChartAxis(options, AxisType.Y, state) };
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
...xAxes,
|
|
116
|
+
...yAxes,
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export default getLineChartScales;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { ChartHoverMode, Position, TooltipLabel } from '../../helpers/enums';
|
|
2
|
+
import { afterLabelCallback, getTooltipLabel } from '../../helpers/chart-utils';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {import('./line-chart.interface').ILineChartOptions} options - line chart options object
|
|
6
|
+
*/
|
|
7
|
+
const getLineChartToolTips = (options) => {
|
|
8
|
+
const getTooltipLabels = (dataset) => {
|
|
9
|
+
const xIndex = dataset.xAxisID?.length > 1 ? dataset.xAxisID[1] - 1 : 0;
|
|
10
|
+
const yIndex = dataset.yAxisID?.length > 1 ? dataset.yAxisID[1] - 1 : 0;
|
|
11
|
+
const xAxis = options.axes.x[xIndex];
|
|
12
|
+
const yAxis = options.axes.y[yIndex];
|
|
13
|
+
|
|
14
|
+
if (options.axes.x[0].position === Position.Top) {
|
|
15
|
+
return {
|
|
16
|
+
titleAxisLabel: yAxis?.label || '',
|
|
17
|
+
valueAxisLabel: xAxis?.label || '',
|
|
18
|
+
titleLabel: TooltipLabel.Y,
|
|
19
|
+
valueLabel: TooltipLabel.X,
|
|
20
|
+
};
|
|
21
|
+
} else {
|
|
22
|
+
return {
|
|
23
|
+
titleAxisLabel: xAxis?.label || '',
|
|
24
|
+
valueAxisLabel: yAxis?.label || '',
|
|
25
|
+
titleLabel: TooltipLabel.X,
|
|
26
|
+
valueLabel: TooltipLabel.Y,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const titleCallback = (tooltipItem, data) => {
|
|
32
|
+
const labels = getTooltipLabels(tooltipItem[0].dataset);
|
|
33
|
+
const { titleLabel, titleAxisLabel } = labels;
|
|
34
|
+
|
|
35
|
+
const formattedValue =
|
|
36
|
+
titleLabel === TooltipLabel.Y
|
|
37
|
+
? tooltipItem[0].parsed.y
|
|
38
|
+
: tooltipItem[0].parsed.x;
|
|
39
|
+
const roundedValue =
|
|
40
|
+
Math.abs(formattedValue) < 1
|
|
41
|
+
? formattedValue.toPrecision(3)
|
|
42
|
+
: formattedValue.toFixed(2);
|
|
43
|
+
|
|
44
|
+
return `${roundedValue} ${titleAxisLabel}`;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const labelCallback = (tooltipItem) => {
|
|
48
|
+
const { showLabelsInTooltips, hideSimulationName } = options.tooltip;
|
|
49
|
+
let label = tooltipItem.dataset.label || '';
|
|
50
|
+
const labels = getTooltipLabels(tooltipItem.dataset);
|
|
51
|
+
const { valueLabel, valueAxisLabel } = labels;
|
|
52
|
+
|
|
53
|
+
const getTooltipItemValue = () => {
|
|
54
|
+
const labelNumber =
|
|
55
|
+
valueLabel === TooltipLabel.X
|
|
56
|
+
? tooltipItem.parsed.x
|
|
57
|
+
: tooltipItem.parsed.y;
|
|
58
|
+
return Math.abs(labelNumber) < 1
|
|
59
|
+
? labelNumber.toPrecision(3)
|
|
60
|
+
: labelNumber.toFixed(3);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const splitValueAxisLabel = valueAxisLabel.split(' ');
|
|
64
|
+
const newValueAxisLabel = hideSimulationName
|
|
65
|
+
? splitValueAxisLabel[splitValueAxisLabel.length - 1]
|
|
66
|
+
: valueAxisLabel;
|
|
67
|
+
|
|
68
|
+
const tooltipItemValue = getTooltipItemValue();
|
|
69
|
+
const valAxisLabel =
|
|
70
|
+
label === valueAxisLabel || valueAxisLabel.includes(label)
|
|
71
|
+
? ''
|
|
72
|
+
: newValueAxisLabel;
|
|
73
|
+
const tooltipLabel = getTooltipLabel(tooltipItem, showLabelsInTooltips);
|
|
74
|
+
|
|
75
|
+
return `${label}: ${tooltipItemValue} ${valAxisLabel}${tooltipLabel}`;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
enabled: options.tooltip.tooltips,
|
|
80
|
+
mode: ChartHoverMode.Nearest,
|
|
81
|
+
intersect: true,
|
|
82
|
+
padding: 8,
|
|
83
|
+
callbacks: {
|
|
84
|
+
title: titleCallback,
|
|
85
|
+
label: labelCallback,
|
|
86
|
+
afterLabel: afterLabelCallback,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export default getLineChartToolTips;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const DEFAULT_POINT_RADIUS = 2;
|
|
2
|
+
export const DEFAULT_HOVER_RADIUS = 5;
|
|
3
|
+
export const DEFAULT_BORDER_WIDTH = 1;
|
|
4
|
+
export const BORDER_JOIN_STYLE = 'round';
|
|
5
|
+
export const DEFAULT_LINE_POINT_RADIUS = 0;
|
|
6
|
+
export const DEFAULT_BACKGROUND_COLOR = 'transparent';
|
|
7
|
+
export const ZOOM_BOX_BACKGROUND_COLOR = 'rgba(0, 0, 0, 0.1)';
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import PropTypes from 'prop-types';
|
|
2
|
+
|
|
3
|
+
export const LineChartPropTypes = {
|
|
4
|
+
table: PropTypes.node,
|
|
5
|
+
chart: PropTypes.shape({
|
|
6
|
+
testId: PropTypes.string,
|
|
7
|
+
data: PropTypes.object.isRequired,
|
|
8
|
+
options: PropTypes.shape({
|
|
9
|
+
title: PropTypes.oneOfType([
|
|
10
|
+
PropTypes.string,
|
|
11
|
+
PropTypes.arrayOf(PropTypes.string),
|
|
12
|
+
]),
|
|
13
|
+
axes: PropTypes.shape({
|
|
14
|
+
x: PropTypes.arrayOf(
|
|
15
|
+
PropTypes.shape({
|
|
16
|
+
label: PropTypes.string,
|
|
17
|
+
position: PropTypes.oneOf(['top', 'bottom']),
|
|
18
|
+
color: PropTypes.string,
|
|
19
|
+
}),
|
|
20
|
+
),
|
|
21
|
+
y: PropTypes.arrayOf(
|
|
22
|
+
PropTypes.shape({
|
|
23
|
+
label: PropTypes.string,
|
|
24
|
+
position: PropTypes.oneOf(['left', 'right']),
|
|
25
|
+
color: PropTypes.string,
|
|
26
|
+
}),
|
|
27
|
+
),
|
|
28
|
+
}),
|
|
29
|
+
additionalAxesOptions: PropTypes.shape({
|
|
30
|
+
chartScaleType: PropTypes.oneOf(['linear', 'logarithmic']),
|
|
31
|
+
reverse: PropTypes.bool,
|
|
32
|
+
beginAtZero: PropTypes.bool,
|
|
33
|
+
stepSize: PropTypes.number,
|
|
34
|
+
truncateAxisNumbersToDigitsCallback: PropTypes.number,
|
|
35
|
+
suggestedMin: PropTypes.number,
|
|
36
|
+
suggestedMax: PropTypes.number,
|
|
37
|
+
range: PropTypes.shape({
|
|
38
|
+
x: PropTypes.shape({
|
|
39
|
+
min: PropTypes.number,
|
|
40
|
+
max: PropTypes.number,
|
|
41
|
+
}),
|
|
42
|
+
y: PropTypes.shape({
|
|
43
|
+
min: PropTypes.number,
|
|
44
|
+
max: PropTypes.number,
|
|
45
|
+
}),
|
|
46
|
+
}),
|
|
47
|
+
autoAxisPadding: PropTypes.bool,
|
|
48
|
+
}),
|
|
49
|
+
chartStyling: PropTypes.shape({
|
|
50
|
+
width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
|
51
|
+
height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
|
52
|
+
maintainAspectRatio: PropTypes.bool,
|
|
53
|
+
staticChartHeight: PropTypes.bool,
|
|
54
|
+
performanceMode: PropTypes.bool,
|
|
55
|
+
squareAspectRatio: PropTypes.bool,
|
|
56
|
+
}),
|
|
57
|
+
tooltip: PropTypes.shape({
|
|
58
|
+
tooltips: PropTypes.bool,
|
|
59
|
+
showLabelsInTooltips: PropTypes.bool,
|
|
60
|
+
hideSimulationName: PropTypes.bool,
|
|
61
|
+
}),
|
|
62
|
+
graph: PropTypes.shape({
|
|
63
|
+
lineTension: PropTypes.number,
|
|
64
|
+
spanGaps: PropTypes.bool,
|
|
65
|
+
showDataLabels: PropTypes.bool,
|
|
66
|
+
showMinorGridlines: PropTypes.bool,
|
|
67
|
+
}),
|
|
68
|
+
annotations: PropTypes.shape({
|
|
69
|
+
labelAnnotation: PropTypes.object,
|
|
70
|
+
showAnnotations: PropTypes.bool,
|
|
71
|
+
controlAnnotation: PropTypes.bool,
|
|
72
|
+
annotationsData: PropTypes.arrayOf(
|
|
73
|
+
PropTypes.shape({
|
|
74
|
+
adjustScaleRange: PropTypes.bool,
|
|
75
|
+
annotationAxis: PropTypes.oneOf(['x', 'y']),
|
|
76
|
+
color: PropTypes.string,
|
|
77
|
+
endValue: PropTypes.number,
|
|
78
|
+
label: PropTypes.string,
|
|
79
|
+
type: PropTypes.string,
|
|
80
|
+
value: PropTypes.number,
|
|
81
|
+
xMin: PropTypes.number,
|
|
82
|
+
xMax: PropTypes.number,
|
|
83
|
+
yMin: PropTypes.number,
|
|
84
|
+
yMax: PropTypes.number,
|
|
85
|
+
}),
|
|
86
|
+
),
|
|
87
|
+
}),
|
|
88
|
+
legend: PropTypes.shape({
|
|
89
|
+
display: PropTypes.bool,
|
|
90
|
+
position: PropTypes.oneOf(['top', 'bottom', 'right']),
|
|
91
|
+
align: PropTypes.oneOf(['start', 'center', 'end']),
|
|
92
|
+
customLegend: PropTypes.shape({
|
|
93
|
+
customLegendPlugin: PropTypes.object,
|
|
94
|
+
customLegendContainerID: PropTypes.string,
|
|
95
|
+
}),
|
|
96
|
+
}),
|
|
97
|
+
chartOptions: PropTypes.shape({
|
|
98
|
+
showPoints: PropTypes.bool,
|
|
99
|
+
enableZoom: PropTypes.bool,
|
|
100
|
+
enablePan: PropTypes.bool,
|
|
101
|
+
closeOnOutsideClick: PropTypes.bool,
|
|
102
|
+
}),
|
|
103
|
+
interactions: PropTypes.shape({
|
|
104
|
+
onLegendClick: PropTypes.func,
|
|
105
|
+
onPointHover: PropTypes.func,
|
|
106
|
+
onPointUnhover: PropTypes.func,
|
|
107
|
+
onAnimationComplete: PropTypes.func,
|
|
108
|
+
}),
|
|
109
|
+
}),
|
|
110
|
+
}).isRequired,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const getDefaultProps = (props) => {
|
|
114
|
+
// Add missing nested objects
|
|
115
|
+
props.chart.options = props.chart.options || {};
|
|
116
|
+
props.chart.options.axes = props.chart.options.axes || {};
|
|
117
|
+
props.chart.options.additionalAxesOptions =
|
|
118
|
+
props.chart.options.additionalAxesOptions || {};
|
|
119
|
+
props.chart.options.chartStyling = props.chart.options.chartStyling || {};
|
|
120
|
+
props.chart.options.tooltip = props.chart.options.tooltip || {};
|
|
121
|
+
props.chart.options.graph = props.chart.options.graph || {};
|
|
122
|
+
props.chart.options.annotations = props.chart.options.annotations || {};
|
|
123
|
+
props.chart.options.legend = props.chart.options.legend || {};
|
|
124
|
+
props.chart.options.legend.customLegend = props.chart.options.legend
|
|
125
|
+
.customLegend || { customLegendPlugin: null, customLegendContainerID: '' };
|
|
126
|
+
props.chart.options.chartOptions = props.chart.options.chartOptions || {};
|
|
127
|
+
props.chart.options.interactions = props.chart.options.interactions || {};
|
|
128
|
+
// Set defaults for missing properties
|
|
129
|
+
const chart = {
|
|
130
|
+
testId: props.chart.testId ?? null,
|
|
131
|
+
data: props.chart.data,
|
|
132
|
+
options: {
|
|
133
|
+
title: props.chart.options.title || '',
|
|
134
|
+
axes: {
|
|
135
|
+
x: props.chart.options.axes.x || [{}],
|
|
136
|
+
y: props.chart.options.axes.y || [{}],
|
|
137
|
+
},
|
|
138
|
+
additionalAxesOptions: {
|
|
139
|
+
chartScaleType:
|
|
140
|
+
props.chart.options.additionalAxesOptions.chartScaleType || 'linear',
|
|
141
|
+
reverse: props.chart.options.additionalAxesOptions.reverse || false,
|
|
142
|
+
beginAtZero:
|
|
143
|
+
props.chart.options.additionalAxesOptions.beginAtZero || false,
|
|
144
|
+
stepSize: props.chart.options.additionalAxesOptions.stepSize,
|
|
145
|
+
truncateAxisNumbersToDigitsCallback:
|
|
146
|
+
props.chart.options.additionalAxesOptions
|
|
147
|
+
.truncateAxisNumbersToDigitsCallback,
|
|
148
|
+
suggestedMin: props.chart.options.additionalAxesOptions.suggestedMin,
|
|
149
|
+
suggestedMax: props.chart.options.additionalAxesOptions.suggestedMax,
|
|
150
|
+
range: props.chart.options.additionalAxesOptions.range,
|
|
151
|
+
autoAxisPadding:
|
|
152
|
+
props.chart.options.additionalAxesOptions.autoAxisPadding ?? false,
|
|
153
|
+
},
|
|
154
|
+
chartStyling: {
|
|
155
|
+
width: props.chart.options.chartStyling.width,
|
|
156
|
+
height: props.chart.options.chartStyling.height,
|
|
157
|
+
maintainAspectRatio:
|
|
158
|
+
props.chart.options.chartStyling.maintainAspectRatio || false,
|
|
159
|
+
staticChartHeight:
|
|
160
|
+
props.chart.options.chartStyling.staticChartHeight || false,
|
|
161
|
+
performanceMode:
|
|
162
|
+
props.chart.options.chartStyling.performanceMode ?? true,
|
|
163
|
+
squareAspectRatio:
|
|
164
|
+
props.chart.options.chartStyling.squareAspectRatio || false,
|
|
165
|
+
},
|
|
166
|
+
tooltip: {
|
|
167
|
+
tooltips: props.chart.options.tooltip.tooltips ?? true,
|
|
168
|
+
showLabelsInTooltips:
|
|
169
|
+
props.chart.options.tooltip.showLabelsInTooltips || false,
|
|
170
|
+
hideSimulationName:
|
|
171
|
+
props.chart.options.tooltip.hideSimulationName || false,
|
|
172
|
+
},
|
|
173
|
+
graph: {
|
|
174
|
+
lineTension: props.chart.options.graph.lineTension || 0.01,
|
|
175
|
+
spanGaps: props.chart.options.graph.spanGaps || false,
|
|
176
|
+
showDataLabels: props.chart.options.graph.showDataLabels || false,
|
|
177
|
+
showMinorGridlines:
|
|
178
|
+
props.chart.options.graph.showMinorGridlines || false,
|
|
179
|
+
},
|
|
180
|
+
annotations: {
|
|
181
|
+
labelAnnotation: props.chart.options.annotations.labelAnnotation || {},
|
|
182
|
+
showAnnotations:
|
|
183
|
+
props.chart.options.annotations.showAnnotations ?? false,
|
|
184
|
+
controlAnnotation:
|
|
185
|
+
props.chart.options.annotations.controlAnnotation || false,
|
|
186
|
+
annotationsData: props.chart.options.annotations.annotationsData || [],
|
|
187
|
+
},
|
|
188
|
+
legend: {
|
|
189
|
+
display: props.chart.options.legend.display ?? true,
|
|
190
|
+
position: props.chart.options.legend.position || 'bottom',
|
|
191
|
+
align: props.chart.options.legend.align || 'center',
|
|
192
|
+
customLegend: props.chart.options.legend.customLegend,
|
|
193
|
+
},
|
|
194
|
+
chartOptions: {
|
|
195
|
+
showPoints: props.chart.options.chartOptions.showPoints ?? true,
|
|
196
|
+
enableZoom: props.chart.options.chartOptions.enableZoom || false,
|
|
197
|
+
enablePan: props.chart.options.chartOptions.enablePan || false,
|
|
198
|
+
showLine: props.chart.options.chartOptions.showLine ?? true,
|
|
199
|
+
closeOnOutsideClick:
|
|
200
|
+
props.chart.options.chartOptions.closeOnOutsideClick || false,
|
|
201
|
+
},
|
|
202
|
+
interactions: {
|
|
203
|
+
onLegendClick: props.chart.options.interactions.onLegendClick,
|
|
204
|
+
onPointHover: props.chart.options.interactions.onPointHover,
|
|
205
|
+
onPointUnhover: props.chart.options.interactions.onPointUnhover,
|
|
206
|
+
onAnimationComplete:
|
|
207
|
+
props.chart.options.interactions.onAnimationComplete,
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
return chart;
|
|
212
|
+
};
|
|
@@ -0,0 +1,192 @@
|
|
|
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
|
+
const isOnlyNumbers = (str) => /^-?\d*\.?\d+$/.test(str);
|
|
13
|
+
|
|
14
|
+
export const charCount = (chr, str) => {
|
|
15
|
+
let total = 0,
|
|
16
|
+
last_location = 0,
|
|
17
|
+
single_char = (chr + '')[0];
|
|
18
|
+
while ((last_location = str.indexOf(single_char, last_location) + 1)) {
|
|
19
|
+
total += 1;
|
|
20
|
+
}
|
|
21
|
+
return total;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const cleanNumStr = (str) => {
|
|
25
|
+
str += '';
|
|
26
|
+
let slashCount = charCount('/', str);
|
|
27
|
+
let spaceCount = charCount(' ', str) + charCount(' ', str);
|
|
28
|
+
let dotcount = charCount('.', str);
|
|
29
|
+
let commacount = charCount(',', str);
|
|
30
|
+
if (slashCount === 0 && spaceCount > 0) str = str.replace(/\s/g, '');
|
|
31
|
+
if (commacount > 1) str = str.replace(/,/g, '');
|
|
32
|
+
if (dotcount > 1) str = str.replace(/\./g, '');
|
|
33
|
+
commacount = charCount(',', str);
|
|
34
|
+
dotcount = charCount('.', str);
|
|
35
|
+
if (dotcount === 1 && commacount === 1) {
|
|
36
|
+
// One of each, make the rightmost act as decimal separator
|
|
37
|
+
if (str.indexOf(',') > str.indexOf('.')) {
|
|
38
|
+
str = str.replace('.', '');
|
|
39
|
+
str = str.replace(',', '.');
|
|
40
|
+
} else {
|
|
41
|
+
str = str.replace(',', '');
|
|
42
|
+
}
|
|
43
|
+
if (str.indexOf('.') === 0) {
|
|
44
|
+
str = 0 + str;
|
|
45
|
+
}
|
|
46
|
+
return str;
|
|
47
|
+
}
|
|
48
|
+
if (dotcount === 1 && commacount) str = str.replace(',', '');
|
|
49
|
+
if (!dotcount && commacount) str = str.replace(',', '.');
|
|
50
|
+
if (str.indexOf('.') === 0) {
|
|
51
|
+
str = 0 + str;
|
|
52
|
+
}
|
|
53
|
+
return str;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const numFraction = (str) => {
|
|
57
|
+
if (str instanceof Array || str === null || str === undefined) {
|
|
58
|
+
return str;
|
|
59
|
+
}
|
|
60
|
+
if (typeof str === 'string') {
|
|
61
|
+
str = str.trim(); //trailing whitespace causes InvalidParameter error in fraction.js
|
|
62
|
+
}
|
|
63
|
+
let result = str;
|
|
64
|
+
try {
|
|
65
|
+
result = Fraction(str);
|
|
66
|
+
} catch (e) {
|
|
67
|
+
//do nothing
|
|
68
|
+
}
|
|
69
|
+
return result.valueOf();
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const isValidNum = (input) => {
|
|
73
|
+
let result = false;
|
|
74
|
+
if (isEmptyString(input)) {
|
|
75
|
+
result = true;
|
|
76
|
+
} else {
|
|
77
|
+
if (
|
|
78
|
+
!(
|
|
79
|
+
isNull(input) ||
|
|
80
|
+
isUndefined(input) ||
|
|
81
|
+
isTrailingPeriodSeparator(input) ||
|
|
82
|
+
isTrailingCommaSeparator(input) ||
|
|
83
|
+
isArray(input) ||
|
|
84
|
+
isObject(input) ||
|
|
85
|
+
!isOnlyNumbers(input)
|
|
86
|
+
)
|
|
87
|
+
) {
|
|
88
|
+
let number;
|
|
89
|
+
const cleaned = cleanNumStr(input);
|
|
90
|
+
if (hasDivisor(cleaned)) {
|
|
91
|
+
//numFraction is slow, so only call it if needed
|
|
92
|
+
number = numFraction(cleaned);
|
|
93
|
+
} else {
|
|
94
|
+
number = parseFloat(cleaned);
|
|
95
|
+
}
|
|
96
|
+
if (!isNaN(number)) {
|
|
97
|
+
result = true;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const toNumber = (input, defaultValue, minimum) => {
|
|
105
|
+
let result;
|
|
106
|
+
defaultValue = defaultValue === undefined ? input : defaultValue;
|
|
107
|
+
if (
|
|
108
|
+
isNull(input) ||
|
|
109
|
+
isUndefined(input) ||
|
|
110
|
+
isEmptyString(input) ||
|
|
111
|
+
isTrailingPeriodSeparator(input) ||
|
|
112
|
+
isTrailingCommaSeparator(input) ||
|
|
113
|
+
isArray(input) ||
|
|
114
|
+
isObject(input)
|
|
115
|
+
) {
|
|
116
|
+
result = defaultValue;
|
|
117
|
+
} else {
|
|
118
|
+
let number;
|
|
119
|
+
const cleaned = cleanNumStr(input);
|
|
120
|
+
if (hasDivisor(cleaned)) {
|
|
121
|
+
//numFraction is slow, so only call it if needed
|
|
122
|
+
number = numFraction(cleaned);
|
|
123
|
+
} else {
|
|
124
|
+
number = parseFloat(cleaned);
|
|
125
|
+
}
|
|
126
|
+
if (isNaN(number)) {
|
|
127
|
+
result = defaultValue;
|
|
128
|
+
} else if (minimum && number < minimum) {
|
|
129
|
+
result = minimum;
|
|
130
|
+
} else {
|
|
131
|
+
result = number;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return result;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export const validNumber = (value) => {
|
|
138
|
+
return isValidNum(value);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export const toNum = (value) => {
|
|
142
|
+
const asNumber = toNumber(value);
|
|
143
|
+
return value === '' || isNaN(asNumber) ? '' : asNumber;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const isLessThanMax = (value, max) => {
|
|
147
|
+
return (
|
|
148
|
+
value === undefined || max === undefined || Number(value) < Number(max)
|
|
149
|
+
);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export const isGreaterThanMin = (value, min) => {
|
|
153
|
+
return (
|
|
154
|
+
value === undefined || min === undefined || Number(value) > Number(min)
|
|
155
|
+
);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Truncates a number to the specified decimal digits.
|
|
160
|
+
* Truncate refers to shaving digits off a number without rounding it.
|
|
161
|
+
* @param {Number} number
|
|
162
|
+
* @param {Number} digits
|
|
163
|
+
* @returns {Number}
|
|
164
|
+
*/
|
|
165
|
+
export const truncateDecimals = (number, digits) => {
|
|
166
|
+
const multiplier = 10 ** digits;
|
|
167
|
+
const adjustedNum = number * multiplier;
|
|
168
|
+
const truncatedNum = Math[adjustedNum < 0 ? 'ceil' : 'floor'](adjustedNum);
|
|
169
|
+
|
|
170
|
+
return truncatedNum / multiplier;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
*
|
|
175
|
+
* @param {'x'|'y'} axisType
|
|
176
|
+
* @param {number} [index] - axis index; optional, 0 by default
|
|
177
|
+
* @param {boolean} [hasMultiAxes] - optional, false by default
|
|
178
|
+
* @return {string} - e.g. x if chart has singular axes; x1, x2 - in case of multiple axes
|
|
179
|
+
*/
|
|
180
|
+
export const generateAxisId = (axisType, index = 0, hasMultiAxes = false) => {
|
|
181
|
+
const i = hasMultiAxes ? index + 1 : '';
|
|
182
|
+
return `${axisType}${i}`;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
Get axis type from a key string.
|
|
187
|
+
@param {string} string - The key string to extract from.
|
|
188
|
+
@returns {string} e.g. x1 => x
|
|
189
|
+
*/
|
|
190
|
+
export const getAxisTypeFromKey = (string) => {
|
|
191
|
+
return string.match(/[^0-9/]+/gi)[0];
|
|
192
|
+
};
|