@gravity-ui/charts 1.27.5 → 1.28.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/dist/cjs/components/AxisX/AxisX.js +13 -5
- package/dist/cjs/components/AxisX/prepare-axis-data.js +8 -1
- package/dist/cjs/components/AxisY/AxisY.js +13 -5
- package/dist/cjs/components/AxisY/prepare-axis-data.js +8 -1
- package/dist/cjs/i18n/keysets/en.json +2 -1
- package/dist/cjs/i18n/keysets/ru.json +2 -1
- package/dist/cjs/types/chart/axis.d.ts +6 -2
- package/dist/cjs/utils/chart/axis/common.js +3 -2
- package/dist/cjs/validation/validate-axes.js +13 -3
- package/dist/esm/components/AxisX/AxisX.js +13 -5
- package/dist/esm/components/AxisX/prepare-axis-data.js +8 -1
- package/dist/esm/components/AxisY/AxisY.js +13 -5
- package/dist/esm/components/AxisY/prepare-axis-data.js +8 -1
- package/dist/esm/i18n/keysets/en.json +2 -1
- package/dist/esm/i18n/keysets/ru.json +2 -1
- package/dist/esm/types/chart/axis.d.ts +6 -2
- package/dist/esm/utils/chart/axis/common.js +3 -2
- package/dist/esm/validation/validate-axes.js +13 -3
- package/package.json +1 -1
|
@@ -11,20 +11,20 @@ export const AxisX = (props) => {
|
|
|
11
11
|
const htmlLabels = preparedAxisData.ticks.map((d) => d.htmlLabel).filter(Boolean);
|
|
12
12
|
React.useEffect(() => {
|
|
13
13
|
if (!ref.current) {
|
|
14
|
-
return;
|
|
14
|
+
return () => { };
|
|
15
15
|
}
|
|
16
16
|
const svgElement = select(ref.current);
|
|
17
17
|
svgElement.selectAll('*').remove();
|
|
18
18
|
let plotBeforeContainer = null;
|
|
19
19
|
let plotAfterContainer = null;
|
|
20
20
|
const plotDataAttr = 'data-plot-x';
|
|
21
|
+
const plotBandDataAttr = `data-plot-x-band-${preparedAxisData.id}`;
|
|
22
|
+
const plotLineDataAttr = `data-plot-x-line-${preparedAxisData.id}`;
|
|
21
23
|
if (plotBeforeRef === null || plotBeforeRef === void 0 ? void 0 : plotBeforeRef.current) {
|
|
22
24
|
plotBeforeContainer = select(plotBeforeRef.current);
|
|
23
|
-
plotBeforeContainer.selectAll(`[${plotDataAttr}]`).remove();
|
|
24
25
|
}
|
|
25
26
|
if (plotAfterRef === null || plotAfterRef === void 0 ? void 0 : plotAfterRef.current) {
|
|
26
27
|
plotAfterContainer = select(plotAfterRef.current);
|
|
27
|
-
plotAfterContainer.selectAll(`[${plotDataAttr}]`).remove();
|
|
28
28
|
}
|
|
29
29
|
if (preparedAxisData.title) {
|
|
30
30
|
svgElement
|
|
@@ -93,7 +93,6 @@ export const AxisX = (props) => {
|
|
|
93
93
|
}
|
|
94
94
|
});
|
|
95
95
|
if (preparedAxisData.plotBands.length > 0) {
|
|
96
|
-
const plotBandDataAttr = `data-plot-x-band-${preparedAxisData.id}`;
|
|
97
96
|
const setPlotBands = (plotContainer, plotBands) => {
|
|
98
97
|
if (!plotContainer || !plotBands.length) {
|
|
99
98
|
return;
|
|
@@ -134,7 +133,6 @@ export const AxisX = (props) => {
|
|
|
134
133
|
setPlotBands(plotAfterContainer, preparedAxisData.plotBands.filter((item) => item.layerPlacement === 'after'));
|
|
135
134
|
}
|
|
136
135
|
if (preparedAxisData.plotLines.length > 0) {
|
|
137
|
-
const plotLineDataAttr = `data-plot-x-line-${preparedAxisData.id}`;
|
|
138
136
|
const setPlotLines = (plotContainer, plotLines) => {
|
|
139
137
|
if (!plotContainer || !plotLines.length) {
|
|
140
138
|
return;
|
|
@@ -175,6 +173,16 @@ export const AxisX = (props) => {
|
|
|
175
173
|
setPlotLines(plotBeforeContainer, preparedAxisData.plotLines.filter((item) => item.layerPlacement === 'before'));
|
|
176
174
|
setPlotLines(plotAfterContainer, preparedAxisData.plotLines.filter((item) => item.layerPlacement === 'after'));
|
|
177
175
|
}
|
|
176
|
+
return () => {
|
|
177
|
+
if (plotBeforeContainer) {
|
|
178
|
+
plotBeforeContainer.selectAll(`[${plotBandDataAttr}]`).remove();
|
|
179
|
+
plotBeforeContainer.selectAll(`[${plotLineDataAttr}]`).remove();
|
|
180
|
+
}
|
|
181
|
+
if (plotAfterContainer) {
|
|
182
|
+
plotAfterContainer.selectAll(`[${plotBandDataAttr}]`).remove();
|
|
183
|
+
plotAfterContainer.selectAll(`[${plotLineDataAttr}]`).remove();
|
|
184
|
+
}
|
|
185
|
+
};
|
|
178
186
|
}, [lineGenerator, plotAfterRef, plotBeforeRef, preparedAxisData]);
|
|
179
187
|
return (React.createElement(React.Fragment, null,
|
|
180
188
|
React.createElement(HtmlLayer, { preparedData: { htmlElements: htmlLabels }, htmlLayout: htmlLayout }),
|
|
@@ -244,11 +244,15 @@ export async function prepareXAxisData({ axis, yAxis, scale, boundsWidth, bounds
|
|
|
244
244
|
const labelSize = plotBand.label.text
|
|
245
245
|
? await getPlotLabelSize(plotBand.label.text)
|
|
246
246
|
: null;
|
|
247
|
+
const plotBandWidth = Math.min(endPos, axisWidth);
|
|
248
|
+
if (plotBandWidth < 0) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
247
251
|
plotBands.push({
|
|
248
252
|
layerPlacement: plotBand.layerPlacement,
|
|
249
253
|
x: Math.max(0, startPos),
|
|
250
254
|
y: 0,
|
|
251
|
-
width:
|
|
255
|
+
width: plotBandWidth,
|
|
252
256
|
height: axisHeight,
|
|
253
257
|
color: plotBand.color,
|
|
254
258
|
opacity: plotBand.opacity,
|
|
@@ -268,6 +272,9 @@ export async function prepareXAxisData({ axis, yAxis, scale, boundsWidth, bounds
|
|
|
268
272
|
const plotLine = axis.plotLines[i];
|
|
269
273
|
const axisScale = scale;
|
|
270
274
|
const plotLineValue = Number(axisScale(plotLine.value));
|
|
275
|
+
if (plotLineValue < 0 || plotLineValue > boundsWidth) {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
271
278
|
const points = [
|
|
272
279
|
[plotLineValue, 0],
|
|
273
280
|
[plotLineValue, axisHeight],
|
|
@@ -17,20 +17,20 @@ export const AxisY = (props) => {
|
|
|
17
17
|
React.useEffect(() => {
|
|
18
18
|
var _a;
|
|
19
19
|
if (!ref.current) {
|
|
20
|
-
return;
|
|
20
|
+
return () => { };
|
|
21
21
|
}
|
|
22
22
|
const svgElement = select(ref.current);
|
|
23
23
|
svgElement.selectAll('*').remove();
|
|
24
24
|
let plotBeforeContainer = null;
|
|
25
25
|
let plotAfterContainer = null;
|
|
26
26
|
const plotDataAttr = 'data-plot-y';
|
|
27
|
+
const plotBandDataAttr = `data-plot-y-band-${preparedAxisData.id}`;
|
|
28
|
+
const plotLineDataAttr = `data-plot-y-line-${preparedAxisData.id}`;
|
|
27
29
|
if (plotBeforeRef === null || plotBeforeRef === void 0 ? void 0 : plotBeforeRef.current) {
|
|
28
30
|
plotBeforeContainer = select(plotBeforeRef.current);
|
|
29
|
-
plotBeforeContainer.selectAll(`[${plotDataAttr}]`).remove();
|
|
30
31
|
}
|
|
31
32
|
if (plotAfterRef === null || plotAfterRef === void 0 ? void 0 : plotAfterRef.current) {
|
|
32
33
|
plotAfterContainer = select(plotAfterRef.current);
|
|
33
|
-
plotAfterContainer.selectAll(`[${plotDataAttr}]`).remove();
|
|
34
34
|
}
|
|
35
35
|
if (((_a = preparedAxisData.title) === null || _a === void 0 ? void 0 : _a.html) === false) {
|
|
36
36
|
svgElement
|
|
@@ -99,7 +99,6 @@ export const AxisY = (props) => {
|
|
|
99
99
|
}
|
|
100
100
|
});
|
|
101
101
|
if (preparedAxisData.plotBands.length > 0) {
|
|
102
|
-
const plotBandDataAttr = `data-plot-y-band-${preparedAxisData.id}`;
|
|
103
102
|
const setPlotBands = (plotContainer, plotBands) => {
|
|
104
103
|
if (!plotContainer || !plotBands.length) {
|
|
105
104
|
return;
|
|
@@ -140,7 +139,6 @@ export const AxisY = (props) => {
|
|
|
140
139
|
setPlotBands(plotAfterContainer, preparedAxisData.plotBands.filter((item) => item.layerPlacement === 'after'));
|
|
141
140
|
}
|
|
142
141
|
if (preparedAxisData.plotLines.length > 0) {
|
|
143
|
-
const plotLineDataAttr = `data-plot-y-line-${preparedAxisData.id}`;
|
|
144
142
|
const setPlotLines = (plotContainer, plotLines) => {
|
|
145
143
|
if (!plotContainer || !plotLines.length) {
|
|
146
144
|
return;
|
|
@@ -181,6 +179,16 @@ export const AxisY = (props) => {
|
|
|
181
179
|
setPlotLines(plotBeforeContainer, preparedAxisData.plotLines.filter((item) => item.layerPlacement === 'before'));
|
|
182
180
|
setPlotLines(plotAfterContainer, preparedAxisData.plotLines.filter((item) => item.layerPlacement === 'after'));
|
|
183
181
|
}
|
|
182
|
+
return () => {
|
|
183
|
+
if (plotBeforeContainer) {
|
|
184
|
+
plotBeforeContainer.selectAll(`[${plotBandDataAttr}]`).remove();
|
|
185
|
+
plotBeforeContainer.selectAll(`[${plotLineDataAttr}]`).remove();
|
|
186
|
+
}
|
|
187
|
+
if (plotAfterContainer) {
|
|
188
|
+
plotAfterContainer.selectAll(`[${plotBandDataAttr}]`).remove();
|
|
189
|
+
plotAfterContainer.selectAll(`[${plotLineDataAttr}]`).remove();
|
|
190
|
+
}
|
|
191
|
+
};
|
|
184
192
|
}, [lineGenerator, plotAfterRef, plotBeforeRef, preparedAxisData]);
|
|
185
193
|
return (React.createElement(React.Fragment, null,
|
|
186
194
|
React.createElement(HtmlLayer, { preparedData: { htmlElements }, htmlLayout: htmlLayout }),
|
|
@@ -211,12 +211,16 @@ export async function prepareYAxisData({ axis, split, scale, top: topOffset, wid
|
|
|
211
211
|
const startPos = halfBandwidth + Math.min(from, to);
|
|
212
212
|
const endPos = Math.min(Math.abs(to - from), axisHeight - Math.min(from, to));
|
|
213
213
|
const top = Math.max(0, startPos);
|
|
214
|
+
const plotBandHeight = Math.min(endPos, axisHeight);
|
|
215
|
+
if (plotBandHeight < 0) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
214
218
|
plotBands.push({
|
|
215
219
|
layerPlacement: plotBand.layerPlacement,
|
|
216
220
|
x: 0,
|
|
217
221
|
y: axisPlotTopPosition + top,
|
|
218
222
|
width,
|
|
219
|
-
height:
|
|
223
|
+
height: plotBandHeight,
|
|
220
224
|
color: plotBand.color,
|
|
221
225
|
opacity: plotBand.opacity,
|
|
222
226
|
label: plotBand.label.text
|
|
@@ -234,6 +238,9 @@ export async function prepareYAxisData({ axis, split, scale, top: topOffset, wid
|
|
|
234
238
|
const plotLine = axis.plotLines[i];
|
|
235
239
|
const axisScale = scale;
|
|
236
240
|
const plotLineValue = Number(axisScale(plotLine.value));
|
|
241
|
+
if (plotLineValue < 0 || plotLineValue > axisHeight) {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
237
244
|
const points = [
|
|
238
245
|
[0, plotLineValue],
|
|
239
246
|
[width, plotLineValue],
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"label_invalid-axis-type": "It seems you are trying to use inappropriate type for \"{{key}}\" axis. Available types: [{{values}}].",
|
|
20
20
|
"label_invalid-axis-labels-html-type": "It seems you are trying to use inappropriate type for \"labels.html\" property. Only boolean is allowed.",
|
|
21
21
|
"label_invalid-axis-labels-html-not-supported-axis-type": "It seems you are trying to use \"labels.html\" property for an axis with an unsupported type. This property is supported only for \"category\" axis.",
|
|
22
|
-
"label_duplicate-axis-categories": "It seems you have duplicate value \"{{duplicate}}\" found in {{key}}[{{axisIndex}}]."
|
|
22
|
+
"label_duplicate-axis-categories": "It seems you have duplicate value \"{{duplicate}}\" found in {{key}}[{{axisIndex}}].",
|
|
23
|
+
"label_invalid-axis-categories": "It seems you are trying to use inappropriate value for \"categories\", or defined it incorrectly. Categories must be a non-empty array for an axis with \"category\" type."
|
|
23
24
|
},
|
|
24
25
|
"tooltip": {
|
|
25
26
|
"label_totals_sum": "Sum",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"label_invalid-axis-type": "Похоже, что вы пытаетесь использовать некорректный тип для оси \"{{key}}\". Доступные типы: [{{values}}].",
|
|
20
20
|
"label_invalid-axis-labels-html-type": "Похоже, что вы пытаетесь использовать некорректный тип для свойства \"labels.html\". Допускается только использование булевых значений.",
|
|
21
21
|
"label_invalid-axis-labels-html-not-supported-axis-type": "Похоже, что вы пытаетесь использовать свойство \"labels.html\" для оси с неподдерживаемым типом. Это свойство поддерживается только для оси типа \"category\".",
|
|
22
|
-
"label_duplicate-axis-categories": "Похоже, что у вас есть дублирующееся значение категории \"{{duplicate}}\" в оси {{key}}[{{axisIndex}}]."
|
|
22
|
+
"label_duplicate-axis-categories": "Похоже, что у вас есть дублирующееся значение категории \"{{duplicate}}\" в оси {{key}}[{{axisIndex}}].",
|
|
23
|
+
"label_invalid-axis-categories": "Похоже, что вы пытаетесь использовать недопустимое значение для \"categories\", или указали его неверно. Категории для оси типа \"category\" должны быть непустым массивом."
|
|
23
24
|
},
|
|
24
25
|
"tooltip": {
|
|
25
26
|
"label_totals_sum": "Сумма",
|
|
@@ -214,15 +214,19 @@ export interface AxisPlotBand extends AxisPlot {
|
|
|
214
214
|
*
|
|
215
215
|
* Can be a number, a string (e.g., a category), or a timestamp if representing a date.
|
|
216
216
|
* When representing a date, the value **must be a timestamp** (number of milliseconds since Unix epoch).
|
|
217
|
+
*
|
|
218
|
+
* If the value is `-Infinity` or `null`, it will be treated as the start of the axis.
|
|
217
219
|
*/
|
|
218
|
-
from: number | string;
|
|
220
|
+
from: number | string | null;
|
|
219
221
|
/**
|
|
220
222
|
* The end position of the plot band in axis units.
|
|
221
223
|
*
|
|
222
224
|
* Can be a number, a string (e.g., a category), or a timestamp if representing a date.
|
|
223
225
|
* When representing a date, the value **must be a timestamp** (number of milliseconds since Unix epoch).
|
|
226
|
+
*
|
|
227
|
+
* If the value is `Infinity` or `null`, it will be treated as the end of the axis.
|
|
224
228
|
*/
|
|
225
|
-
to: number | string;
|
|
229
|
+
to: number | string | null;
|
|
226
230
|
}
|
|
227
231
|
export interface AxisCrosshair extends Omit<AxisPlotLine, 'value' | 'label'> {
|
|
228
232
|
/** Whether the crosshair should snap to the point or follow the pointer independent of points.
|
|
@@ -67,8 +67,9 @@ export const getAxisPlotsPosition = (axis, split, width = 0) => {
|
|
|
67
67
|
export function getBandsPosition(args) {
|
|
68
68
|
var _a, _b, _c;
|
|
69
69
|
const { band, axisScale } = args;
|
|
70
|
-
const
|
|
71
|
-
const scalePosFrom = axisScale(band.from);
|
|
70
|
+
const range = axisScale.range();
|
|
71
|
+
const scalePosFrom = band.from === -Infinity || band.from === null ? range[0] : axisScale(band.from);
|
|
72
|
+
const scalePosTo = band.to === Infinity || band.to === null ? range[1] : axisScale(band.to);
|
|
72
73
|
const isX = args.axis === 'x';
|
|
73
74
|
if (scalePosTo !== undefined && scalePosFrom !== undefined) {
|
|
74
75
|
return {
|
|
@@ -2,7 +2,15 @@ import { AXIS_TYPE } from '../constants';
|
|
|
2
2
|
import { i18n } from '../i18n';
|
|
3
3
|
import { CHART_ERROR_CODE, ChartError } from '../libs';
|
|
4
4
|
const AVAILABLE_AXIS_TYPES = Object.values(AXIS_TYPE);
|
|
5
|
-
function
|
|
5
|
+
function validateCategories(axis) {
|
|
6
|
+
if (!axis.categories || !Array.isArray(axis.categories) || axis.categories.length === 0) {
|
|
7
|
+
throw new ChartError({
|
|
8
|
+
code: CHART_ERROR_CODE.INVALID_DATA,
|
|
9
|
+
message: i18n('error', 'label_invalid-axis-categories'),
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function validateDuplicateCategories({ axisIndex, key, categories = [], }) {
|
|
6
14
|
const seen = new Set();
|
|
7
15
|
categories.forEach((category) => {
|
|
8
16
|
if (seen.has(category)) {
|
|
@@ -54,7 +62,8 @@ export function validateAxes(args) {
|
|
|
54
62
|
if (xAxis) {
|
|
55
63
|
validateAxisType({ axis: xAxis, key: 'x' });
|
|
56
64
|
validateLabelsHtmlOptions({ axis: xAxis });
|
|
57
|
-
if ((xAxis === null || xAxis === void 0 ? void 0 : xAxis.type) === 'category'
|
|
65
|
+
if ((xAxis === null || xAxis === void 0 ? void 0 : xAxis.type) === 'category') {
|
|
66
|
+
validateCategories(xAxis);
|
|
58
67
|
validateDuplicateCategories({
|
|
59
68
|
categories: xAxis.categories,
|
|
60
69
|
key: 'x',
|
|
@@ -64,7 +73,8 @@ export function validateAxes(args) {
|
|
|
64
73
|
}
|
|
65
74
|
yAxis.forEach((axis, axisIndex) => {
|
|
66
75
|
validateAxisType({ axis, key: 'y' });
|
|
67
|
-
if (axis.type === 'category'
|
|
76
|
+
if (axis.type === 'category') {
|
|
77
|
+
validateCategories(axis);
|
|
68
78
|
validateDuplicateCategories({
|
|
69
79
|
categories: axis.categories,
|
|
70
80
|
key: 'y',
|
|
@@ -11,20 +11,20 @@ export const AxisX = (props) => {
|
|
|
11
11
|
const htmlLabels = preparedAxisData.ticks.map((d) => d.htmlLabel).filter(Boolean);
|
|
12
12
|
React.useEffect(() => {
|
|
13
13
|
if (!ref.current) {
|
|
14
|
-
return;
|
|
14
|
+
return () => { };
|
|
15
15
|
}
|
|
16
16
|
const svgElement = select(ref.current);
|
|
17
17
|
svgElement.selectAll('*').remove();
|
|
18
18
|
let plotBeforeContainer = null;
|
|
19
19
|
let plotAfterContainer = null;
|
|
20
20
|
const plotDataAttr = 'data-plot-x';
|
|
21
|
+
const plotBandDataAttr = `data-plot-x-band-${preparedAxisData.id}`;
|
|
22
|
+
const plotLineDataAttr = `data-plot-x-line-${preparedAxisData.id}`;
|
|
21
23
|
if (plotBeforeRef === null || plotBeforeRef === void 0 ? void 0 : plotBeforeRef.current) {
|
|
22
24
|
plotBeforeContainer = select(plotBeforeRef.current);
|
|
23
|
-
plotBeforeContainer.selectAll(`[${plotDataAttr}]`).remove();
|
|
24
25
|
}
|
|
25
26
|
if (plotAfterRef === null || plotAfterRef === void 0 ? void 0 : plotAfterRef.current) {
|
|
26
27
|
plotAfterContainer = select(plotAfterRef.current);
|
|
27
|
-
plotAfterContainer.selectAll(`[${plotDataAttr}]`).remove();
|
|
28
28
|
}
|
|
29
29
|
if (preparedAxisData.title) {
|
|
30
30
|
svgElement
|
|
@@ -93,7 +93,6 @@ export const AxisX = (props) => {
|
|
|
93
93
|
}
|
|
94
94
|
});
|
|
95
95
|
if (preparedAxisData.plotBands.length > 0) {
|
|
96
|
-
const plotBandDataAttr = `data-plot-x-band-${preparedAxisData.id}`;
|
|
97
96
|
const setPlotBands = (plotContainer, plotBands) => {
|
|
98
97
|
if (!plotContainer || !plotBands.length) {
|
|
99
98
|
return;
|
|
@@ -134,7 +133,6 @@ export const AxisX = (props) => {
|
|
|
134
133
|
setPlotBands(plotAfterContainer, preparedAxisData.plotBands.filter((item) => item.layerPlacement === 'after'));
|
|
135
134
|
}
|
|
136
135
|
if (preparedAxisData.plotLines.length > 0) {
|
|
137
|
-
const plotLineDataAttr = `data-plot-x-line-${preparedAxisData.id}`;
|
|
138
136
|
const setPlotLines = (plotContainer, plotLines) => {
|
|
139
137
|
if (!plotContainer || !plotLines.length) {
|
|
140
138
|
return;
|
|
@@ -175,6 +173,16 @@ export const AxisX = (props) => {
|
|
|
175
173
|
setPlotLines(plotBeforeContainer, preparedAxisData.plotLines.filter((item) => item.layerPlacement === 'before'));
|
|
176
174
|
setPlotLines(plotAfterContainer, preparedAxisData.plotLines.filter((item) => item.layerPlacement === 'after'));
|
|
177
175
|
}
|
|
176
|
+
return () => {
|
|
177
|
+
if (plotBeforeContainer) {
|
|
178
|
+
plotBeforeContainer.selectAll(`[${plotBandDataAttr}]`).remove();
|
|
179
|
+
plotBeforeContainer.selectAll(`[${plotLineDataAttr}]`).remove();
|
|
180
|
+
}
|
|
181
|
+
if (plotAfterContainer) {
|
|
182
|
+
plotAfterContainer.selectAll(`[${plotBandDataAttr}]`).remove();
|
|
183
|
+
plotAfterContainer.selectAll(`[${plotLineDataAttr}]`).remove();
|
|
184
|
+
}
|
|
185
|
+
};
|
|
178
186
|
}, [lineGenerator, plotAfterRef, plotBeforeRef, preparedAxisData]);
|
|
179
187
|
return (React.createElement(React.Fragment, null,
|
|
180
188
|
React.createElement(HtmlLayer, { preparedData: { htmlElements: htmlLabels }, htmlLayout: htmlLayout }),
|
|
@@ -244,11 +244,15 @@ export async function prepareXAxisData({ axis, yAxis, scale, boundsWidth, bounds
|
|
|
244
244
|
const labelSize = plotBand.label.text
|
|
245
245
|
? await getPlotLabelSize(plotBand.label.text)
|
|
246
246
|
: null;
|
|
247
|
+
const plotBandWidth = Math.min(endPos, axisWidth);
|
|
248
|
+
if (plotBandWidth < 0) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
247
251
|
plotBands.push({
|
|
248
252
|
layerPlacement: plotBand.layerPlacement,
|
|
249
253
|
x: Math.max(0, startPos),
|
|
250
254
|
y: 0,
|
|
251
|
-
width:
|
|
255
|
+
width: plotBandWidth,
|
|
252
256
|
height: axisHeight,
|
|
253
257
|
color: plotBand.color,
|
|
254
258
|
opacity: plotBand.opacity,
|
|
@@ -268,6 +272,9 @@ export async function prepareXAxisData({ axis, yAxis, scale, boundsWidth, bounds
|
|
|
268
272
|
const plotLine = axis.plotLines[i];
|
|
269
273
|
const axisScale = scale;
|
|
270
274
|
const plotLineValue = Number(axisScale(plotLine.value));
|
|
275
|
+
if (plotLineValue < 0 || plotLineValue > boundsWidth) {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
271
278
|
const points = [
|
|
272
279
|
[plotLineValue, 0],
|
|
273
280
|
[plotLineValue, axisHeight],
|
|
@@ -17,20 +17,20 @@ export const AxisY = (props) => {
|
|
|
17
17
|
React.useEffect(() => {
|
|
18
18
|
var _a;
|
|
19
19
|
if (!ref.current) {
|
|
20
|
-
return;
|
|
20
|
+
return () => { };
|
|
21
21
|
}
|
|
22
22
|
const svgElement = select(ref.current);
|
|
23
23
|
svgElement.selectAll('*').remove();
|
|
24
24
|
let plotBeforeContainer = null;
|
|
25
25
|
let plotAfterContainer = null;
|
|
26
26
|
const plotDataAttr = 'data-plot-y';
|
|
27
|
+
const plotBandDataAttr = `data-plot-y-band-${preparedAxisData.id}`;
|
|
28
|
+
const plotLineDataAttr = `data-plot-y-line-${preparedAxisData.id}`;
|
|
27
29
|
if (plotBeforeRef === null || plotBeforeRef === void 0 ? void 0 : plotBeforeRef.current) {
|
|
28
30
|
plotBeforeContainer = select(plotBeforeRef.current);
|
|
29
|
-
plotBeforeContainer.selectAll(`[${plotDataAttr}]`).remove();
|
|
30
31
|
}
|
|
31
32
|
if (plotAfterRef === null || plotAfterRef === void 0 ? void 0 : plotAfterRef.current) {
|
|
32
33
|
plotAfterContainer = select(plotAfterRef.current);
|
|
33
|
-
plotAfterContainer.selectAll(`[${plotDataAttr}]`).remove();
|
|
34
34
|
}
|
|
35
35
|
if (((_a = preparedAxisData.title) === null || _a === void 0 ? void 0 : _a.html) === false) {
|
|
36
36
|
svgElement
|
|
@@ -99,7 +99,6 @@ export const AxisY = (props) => {
|
|
|
99
99
|
}
|
|
100
100
|
});
|
|
101
101
|
if (preparedAxisData.plotBands.length > 0) {
|
|
102
|
-
const plotBandDataAttr = `data-plot-y-band-${preparedAxisData.id}`;
|
|
103
102
|
const setPlotBands = (plotContainer, plotBands) => {
|
|
104
103
|
if (!plotContainer || !plotBands.length) {
|
|
105
104
|
return;
|
|
@@ -140,7 +139,6 @@ export const AxisY = (props) => {
|
|
|
140
139
|
setPlotBands(plotAfterContainer, preparedAxisData.plotBands.filter((item) => item.layerPlacement === 'after'));
|
|
141
140
|
}
|
|
142
141
|
if (preparedAxisData.plotLines.length > 0) {
|
|
143
|
-
const plotLineDataAttr = `data-plot-y-line-${preparedAxisData.id}`;
|
|
144
142
|
const setPlotLines = (plotContainer, plotLines) => {
|
|
145
143
|
if (!plotContainer || !plotLines.length) {
|
|
146
144
|
return;
|
|
@@ -181,6 +179,16 @@ export const AxisY = (props) => {
|
|
|
181
179
|
setPlotLines(plotBeforeContainer, preparedAxisData.plotLines.filter((item) => item.layerPlacement === 'before'));
|
|
182
180
|
setPlotLines(plotAfterContainer, preparedAxisData.plotLines.filter((item) => item.layerPlacement === 'after'));
|
|
183
181
|
}
|
|
182
|
+
return () => {
|
|
183
|
+
if (plotBeforeContainer) {
|
|
184
|
+
plotBeforeContainer.selectAll(`[${plotBandDataAttr}]`).remove();
|
|
185
|
+
plotBeforeContainer.selectAll(`[${plotLineDataAttr}]`).remove();
|
|
186
|
+
}
|
|
187
|
+
if (plotAfterContainer) {
|
|
188
|
+
plotAfterContainer.selectAll(`[${plotBandDataAttr}]`).remove();
|
|
189
|
+
plotAfterContainer.selectAll(`[${plotLineDataAttr}]`).remove();
|
|
190
|
+
}
|
|
191
|
+
};
|
|
184
192
|
}, [lineGenerator, plotAfterRef, plotBeforeRef, preparedAxisData]);
|
|
185
193
|
return (React.createElement(React.Fragment, null,
|
|
186
194
|
React.createElement(HtmlLayer, { preparedData: { htmlElements }, htmlLayout: htmlLayout }),
|
|
@@ -211,12 +211,16 @@ export async function prepareYAxisData({ axis, split, scale, top: topOffset, wid
|
|
|
211
211
|
const startPos = halfBandwidth + Math.min(from, to);
|
|
212
212
|
const endPos = Math.min(Math.abs(to - from), axisHeight - Math.min(from, to));
|
|
213
213
|
const top = Math.max(0, startPos);
|
|
214
|
+
const plotBandHeight = Math.min(endPos, axisHeight);
|
|
215
|
+
if (plotBandHeight < 0) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
214
218
|
plotBands.push({
|
|
215
219
|
layerPlacement: plotBand.layerPlacement,
|
|
216
220
|
x: 0,
|
|
217
221
|
y: axisPlotTopPosition + top,
|
|
218
222
|
width,
|
|
219
|
-
height:
|
|
223
|
+
height: plotBandHeight,
|
|
220
224
|
color: plotBand.color,
|
|
221
225
|
opacity: plotBand.opacity,
|
|
222
226
|
label: plotBand.label.text
|
|
@@ -234,6 +238,9 @@ export async function prepareYAxisData({ axis, split, scale, top: topOffset, wid
|
|
|
234
238
|
const plotLine = axis.plotLines[i];
|
|
235
239
|
const axisScale = scale;
|
|
236
240
|
const plotLineValue = Number(axisScale(plotLine.value));
|
|
241
|
+
if (plotLineValue < 0 || plotLineValue > axisHeight) {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
237
244
|
const points = [
|
|
238
245
|
[0, plotLineValue],
|
|
239
246
|
[width, plotLineValue],
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"label_invalid-axis-type": "It seems you are trying to use inappropriate type for \"{{key}}\" axis. Available types: [{{values}}].",
|
|
20
20
|
"label_invalid-axis-labels-html-type": "It seems you are trying to use inappropriate type for \"labels.html\" property. Only boolean is allowed.",
|
|
21
21
|
"label_invalid-axis-labels-html-not-supported-axis-type": "It seems you are trying to use \"labels.html\" property for an axis with an unsupported type. This property is supported only for \"category\" axis.",
|
|
22
|
-
"label_duplicate-axis-categories": "It seems you have duplicate value \"{{duplicate}}\" found in {{key}}[{{axisIndex}}]."
|
|
22
|
+
"label_duplicate-axis-categories": "It seems you have duplicate value \"{{duplicate}}\" found in {{key}}[{{axisIndex}}].",
|
|
23
|
+
"label_invalid-axis-categories": "It seems you are trying to use inappropriate value for \"categories\", or defined it incorrectly. Categories must be a non-empty array for an axis with \"category\" type."
|
|
23
24
|
},
|
|
24
25
|
"tooltip": {
|
|
25
26
|
"label_totals_sum": "Sum",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"label_invalid-axis-type": "Похоже, что вы пытаетесь использовать некорректный тип для оси \"{{key}}\". Доступные типы: [{{values}}].",
|
|
20
20
|
"label_invalid-axis-labels-html-type": "Похоже, что вы пытаетесь использовать некорректный тип для свойства \"labels.html\". Допускается только использование булевых значений.",
|
|
21
21
|
"label_invalid-axis-labels-html-not-supported-axis-type": "Похоже, что вы пытаетесь использовать свойство \"labels.html\" для оси с неподдерживаемым типом. Это свойство поддерживается только для оси типа \"category\".",
|
|
22
|
-
"label_duplicate-axis-categories": "Похоже, что у вас есть дублирующееся значение категории \"{{duplicate}}\" в оси {{key}}[{{axisIndex}}]."
|
|
22
|
+
"label_duplicate-axis-categories": "Похоже, что у вас есть дублирующееся значение категории \"{{duplicate}}\" в оси {{key}}[{{axisIndex}}].",
|
|
23
|
+
"label_invalid-axis-categories": "Похоже, что вы пытаетесь использовать недопустимое значение для \"categories\", или указали его неверно. Категории для оси типа \"category\" должны быть непустым массивом."
|
|
23
24
|
},
|
|
24
25
|
"tooltip": {
|
|
25
26
|
"label_totals_sum": "Сумма",
|
|
@@ -214,15 +214,19 @@ export interface AxisPlotBand extends AxisPlot {
|
|
|
214
214
|
*
|
|
215
215
|
* Can be a number, a string (e.g., a category), or a timestamp if representing a date.
|
|
216
216
|
* When representing a date, the value **must be a timestamp** (number of milliseconds since Unix epoch).
|
|
217
|
+
*
|
|
218
|
+
* If the value is `-Infinity` or `null`, it will be treated as the start of the axis.
|
|
217
219
|
*/
|
|
218
|
-
from: number | string;
|
|
220
|
+
from: number | string | null;
|
|
219
221
|
/**
|
|
220
222
|
* The end position of the plot band in axis units.
|
|
221
223
|
*
|
|
222
224
|
* Can be a number, a string (e.g., a category), or a timestamp if representing a date.
|
|
223
225
|
* When representing a date, the value **must be a timestamp** (number of milliseconds since Unix epoch).
|
|
226
|
+
*
|
|
227
|
+
* If the value is `Infinity` or `null`, it will be treated as the end of the axis.
|
|
224
228
|
*/
|
|
225
|
-
to: number | string;
|
|
229
|
+
to: number | string | null;
|
|
226
230
|
}
|
|
227
231
|
export interface AxisCrosshair extends Omit<AxisPlotLine, 'value' | 'label'> {
|
|
228
232
|
/** Whether the crosshair should snap to the point or follow the pointer independent of points.
|
|
@@ -67,8 +67,9 @@ export const getAxisPlotsPosition = (axis, split, width = 0) => {
|
|
|
67
67
|
export function getBandsPosition(args) {
|
|
68
68
|
var _a, _b, _c;
|
|
69
69
|
const { band, axisScale } = args;
|
|
70
|
-
const
|
|
71
|
-
const scalePosFrom = axisScale(band.from);
|
|
70
|
+
const range = axisScale.range();
|
|
71
|
+
const scalePosFrom = band.from === -Infinity || band.from === null ? range[0] : axisScale(band.from);
|
|
72
|
+
const scalePosTo = band.to === Infinity || band.to === null ? range[1] : axisScale(band.to);
|
|
72
73
|
const isX = args.axis === 'x';
|
|
73
74
|
if (scalePosTo !== undefined && scalePosFrom !== undefined) {
|
|
74
75
|
return {
|
|
@@ -2,7 +2,15 @@ import { AXIS_TYPE } from '../constants';
|
|
|
2
2
|
import { i18n } from '../i18n';
|
|
3
3
|
import { CHART_ERROR_CODE, ChartError } from '../libs';
|
|
4
4
|
const AVAILABLE_AXIS_TYPES = Object.values(AXIS_TYPE);
|
|
5
|
-
function
|
|
5
|
+
function validateCategories(axis) {
|
|
6
|
+
if (!axis.categories || !Array.isArray(axis.categories) || axis.categories.length === 0) {
|
|
7
|
+
throw new ChartError({
|
|
8
|
+
code: CHART_ERROR_CODE.INVALID_DATA,
|
|
9
|
+
message: i18n('error', 'label_invalid-axis-categories'),
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function validateDuplicateCategories({ axisIndex, key, categories = [], }) {
|
|
6
14
|
const seen = new Set();
|
|
7
15
|
categories.forEach((category) => {
|
|
8
16
|
if (seen.has(category)) {
|
|
@@ -54,7 +62,8 @@ export function validateAxes(args) {
|
|
|
54
62
|
if (xAxis) {
|
|
55
63
|
validateAxisType({ axis: xAxis, key: 'x' });
|
|
56
64
|
validateLabelsHtmlOptions({ axis: xAxis });
|
|
57
|
-
if ((xAxis === null || xAxis === void 0 ? void 0 : xAxis.type) === 'category'
|
|
65
|
+
if ((xAxis === null || xAxis === void 0 ? void 0 : xAxis.type) === 'category') {
|
|
66
|
+
validateCategories(xAxis);
|
|
58
67
|
validateDuplicateCategories({
|
|
59
68
|
categories: xAxis.categories,
|
|
60
69
|
key: 'x',
|
|
@@ -64,7 +73,8 @@ export function validateAxes(args) {
|
|
|
64
73
|
}
|
|
65
74
|
yAxis.forEach((axis, axisIndex) => {
|
|
66
75
|
validateAxisType({ axis, key: 'y' });
|
|
67
|
-
if (axis.type === 'category'
|
|
76
|
+
if (axis.type === 'category') {
|
|
77
|
+
validateCategories(axis);
|
|
68
78
|
validateDuplicateCategories({
|
|
69
79
|
categories: axis.categories,
|
|
70
80
|
key: 'y',
|