@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.
@@ -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: Math.min(endPos, axisWidth),
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: Math.min(endPos, axisHeight),
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 scalePosTo = axisScale(band.to);
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 validateDuplicateCategories({ categories, key, axisIndex, }) {
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' && xAxis.categories) {
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' && axis.categories) {
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: Math.min(endPos, axisWidth),
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: Math.min(endPos, axisHeight),
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 scalePosTo = axisScale(band.to);
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 validateDuplicateCategories({ categories, key, axisIndex, }) {
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' && xAxis.categories) {
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' && axis.categories) {
76
+ if (axis.type === 'category') {
77
+ validateCategories(axis);
68
78
  validateDuplicateCategories({
69
79
  categories: axis.categories,
70
80
  key: 'y',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/charts",
3
- "version": "1.27.5",
3
+ "version": "1.28.1",
4
4
  "description": "React component used to render charts",
5
5
  "license": "MIT",
6
6
  "main": "dist/cjs/index.js",