@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,243 @@
|
|
|
1
|
+
import React, { useRef, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
BarElement,
|
|
4
|
+
CategoryScale,
|
|
5
|
+
Chart as ChartJS,
|
|
6
|
+
Filler,
|
|
7
|
+
Legend,
|
|
8
|
+
LinearScale,
|
|
9
|
+
LogarithmicScale,
|
|
10
|
+
Title,
|
|
11
|
+
Tooltip,
|
|
12
|
+
} from 'chart.js';
|
|
13
|
+
import { Bar } from 'react-chartjs-2';
|
|
14
|
+
import zoomPlugin from 'chartjs-plugin-zoom';
|
|
15
|
+
import dataLabelsPlugin from 'chartjs-plugin-datalabels';
|
|
16
|
+
import annotationPlugin from 'chartjs-plugin-annotation';
|
|
17
|
+
|
|
18
|
+
import styles from './bar-chart.module.less';
|
|
19
|
+
import { BarChartPropTypes, getDefaultProps } from './bar-chart-prop-types';
|
|
20
|
+
import getBarChartScales from './get-bar-chart-scales';
|
|
21
|
+
import getBarChartToolTips from './get-bar-chart-tooltips';
|
|
22
|
+
import getBarChartDataLabels from './get-bar-chart-data-labels';
|
|
23
|
+
import getAnnotation from '../../helpers/get-chart-annotation';
|
|
24
|
+
import {
|
|
25
|
+
getTitle,
|
|
26
|
+
isVertical,
|
|
27
|
+
setAnnotations,
|
|
28
|
+
generateRandomColor,
|
|
29
|
+
getClassName,
|
|
30
|
+
getPlugins,
|
|
31
|
+
getLegend,
|
|
32
|
+
setDefaultTheme,
|
|
33
|
+
} from '../../helpers/chart-utils';
|
|
34
|
+
import {
|
|
35
|
+
ALPHA_CHANEL,
|
|
36
|
+
ANIMATION_DURATION,
|
|
37
|
+
AUTO,
|
|
38
|
+
COLORS,
|
|
39
|
+
CUSTOM_LEGEND_PLUGIN_NAME,
|
|
40
|
+
BORDER_COLOR,
|
|
41
|
+
} from '../../helpers/chart-consts';
|
|
42
|
+
import {
|
|
43
|
+
AxisType,
|
|
44
|
+
ChartHoverMode,
|
|
45
|
+
ChartType,
|
|
46
|
+
PointStyle,
|
|
47
|
+
} from '../../helpers/enums';
|
|
48
|
+
|
|
49
|
+
ChartJS.register(
|
|
50
|
+
LinearScale,
|
|
51
|
+
CategoryScale,
|
|
52
|
+
LogarithmicScale,
|
|
53
|
+
BarElement,
|
|
54
|
+
Legend,
|
|
55
|
+
Tooltip,
|
|
56
|
+
Title,
|
|
57
|
+
Filler,
|
|
58
|
+
zoomPlugin,
|
|
59
|
+
dataLabelsPlugin,
|
|
60
|
+
annotationPlugin,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* this is a bar chart component
|
|
65
|
+
* @param {import('./bar-chart.interface').IBarChartProps} props
|
|
66
|
+
*/
|
|
67
|
+
const BarChart = (props) => {
|
|
68
|
+
setDefaultTheme();
|
|
69
|
+
const chartRef = useRef(null);
|
|
70
|
+
const [pointHover, setPointHover] = useState(false);
|
|
71
|
+
const chart = getDefaultProps(props);
|
|
72
|
+
const { options, testId } = chart;
|
|
73
|
+
const { annotations, chartStyling, interactions, graph } = chart.options;
|
|
74
|
+
|
|
75
|
+
const [visibleAnnotationsIndices, setVisibleAnnotationsIndices] = useState(
|
|
76
|
+
setAnnotations(annotations.annotationsData),
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const generateBarChartDatasets = (datasets) => {
|
|
80
|
+
const barDatasetsCopy = [...datasets];
|
|
81
|
+
|
|
82
|
+
// Add annotations to dataset to have them appear in legend.
|
|
83
|
+
if (
|
|
84
|
+
annotations.controlAnnotation &&
|
|
85
|
+
annotations.showAnnotations &&
|
|
86
|
+
annotations.annotationsData?.length
|
|
87
|
+
) {
|
|
88
|
+
annotations.annotationsData.forEach((annotation, index) => {
|
|
89
|
+
barDatasetsCopy.push({
|
|
90
|
+
isAnnotation: true,
|
|
91
|
+
label: annotation.label,
|
|
92
|
+
annotationIndex: index,
|
|
93
|
+
backgroundColor: annotation.color || COLORS[index],
|
|
94
|
+
borderColor: annotation.color || COLORS[index],
|
|
95
|
+
data: [],
|
|
96
|
+
type: ChartType.Bar,
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const generatedDatasets = barDatasetsCopy.map((barDataset, index) => {
|
|
102
|
+
const colorSchema = COLORS;
|
|
103
|
+
const colors = barDataset.data.map((_, i) => {
|
|
104
|
+
const colorSelectionIndex = datasets.length > 1 ? index : i;
|
|
105
|
+
return colorSchema[colorSelectionIndex] || generateRandomColor(COLORS);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const backgroundColors =
|
|
109
|
+
barDataset.backgroundColor ||
|
|
110
|
+
colors.map((color) => color + ALPHA_CHANEL);
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
...barDataset,
|
|
114
|
+
borderWidth: parseFloat(barDataset.borderWidth) || '1',
|
|
115
|
+
borderColor: barDataset.borderColor || colors,
|
|
116
|
+
backgroundColor: backgroundColors,
|
|
117
|
+
};
|
|
118
|
+
});
|
|
119
|
+
return generatedDatasets;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const generatedDatasets = generateBarChartDatasets(chart.data.datasets);
|
|
123
|
+
|
|
124
|
+
const legendClick = (e, legendItem) => {
|
|
125
|
+
const index = legendItem.datasetIndex;
|
|
126
|
+
const chartInstance = chartRef.current;
|
|
127
|
+
const { datasets } = chartInstance.data;
|
|
128
|
+
const dataset = datasets[index];
|
|
129
|
+
const meta = chartInstance.getDatasetMeta(index);
|
|
130
|
+
meta.hidden = meta.hidden === null ? !dataset.hidden : null;
|
|
131
|
+
|
|
132
|
+
if (annotations.controlAnnotation && dataset.isAnnotation) {
|
|
133
|
+
const { annotationIndex } = dataset;
|
|
134
|
+
if (visibleAnnotationsIndices.includes(annotationIndex)) {
|
|
135
|
+
setVisibleAnnotationsIndices(
|
|
136
|
+
visibleAnnotationsIndices.filter((ann) => ann !== annotationIndex),
|
|
137
|
+
);
|
|
138
|
+
} else {
|
|
139
|
+
setVisibleAnnotationsIndices([
|
|
140
|
+
...visibleAnnotationsIndices,
|
|
141
|
+
annotationIndex,
|
|
142
|
+
]);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Show/hide entire display group
|
|
147
|
+
if (dataset.displayGroup) {
|
|
148
|
+
datasets.forEach((ds, ix) => {
|
|
149
|
+
if (ds.displayGroup !== dataset.displayGroup) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
chartInstance.getDatasetMeta(ix).hidden = meta.hidden;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (interactions.onLegendClick) {
|
|
157
|
+
interactions.onLegendClick(legendItem?.text, legendItem.hidden);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
chartInstance.update();
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const onClick = (evt, elements, chartInstance) => {
|
|
164
|
+
chartInstance.resetZoom();
|
|
165
|
+
// TODO: Restore redux-logic for zoom
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const onHover = (evt, hoveredItems, chartInstance) => {
|
|
169
|
+
if (pointHover && !hoveredItems?.length) {
|
|
170
|
+
setPointHover(false);
|
|
171
|
+
if (interactions.onBarUnhover) {
|
|
172
|
+
interactions.onBarUnhover(evt);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (!pointHover && hoveredItems?.length) {
|
|
176
|
+
setPointHover(true);
|
|
177
|
+
if (interactions.onBarHover) {
|
|
178
|
+
const { index, datasetIndex } = hoveredItems[0];
|
|
179
|
+
const generatedDataset = generatedDatasets;
|
|
180
|
+
interactions.onBarHover(evt, datasetIndex, index, generatedDataset);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<div
|
|
187
|
+
className={getClassName(chartStyling, styles)}
|
|
188
|
+
style={{
|
|
189
|
+
width: chartStyling.width || AUTO,
|
|
190
|
+
height: chartStyling.height || AUTO,
|
|
191
|
+
}}
|
|
192
|
+
data-testid={testId}
|
|
193
|
+
>
|
|
194
|
+
<Bar
|
|
195
|
+
ref={chartRef}
|
|
196
|
+
data={{
|
|
197
|
+
labels: chart.data.labels.length ? chart.data.labels : [''],
|
|
198
|
+
datasets: generatedDatasets,
|
|
199
|
+
}}
|
|
200
|
+
options={{
|
|
201
|
+
onClick,
|
|
202
|
+
onHover,
|
|
203
|
+
indexAxis: isVertical(options.direction) ? AxisType.X : AxisType.Y,
|
|
204
|
+
maintainAspectRatio: chartStyling.maintainAspectRatio,
|
|
205
|
+
animation: chartStyling.performanceMode
|
|
206
|
+
? false
|
|
207
|
+
: {
|
|
208
|
+
duration: ANIMATION_DURATION.FAST,
|
|
209
|
+
},
|
|
210
|
+
hover: {
|
|
211
|
+
mode: ChartHoverMode.Nearest,
|
|
212
|
+
intersect: true,
|
|
213
|
+
},
|
|
214
|
+
elements: {
|
|
215
|
+
bar: { pointStyle: PointStyle.Circle },
|
|
216
|
+
},
|
|
217
|
+
scales: getBarChartScales(chart),
|
|
218
|
+
plugins: {
|
|
219
|
+
title: getTitle(options),
|
|
220
|
+
datalabels: getBarChartDataLabels(options),
|
|
221
|
+
annotation: getAnnotation(options, {
|
|
222
|
+
showAnnotationLineIndex: visibleAnnotationsIndices,
|
|
223
|
+
}),
|
|
224
|
+
tooltip: getBarChartToolTips(options),
|
|
225
|
+
legend: getLegend(options, legendClick),
|
|
226
|
+
[CUSTOM_LEGEND_PLUGIN_NAME]: options.legend.customLegend
|
|
227
|
+
.customLegendPlugin && {
|
|
228
|
+
containerID: options.legend.customLegend.customLegendContainerID,
|
|
229
|
+
},
|
|
230
|
+
chartAreaBorder: {
|
|
231
|
+
borderColor: BORDER_COLOR,
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
}}
|
|
235
|
+
plugins={getPlugins(graph, options.legend)}
|
|
236
|
+
/>
|
|
237
|
+
</div>
|
|
238
|
+
);
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
BarChart.propTypes = BarChartPropTypes;
|
|
242
|
+
|
|
243
|
+
export { BarChart };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
html[data-theme='dark'] .chart canvas {
|
|
2
|
+
// Flip chart colors if dark mode enabled
|
|
3
|
+
filter: invert(1) hue-rotate(180deg);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.chart {
|
|
7
|
+
border: 1px solid rgba(255, 255, 255, 0);
|
|
8
|
+
padding-top: 10px;
|
|
9
|
+
position: relative;
|
|
10
|
+
|
|
11
|
+
canvas {
|
|
12
|
+
width: 100% !important; // Fix for resizing bug
|
|
13
|
+
height: 100% !important; // Remove if stretched when maintainAspectRatio=true
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
&.fixedHeight {
|
|
17
|
+
display: flex;
|
|
18
|
+
align-items: flex-start;
|
|
19
|
+
justify-content: flex-start;
|
|
20
|
+
height: auto;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
&.stretchHeight {
|
|
24
|
+
display: flex;
|
|
25
|
+
align-items: stretch;
|
|
26
|
+
justify-content: stretch;
|
|
27
|
+
height: 100%;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
&:focus {
|
|
31
|
+
border: 1px solid #85b7d9;
|
|
32
|
+
outline: none; // Remove dotted outline on FF
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
&::-moz-focus-inner {
|
|
36
|
+
border: 0; // Remove dotted outline on FF
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.zoomForm {
|
|
41
|
+
position: absolute;
|
|
42
|
+
display: flex;
|
|
43
|
+
align-items: center;
|
|
44
|
+
justify-content: center;
|
|
45
|
+
top: 0;
|
|
46
|
+
right: 0;
|
|
47
|
+
|
|
48
|
+
.zoomReset {
|
|
49
|
+
margin-left: 10px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.help {
|
|
53
|
+
margin-left: 5px;
|
|
54
|
+
line-height: 0; // Strip whitespace from icon
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.autoWeight {
|
|
59
|
+
width: auto;
|
|
60
|
+
height: auto;
|
|
61
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { AlignOptions } from '../../helpers/enums';
|
|
2
|
+
import { AUTO } from '../../helpers/chart-consts';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {import('./bar-chart.interface').IBarChartOptions} options - bar chart options object
|
|
6
|
+
*/
|
|
7
|
+
const getBarChartDataLabels = (options) => {
|
|
8
|
+
const { beginAtZero, reverse } = options.additionalAxesOptions;
|
|
9
|
+
|
|
10
|
+
const formatterCallback = (value, context) => {
|
|
11
|
+
const { dataset, dataIndex } = context;
|
|
12
|
+
const dataElement = dataset.data[dataIndex];
|
|
13
|
+
|
|
14
|
+
let dataLabel = '';
|
|
15
|
+
if (typeof dataElement === 'number') {
|
|
16
|
+
dataLabel = Number.isInteger(dataElement)
|
|
17
|
+
? dataElement
|
|
18
|
+
: dataElement.toFixed(2);
|
|
19
|
+
}
|
|
20
|
+
if (Array.isArray(dataElement)) {
|
|
21
|
+
dataLabel = dataElement.reduce((acc, curr, i) => {
|
|
22
|
+
return i === 0 ? `${acc} ${curr}` : `${acc}, ${curr}`;
|
|
23
|
+
}, '');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return dataLabel;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const beginAtNotZeroAlign = reverse ? AlignOptions.End : AlignOptions.Start;
|
|
30
|
+
const beginAtNotZeroAnchor = reverse ? AlignOptions.Start : AlignOptions.End;
|
|
31
|
+
|
|
32
|
+
return options.graph.showDataLabels
|
|
33
|
+
? {
|
|
34
|
+
display: AUTO,
|
|
35
|
+
align: beginAtZero ? AlignOptions.Center : beginAtNotZeroAlign,
|
|
36
|
+
anchor: beginAtZero ? AlignOptions.Center : beginAtNotZeroAnchor,
|
|
37
|
+
formatter: formatterCallback,
|
|
38
|
+
}
|
|
39
|
+
: { display: false };
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default getBarChartDataLabels;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { AxisType, ScaleType } from '../../helpers/enums';
|
|
2
|
+
import {
|
|
3
|
+
getAxisPosition,
|
|
4
|
+
isVertical,
|
|
5
|
+
generateRandomColor,
|
|
6
|
+
} from '../../helpers/chart-utils';
|
|
7
|
+
import { COLORS, LOGARITHMIC_STEPS } from '../../helpers/chart-consts';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {import('./bar-chart.interface').IBarChartData} chart - chart data
|
|
11
|
+
* @param {'x'|'y'} axisType
|
|
12
|
+
* @param {import('./bar-chart.interface').IBarChartAxes} [currentScales]
|
|
13
|
+
*/
|
|
14
|
+
const getBarChartAxis = (chart, axisType, currentScales) => {
|
|
15
|
+
const isDirectionVertical = isVertical(chart.options.direction);
|
|
16
|
+
|
|
17
|
+
const axisData = currentScales || chart.options.axes[axisType][0];
|
|
18
|
+
const isDirectionCompatibleWithAxisType =
|
|
19
|
+
(isDirectionVertical && axisType === AxisType.Y) ||
|
|
20
|
+
(!isDirectionVertical && axisType === AxisType.X);
|
|
21
|
+
|
|
22
|
+
const grid = axisData?.gridLines || {};
|
|
23
|
+
|
|
24
|
+
const getScaleType = () => {
|
|
25
|
+
const scaleType = chart.data.labels ? ScaleType.Category : ScaleType.Linear;
|
|
26
|
+
const axisWithScale = isDirectionVertical ? AxisType.X : AxisType.Y;
|
|
27
|
+
return axisType === axisWithScale
|
|
28
|
+
? scaleType
|
|
29
|
+
: chart.options.additionalAxesOptions.chartScaleType;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const getReverse = () => {
|
|
33
|
+
const axisWithReverse = isDirectionVertical ? AxisType.Y : AxisType.X;
|
|
34
|
+
return axisType === axisWithReverse
|
|
35
|
+
? chart.options.additionalAxesOptions.reverse
|
|
36
|
+
: false;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const getTicks = () => {
|
|
40
|
+
const areLogarithmicTicks =
|
|
41
|
+
isDirectionCompatibleWithAxisType &&
|
|
42
|
+
chart.options.additionalAxesOptions.chartScaleType ===
|
|
43
|
+
ScaleType.Logarithmic;
|
|
44
|
+
|
|
45
|
+
if (areLogarithmicTicks) {
|
|
46
|
+
return {
|
|
47
|
+
callback: (tick) => {
|
|
48
|
+
return LOGARITHMIC_STEPS.includes(tick) ? tick.toLocaleString() : '';
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
} else {
|
|
52
|
+
return {
|
|
53
|
+
stepSize:
|
|
54
|
+
axisData.stepSize || chart.options.additionalAxesOptions.stepSize,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
type: getScaleType(),
|
|
61
|
+
position: axisData.position,
|
|
62
|
+
beginAtZero: chart.options.additionalAxesOptions.beginAtZero,
|
|
63
|
+
reverse: getReverse(),
|
|
64
|
+
suggestedMax: chart.options?.additionalAxesOptions?.suggestedMax,
|
|
65
|
+
suggestedMin: chart.options?.additionalAxesOptions?.suggestedMin,
|
|
66
|
+
min: isDirectionCompatibleWithAxisType
|
|
67
|
+
? chart.options?.additionalAxesOptions?.min
|
|
68
|
+
: undefined,
|
|
69
|
+
max: isDirectionCompatibleWithAxisType
|
|
70
|
+
? chart.options?.additionalAxesOptions?.max
|
|
71
|
+
: undefined,
|
|
72
|
+
title: {
|
|
73
|
+
display: axisData.label?.length || axisData.unit?.length,
|
|
74
|
+
text: axisData.label || axisData.unit,
|
|
75
|
+
padding: 0,
|
|
76
|
+
},
|
|
77
|
+
ticks: getTicks(),
|
|
78
|
+
grid: {
|
|
79
|
+
...grid,
|
|
80
|
+
},
|
|
81
|
+
stacked: chart.options.additionalAxesOptions.stacked,
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @param {import('./bar-chart.interface').IBarChartData} chart - chart data
|
|
87
|
+
* @param {'x'|'y'} axisType
|
|
88
|
+
*/
|
|
89
|
+
const getBarChartAxes = (chart, axisType) => {
|
|
90
|
+
const axesData = chart.options.axes[axisType];
|
|
91
|
+
const axes = axesData.reduce((acc, curr, i) => {
|
|
92
|
+
const axisData = curr;
|
|
93
|
+
const color = curr.color || COLORS[i] || generateRandomColor(COLORS);
|
|
94
|
+
axisData.color = color;
|
|
95
|
+
axisData.position = curr.position || getAxisPosition(axisType, i);
|
|
96
|
+
|
|
97
|
+
const axis = getBarChartAxis(chart, axisType, axisData);
|
|
98
|
+
return { ...acc, [axisType + (i + 1)]: axis };
|
|
99
|
+
}, {});
|
|
100
|
+
return axes;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @param {import('./bar-chart.interface').IBarChartData} chart - chart data
|
|
105
|
+
*/
|
|
106
|
+
const getBarChartScales = (chart) => {
|
|
107
|
+
const hasMultipleXAxes = chart.options.axes.x?.length > 1;
|
|
108
|
+
const hasMultipleYAxes = chart.options.axes.y?.length > 1;
|
|
109
|
+
|
|
110
|
+
const xAxesScales = hasMultipleXAxes
|
|
111
|
+
? getBarChartAxes(chart, AxisType.X)
|
|
112
|
+
: { x: getBarChartAxis(chart, AxisType.X) };
|
|
113
|
+
const yAxesScales = hasMultipleYAxes
|
|
114
|
+
? getBarChartAxes(chart, AxisType.Y)
|
|
115
|
+
: { y: getBarChartAxis(chart, AxisType.Y) };
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
...xAxesScales,
|
|
119
|
+
...yAxesScales,
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export default getBarChartScales;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterLabelCallback,
|
|
3
|
+
getTooltipLabel,
|
|
4
|
+
isVertical,
|
|
5
|
+
} from '../../helpers/chart-utils';
|
|
6
|
+
import { ChartHoverMode, TooltipLabel } from '../../helpers/enums';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {import('./bar-chart.interface').IBarChartOptions} options - bar chart options object
|
|
10
|
+
*/
|
|
11
|
+
const getBarChartToolTips = (options) => {
|
|
12
|
+
const getTooltipLabels = (dataset) => {
|
|
13
|
+
const isDirectionVertical = isVertical(options.direction);
|
|
14
|
+
const { x, y } = options.axes;
|
|
15
|
+
|
|
16
|
+
const xIndex = dataset.xAxisID?.length > 1 ? dataset.xAxisID[1] - 1 : 0;
|
|
17
|
+
const yIndex = dataset.yAxisID?.length > 1 ? dataset.yAxisID[1] - 1 : 0;
|
|
18
|
+
const xAxis = isDirectionVertical ? x[xIndex] : y[yIndex];
|
|
19
|
+
const yAxis = isDirectionVertical ? y[yIndex] : x[xIndex];
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
titleAxisLabel: xAxis?.label || '',
|
|
23
|
+
valueAxisLabel: yAxis?.label || '',
|
|
24
|
+
titleLabel: TooltipLabel.X,
|
|
25
|
+
valueLabel: TooltipLabel.Y,
|
|
26
|
+
titleUnit: xAxis?.unit || '',
|
|
27
|
+
valueUnit: yAxis?.unit || '',
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const titleCallback = (tooltipItem, data) => {
|
|
32
|
+
const barLabel = tooltipItem[0]?.label || '';
|
|
33
|
+
const labels = getTooltipLabels(tooltipItem[0].dataset);
|
|
34
|
+
const { titleAxisLabel, titleUnit } = labels;
|
|
35
|
+
const axisLabel =
|
|
36
|
+
!titleAxisLabel && !titleUnit ? '' : `${titleAxisLabel || titleUnit}: `;
|
|
37
|
+
return axisLabel + barLabel;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const labelCallback = (tooltipItem) => {
|
|
41
|
+
const { showLabelsInTooltips } = options.tooltip;
|
|
42
|
+
let label = tooltipItem.dataset?.label || '';
|
|
43
|
+
|
|
44
|
+
const labels = getTooltipLabels(tooltipItem.dataset);
|
|
45
|
+
const { valueUnit, valueAxisLabel } = labels;
|
|
46
|
+
|
|
47
|
+
const getTooltipItemValue = () => {
|
|
48
|
+
const { formattedValue } = tooltipItem;
|
|
49
|
+
const labelNumber = Number(formattedValue);
|
|
50
|
+
|
|
51
|
+
let labelNumberFormatted;
|
|
52
|
+
if (Math.abs(labelNumber) < 1) {
|
|
53
|
+
labelNumberFormatted = labelNumber.toPrecision(3);
|
|
54
|
+
} else {
|
|
55
|
+
labelNumberFormatted = Number.isInteger(labelNumber)
|
|
56
|
+
? labelNumber
|
|
57
|
+
: labelNumber.toFixed(3);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let tooltipItemValue = '';
|
|
61
|
+
if (typeof tooltipItem.raw === 'number') {
|
|
62
|
+
tooltipItemValue = labelNumberFormatted;
|
|
63
|
+
}
|
|
64
|
+
if (Array.isArray(tooltipItem.raw)) {
|
|
65
|
+
tooltipItemValue = tooltipItem.raw.reduce((acc, curr, i) => {
|
|
66
|
+
return i === 0 ? `${acc} ${curr}` : `${acc}, ${curr}`;
|
|
67
|
+
}, '');
|
|
68
|
+
}
|
|
69
|
+
if (typeof tooltipItem.raw === 'object') {
|
|
70
|
+
tooltipItemValue = formattedValue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return tooltipItemValue;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const hideValueAxisLabel =
|
|
77
|
+
label === valueAxisLabel || valueAxisLabel.includes(label);
|
|
78
|
+
|
|
79
|
+
const tooltipItemValue = getTooltipItemValue();
|
|
80
|
+
const unit = valueUnit ? `[${valueUnit}] ` : '';
|
|
81
|
+
const valAxisLabel = hideValueAxisLabel ? '' : valueAxisLabel;
|
|
82
|
+
const tooltipLabel = getTooltipLabel(tooltipItem, showLabelsInTooltips);
|
|
83
|
+
|
|
84
|
+
return `${label}: ${tooltipItemValue} ${unit}${valAxisLabel}${tooltipLabel}`;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
enabled: options.tooltip.tooltips,
|
|
89
|
+
mode: ChartHoverMode.Nearest,
|
|
90
|
+
intersect: true,
|
|
91
|
+
padding: 8,
|
|
92
|
+
callbacks: {
|
|
93
|
+
title: titleCallback,
|
|
94
|
+
label: labelCallback,
|
|
95
|
+
afterLabel: afterLabelCallback,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export default getBarChartToolTips;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { produce } from 'immer';
|
|
2
|
+
import {
|
|
3
|
+
isGreaterThanMin,
|
|
4
|
+
isLessThanMax,
|
|
5
|
+
validNumber,
|
|
6
|
+
} from '../../line-chart/line-chart-utils';
|
|
7
|
+
|
|
8
|
+
export const actionTypes = Object.freeze({
|
|
9
|
+
reset: 'reset',
|
|
10
|
+
valueUpdated: 'valueUpdated',
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Initialize local component form state for a custom loads density table
|
|
15
|
+
*
|
|
16
|
+
* @param {Object} args
|
|
17
|
+
* @param {Object} args.initialAxesRanges
|
|
18
|
+
* @param {Array} [args.axes]
|
|
19
|
+
* @returns {Object} formState
|
|
20
|
+
*/
|
|
21
|
+
export const initializeFormState = ({ initialAxesRanges, axes = [] }) => {
|
|
22
|
+
return initialAxesRanges.map((initialAxisRange) => {
|
|
23
|
+
const currentAxisRange = axes.find((a) => a.id === initialAxisRange.id);
|
|
24
|
+
return {
|
|
25
|
+
id: initialAxisRange.id,
|
|
26
|
+
min: currentAxisRange?.min ?? initialAxisRange?.min,
|
|
27
|
+
max: currentAxisRange?.max ?? initialAxisRange?.max,
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @typedef {Object} Action
|
|
34
|
+
* @property {String} type Action type
|
|
35
|
+
* @property {Object} [payload] Action payload (optional)
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
const isEmptyString = (value) => value === '';
|
|
39
|
+
|
|
40
|
+
export const validateAxes = (formState) => {
|
|
41
|
+
return formState.reduce(
|
|
42
|
+
(acc, { id, min, max }) => {
|
|
43
|
+
return produce(acc, (draft) => {
|
|
44
|
+
const errors = {
|
|
45
|
+
min: [
|
|
46
|
+
...(isEmptyString(min) ? ['Must have a value'] : []),
|
|
47
|
+
...(!validNumber(min) ? ['Must be a number'] : []),
|
|
48
|
+
...(!isLessThanMax(min, max) ? ['Must be less than max'] : []),
|
|
49
|
+
],
|
|
50
|
+
max: [
|
|
51
|
+
...(isEmptyString(max) ? ['Must have a value'] : []),
|
|
52
|
+
...(!validNumber(max) ? ['Must be a number'] : []),
|
|
53
|
+
...(!isGreaterThanMin(max, min)
|
|
54
|
+
? ['Must be greater than min']
|
|
55
|
+
: []),
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
draft.errors.push({
|
|
59
|
+
id,
|
|
60
|
+
...errors,
|
|
61
|
+
});
|
|
62
|
+
draft.valid = !(
|
|
63
|
+
!acc.valid ||
|
|
64
|
+
!!errors.min.length ||
|
|
65
|
+
!!errors.max.length
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
{ errors: [], valid: true },
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Local component form state reducer for a axes options form
|
|
75
|
+
*
|
|
76
|
+
* @param {Object} state Local component form state
|
|
77
|
+
* @param {Action} action Action with type and payload
|
|
78
|
+
* @returns {Object} FormState
|
|
79
|
+
*/
|
|
80
|
+
export const reducer = (state, action) => {
|
|
81
|
+
switch (action.type) {
|
|
82
|
+
case actionTypes.reset: {
|
|
83
|
+
const { initialAxesRanges } = action.payload;
|
|
84
|
+
return initializeFormState({ initialAxesRanges });
|
|
85
|
+
}
|
|
86
|
+
case actionTypes.valueUpdated:
|
|
87
|
+
return produce(state, (draft) => {
|
|
88
|
+
const { name, value, id } = action.payload;
|
|
89
|
+
const axis = draft.find((a) => a.id === id);
|
|
90
|
+
axis[name] = value;
|
|
91
|
+
});
|
|
92
|
+
default:
|
|
93
|
+
return state;
|
|
94
|
+
}
|
|
95
|
+
};
|