@gravity-ui/charts 1.32.0 → 1.33.0

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.
Files changed (69) hide show
  1. package/dist/cjs/hooks/index.d.ts +1 -0
  2. package/dist/cjs/hooks/index.js +1 -0
  3. package/dist/cjs/hooks/useAxis/x-axis.js +2 -0
  4. package/dist/cjs/hooks/useAxis/y-axis.js +2 -0
  5. package/dist/cjs/hooks/useAxisScales/index.d.ts +6 -8
  6. package/dist/cjs/hooks/useAxisScales/index.js +145 -47
  7. package/dist/cjs/hooks/useAxisScales/types.d.ts +6 -0
  8. package/dist/cjs/hooks/useAxisScales/types.js +1 -0
  9. package/dist/cjs/hooks/useAxisScales/utils.d.ts +9 -1
  10. package/dist/cjs/hooks/useAxisScales/utils.js +74 -0
  11. package/dist/cjs/hooks/useNormalizedOriginalData/index.d.ts +2 -0
  12. package/dist/cjs/hooks/useRangeSlider/index.js +1 -0
  13. package/dist/cjs/hooks/useRangeSlider/types.d.ts +1 -1
  14. package/dist/cjs/hooks/useRangeSlider/utils.d.ts +1 -1
  15. package/dist/cjs/hooks/useShapes/area/prepare-data.d.ts +1 -1
  16. package/dist/cjs/hooks/useShapes/area/prepare-data.js +25 -11
  17. package/dist/cjs/hooks/useShapes/bar-x/prepare-data.d.ts +1 -1
  18. package/dist/cjs/hooks/useShapes/bar-y/prepare-data.d.ts +1 -1
  19. package/dist/cjs/hooks/useShapes/bar-y/prepare-data.js +10 -3
  20. package/dist/cjs/hooks/useShapes/heatmap/prepare-data.d.ts +1 -1
  21. package/dist/cjs/hooks/useShapes/index.d.ts +1 -1
  22. package/dist/cjs/hooks/useShapes/line/prepare-data.d.ts +1 -1
  23. package/dist/cjs/hooks/useShapes/scatter/prepare-data.d.ts +1 -1
  24. package/dist/cjs/hooks/useShapes/utils.d.ts +1 -1
  25. package/dist/cjs/hooks/useShapes/waterfall/prepare-data.d.ts +1 -1
  26. package/dist/cjs/hooks/useZoom/index.d.ts +1 -1
  27. package/dist/cjs/hooks/useZoom/utils.d.ts +1 -1
  28. package/dist/cjs/hooks/utils/bar-x.d.ts +1 -1
  29. package/dist/cjs/hooks/utils/bar-y.d.ts +1 -1
  30. package/dist/cjs/i18n/keysets/en.json +2 -1
  31. package/dist/cjs/i18n/keysets/ru.json +2 -1
  32. package/dist/cjs/types/chart/axis.d.ts +25 -0
  33. package/dist/cjs/utils/chart/index.js +3 -5
  34. package/dist/cjs/validation/validate-axes.js +35 -0
  35. package/dist/esm/hooks/index.d.ts +1 -0
  36. package/dist/esm/hooks/index.js +1 -0
  37. package/dist/esm/hooks/useAxis/x-axis.js +2 -0
  38. package/dist/esm/hooks/useAxis/y-axis.js +2 -0
  39. package/dist/esm/hooks/useAxisScales/index.d.ts +6 -8
  40. package/dist/esm/hooks/useAxisScales/index.js +145 -47
  41. package/dist/esm/hooks/useAxisScales/types.d.ts +6 -0
  42. package/dist/esm/hooks/useAxisScales/types.js +1 -0
  43. package/dist/esm/hooks/useAxisScales/utils.d.ts +9 -1
  44. package/dist/esm/hooks/useAxisScales/utils.js +74 -0
  45. package/dist/esm/hooks/useNormalizedOriginalData/index.d.ts +2 -0
  46. package/dist/esm/hooks/useRangeSlider/index.js +1 -0
  47. package/dist/esm/hooks/useRangeSlider/types.d.ts +1 -1
  48. package/dist/esm/hooks/useRangeSlider/utils.d.ts +1 -1
  49. package/dist/esm/hooks/useShapes/area/prepare-data.d.ts +1 -1
  50. package/dist/esm/hooks/useShapes/area/prepare-data.js +25 -11
  51. package/dist/esm/hooks/useShapes/bar-x/prepare-data.d.ts +1 -1
  52. package/dist/esm/hooks/useShapes/bar-y/prepare-data.d.ts +1 -1
  53. package/dist/esm/hooks/useShapes/bar-y/prepare-data.js +10 -3
  54. package/dist/esm/hooks/useShapes/heatmap/prepare-data.d.ts +1 -1
  55. package/dist/esm/hooks/useShapes/index.d.ts +1 -1
  56. package/dist/esm/hooks/useShapes/line/prepare-data.d.ts +1 -1
  57. package/dist/esm/hooks/useShapes/scatter/prepare-data.d.ts +1 -1
  58. package/dist/esm/hooks/useShapes/utils.d.ts +1 -1
  59. package/dist/esm/hooks/useShapes/waterfall/prepare-data.d.ts +1 -1
  60. package/dist/esm/hooks/useZoom/index.d.ts +1 -1
  61. package/dist/esm/hooks/useZoom/utils.d.ts +1 -1
  62. package/dist/esm/hooks/utils/bar-x.d.ts +1 -1
  63. package/dist/esm/hooks/utils/bar-y.d.ts +1 -1
  64. package/dist/esm/i18n/keysets/en.json +2 -1
  65. package/dist/esm/i18n/keysets/ru.json +2 -1
  66. package/dist/esm/types/chart/axis.d.ts +25 -0
  67. package/dist/esm/utils/chart/index.js +3 -5
  68. package/dist/esm/validation/validate-axes.js +35 -0
  69. package/package.json +1 -1
@@ -1,3 +1,4 @@
1
+ import { ticks } from 'd3';
1
2
  import get from 'lodash/get';
2
3
  import { SERIES_TYPE } from '../../constants';
3
4
  const MARKER_SERIES_TYPES = [SERIES_TYPE.Area, SERIES_TYPE.Line, SERIES_TYPE.Scatter];
@@ -49,3 +50,76 @@ export function getXMaxDomainResult(args) {
49
50
  }
50
51
  return xMaxDomainResult;
51
52
  }
53
+ export function clusterYAxes(yAxes) {
54
+ if (yAxes.length <= 1) {
55
+ return yAxes.map((axis) => [axis]);
56
+ }
57
+ const clusters = {};
58
+ yAxes.forEach((axis) => {
59
+ var _a;
60
+ const plotIndex = (_a = axis.plotIndex) !== null && _a !== void 0 ? _a : 0;
61
+ if (!clusters[plotIndex]) {
62
+ clusters[plotIndex] = [];
63
+ }
64
+ clusters[plotIndex].push(axis);
65
+ });
66
+ return Object.values(clusters).map((cluster) => {
67
+ if (cluster.length <= 1) {
68
+ return [cluster[0]];
69
+ }
70
+ const leftAxis = cluster.find((a) => a.position === 'left');
71
+ const secondaryAxis = cluster.find((a) => a !== leftAxis);
72
+ if (leftAxis) {
73
+ return [leftAxis, secondaryAxis];
74
+ }
75
+ return [cluster[0], cluster[1]];
76
+ });
77
+ }
78
+ export function getDomainSyncedToPrimaryTicks(args) {
79
+ const { primaryTickPositions, range, scaleFn, secondaryDomain } = args;
80
+ const [dMin, dMax] = secondaryDomain;
81
+ const primaryPosBottom = primaryTickPositions[0];
82
+ const primaryPosTop = primaryTickPositions[primaryTickPositions.length - 1];
83
+ let secondaryTicks = ticks(dMin, dMax, primaryTickPositions.length);
84
+ let originalStep = 0;
85
+ if (typeof secondaryTicks[0] === 'number' && typeof secondaryTicks[1] === 'number') {
86
+ originalStep = secondaryTicks[1] - secondaryTicks[0];
87
+ }
88
+ let i = 1;
89
+ while (secondaryTicks.length > primaryTickPositions.length) {
90
+ secondaryTicks = ticks(dMin, dMax, primaryTickPositions.length - i);
91
+ i += 1;
92
+ }
93
+ let step = originalStep;
94
+ if (typeof secondaryTicks[0] === 'number' && typeof secondaryTicks[1] === 'number') {
95
+ step = secondaryTicks[1] - secondaryTicks[0];
96
+ }
97
+ let ticksCountDiff = primaryTickPositions.length - secondaryTicks.length;
98
+ let deltaMin = Math.abs(dMin - secondaryTicks[0]);
99
+ let deltaMax = Math.abs(dMax - secondaryTicks[secondaryTicks.length - 1]);
100
+ while (ticksCountDiff > 0) {
101
+ if (deltaMin > deltaMax) {
102
+ secondaryTicks.unshift(secondaryTicks[0] - step);
103
+ deltaMin -= step;
104
+ }
105
+ else {
106
+ secondaryTicks.push(secondaryTicks[secondaryTicks.length - 1] + step);
107
+ deltaMax -= step;
108
+ }
109
+ ticksCountDiff -= 1;
110
+ }
111
+ let tmpScale = scaleFn()
112
+ .domain([secondaryTicks[0], secondaryTicks[secondaryTicks.length - 1]])
113
+ .range([primaryPosBottom, primaryPosTop]);
114
+ let dNewMin = tmpScale.invert(range[0]);
115
+ let dNewMax = tmpScale.invert(range[1]);
116
+ if (dNewMin < dMin) {
117
+ secondaryTicks = secondaryTicks.map((st) => st + step);
118
+ tmpScale = scaleFn()
119
+ .domain([secondaryTicks[0], secondaryTicks[secondaryTicks.length - 1]])
120
+ .range([primaryPosBottom, primaryPosTop]);
121
+ dNewMin = tmpScale.invert(range[0]);
122
+ dNewMax = tmpScale.invert(range[1]);
123
+ }
124
+ return [dNewMin, dNewMax];
125
+ }
@@ -29,6 +29,8 @@ export declare function useNormalizedOriginalData(props: UseOriginalDataProps):
29
29
  plotBands?: import("../..").AxisPlotBand[];
30
30
  visible?: boolean;
31
31
  order?: "sortAsc" | "sortDesc" | "reverse";
32
+ startOnTick?: boolean;
33
+ endOnTick?: boolean;
32
34
  };
33
35
  normalizedYAxis: import("../..").ChartYAxis[] | undefined;
34
36
  };
@@ -40,6 +40,7 @@ export function useRangeSlider(props) {
40
40
  const { xScale, yScale } = useAxisScales({
41
41
  boundsHeight: preparedRangeSlider.height,
42
42
  boundsWidth,
43
+ isRangeSlider: true,
43
44
  series: preparedSeries,
44
45
  split: EMPTY_PREPARED_SPLIT,
45
46
  xAxis: preparedXAxis,
@@ -1,6 +1,6 @@
1
1
  import type { ChartXAxis, ChartYAxis } from '../../types';
2
2
  import type { PreparedRangeSlider, PreparedXAxis, PreparedYAxis } from '../useAxis/types';
3
- import type { ChartScale } from '../useAxisScales';
3
+ import type { ChartScale } from '../useAxisScales/types';
4
4
  import type { BrushSelection, UseBrushProps } from '../useBrush/types';
5
5
  import type { PreparedChart } from '../useChartOptions/types';
6
6
  import type { PreparedLegend, PreparedSeries, PreparedSeriesOptions } from '../useSeries/types';
@@ -1,5 +1,5 @@
1
1
  import type { PreparedRangeSlider } from '../useAxis/types';
2
- import type { ChartScale } from '../useAxisScales';
2
+ import type { ChartScale } from '../useAxisScales/types';
3
3
  import type { BrushSelection } from '../useBrush/types';
4
4
  import type { PreparedChart } from '../useChartOptions/types';
5
5
  import type { PreparedLegend } from '../useSeries/types';
@@ -1,5 +1,5 @@
1
1
  import type { PreparedXAxis, PreparedYAxis } from '../../useAxis/types';
2
- import type { ChartScale } from '../../useAxisScales';
2
+ import type { ChartScale } from '../../useAxisScales/types';
3
3
  import type { PreparedAreaSeries } from '../../useSeries/types';
4
4
  import type { PreparedSplit } from '../../useSplit/types';
5
5
  import type { PreparedAreaData } from './types';
@@ -91,9 +91,11 @@ export const prepareAreaData = async (args) => {
91
91
  for (let i = 0; i < list.length; i++) {
92
92
  const [_stackId, seriesStack] = list[i];
93
93
  const xValues = getXValues(seriesStack, xAxis, xScale);
94
- const accumulatedYValues = new Map();
94
+ const positiveStackValues = new Map();
95
+ const negativeStackValues = new Map();
95
96
  xValues.forEach(([key]) => {
96
- accumulatedYValues.set(key, 0);
97
+ positiveStackValues.set(key, 0);
98
+ negativeStackValues.set(key, 0);
97
99
  });
98
100
  const seriesStackData = [];
99
101
  for (let j = 0; j < seriesStack.length; j++) {
@@ -122,24 +124,36 @@ export const prepareAreaData = async (args) => {
122
124
  return m.set(key, d);
123
125
  }, new Map());
124
126
  const points = xValues.reduce((pointsAcc, [x, xValue]) => {
125
- var _a;
126
- const accumulatedYValue = accumulatedYValues.get(x) || 0;
127
+ var _a, _b;
127
128
  const d = (_a = seriesData.get(x)) !== null && _a !== void 0 ? _a : {
128
129
  x,
129
130
  y: 0,
130
131
  };
132
+ const yDataValue = (_b = d.y) !== null && _b !== void 0 ? _b : null;
131
133
  const yValue = getYValue({ point: d, yAxis: seriesYAxis, yScale: seriesYScale });
132
- const yPointValue = yValue === null ? null : yValue - accumulatedYValue;
133
- if (yPointValue !== null) {
134
- accumulatedYValues.set(x, yMin - yPointValue);
134
+ let y = null;
135
+ let y0 = yAxisTop + yMin;
136
+ if (typeof yDataValue === 'number' && yValue !== null) {
137
+ if (yDataValue >= 0) {
138
+ const positiveStackHeight = positiveStackValues.get(x) || 0;
139
+ y = yAxisTop + yValue - positiveStackHeight;
140
+ y0 -= positiveStackHeight;
141
+ positiveStackValues.set(x, positiveStackHeight + (yMin - yValue));
142
+ }
143
+ else {
144
+ const negativeStackHeight = negativeStackValues.get(x) || 0;
145
+ y = yAxisTop + yValue + negativeStackHeight;
146
+ y0 += negativeStackHeight;
147
+ negativeStackValues.set(x, negativeStackHeight + (yValue - yMin));
148
+ }
135
149
  }
136
- if (s.nullMode === 'connect' && yPointValue === null) {
150
+ if (s.nullMode === 'connect' && yDataValue === null) {
137
151
  return pointsAcc;
138
152
  }
139
153
  pointsAcc.push({
140
- y0: yAxisTop + yMin - accumulatedYValue,
154
+ y0,
141
155
  x: xValue,
142
- y: yPointValue === null ? null : yAxisTop + (yPointValue !== null && yPointValue !== void 0 ? yPointValue : 0),
156
+ y,
143
157
  data: d,
144
158
  series: s,
145
159
  });
@@ -183,7 +197,7 @@ export const prepareAreaData = async (args) => {
183
197
  }
184
198
  if (series.some((s) => s.stacking === 'percent')) {
185
199
  xValues.forEach(([x], index) => {
186
- const stackHeight = accumulatedYValues.get(x) || 0;
200
+ const stackHeight = positiveStackValues.get(x) || 0;
187
201
  let acc = 0;
188
202
  const ratio = plotHeight / stackHeight;
189
203
  seriesStackData.forEach((item) => {
@@ -1,5 +1,5 @@
1
1
  import type { PreparedXAxis, PreparedYAxis } from '../../useAxis/types';
2
- import type { ChartScale } from '../../useAxisScales';
2
+ import type { ChartScale } from '../../useAxisScales/types';
3
3
  import type { PreparedBarXSeries, PreparedSeriesOptions } from '../../useSeries/types';
4
4
  import type { PreparedSplit } from '../../useSplit/types';
5
5
  import type { PreparedBarXData } from './types';
@@ -1,5 +1,5 @@
1
1
  import type { PreparedXAxis, PreparedYAxis } from '../../useAxis/types';
2
- import type { ChartScale } from '../../useAxisScales';
2
+ import type { ChartScale } from '../../useAxisScales/types';
3
3
  import type { PreparedBarYSeries, PreparedSeriesOptions } from '../../useSeries/types';
4
4
  import type { BarYShapesArgs } from './types';
5
5
  export declare function prepareBarYData(args: {
@@ -45,7 +45,8 @@ export async function prepareBarYData(args) {
45
45
  stacks.forEach((measureValues, groupItemIndex) => {
46
46
  const baseValue = xAxis.type === 'logarithmic' ? 0 : xLinearScale(0);
47
47
  const base = baseValue - measureValues[0].series.borderWidth;
48
- let stackSum = base;
48
+ let positiveStack = base;
49
+ let negativeStack = base;
49
50
  const stackItems = [];
50
51
  const sortedData = sortKey
51
52
  ? sort(measureValues, (a, b) => comparator(get(a, sortKey), get(b, sortKey)))
@@ -93,7 +94,8 @@ export async function prepareBarYData(args) {
93
94
  const isLastStackItem = xValueIndex === sortedData.length - 1;
94
95
  // Calculate position with border compensation
95
96
  // Border extends halfBorder outward from the shape, so we need to adjust position
96
- let itemX = (xValue > baseRangeValue ? stackSum : stackSum - width) + itemStackGap;
97
+ let itemX = xValue > baseRangeValue ? positiveStack : negativeStack - width;
98
+ itemX += itemStackGap;
97
99
  const halfBorder = borderWidth / 2;
98
100
  if (isFirstInStack && xValue > 0) {
99
101
  // Positive bar: border extends left, so shift position left by halfBorder
@@ -119,7 +121,12 @@ export async function prepareBarYData(args) {
119
121
  isLastStackItem,
120
122
  };
121
123
  stackItems.push(item);
122
- stackSum += width;
124
+ if (xValue > baseRangeValue) {
125
+ positiveStack += width;
126
+ }
127
+ else {
128
+ negativeStack -= width;
129
+ }
123
130
  });
124
131
  result.push(...stackItems);
125
132
  });
@@ -1,5 +1,5 @@
1
1
  import type { PreparedXAxis, PreparedYAxis } from '../../../hooks/useAxis/types';
2
- import type { ChartScale } from '../../../hooks/useAxisScales';
2
+ import type { ChartScale } from '../../../hooks/useAxisScales/types';
3
3
  import type { PreparedHeatmapSeries } from '../../useSeries/types';
4
4
  import type { PreparedHeatmapData } from './types';
5
5
  type PrepareHeatmapDataArgs = {
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import type { Dispatch } from 'd3';
3
3
  import type { SeriesType } from '../../constants';
4
4
  import type { PreparedXAxis, PreparedYAxis } from '../useAxis/types';
5
- import type { ChartScale } from '../useAxisScales';
5
+ import type { ChartScale } from '../useAxisScales/types';
6
6
  import type { PreparedSeries, PreparedSeriesOptions } from '../useSeries/types';
7
7
  import type { PreparedSplit } from '../useSplit/types';
8
8
  import type { PreparedAreaData } from './area/types';
@@ -1,5 +1,5 @@
1
1
  import type { PreparedXAxis, PreparedYAxis } from '../../useAxis/types';
2
- import type { ChartScale } from '../../useAxisScales';
2
+ import type { ChartScale } from '../../useAxisScales/types';
3
3
  import type { PreparedLineSeries } from '../../useSeries/types';
4
4
  import type { PreparedSplit } from '../../useSplit/types';
5
5
  import type { PreparedLineData } from './types';
@@ -1,5 +1,5 @@
1
1
  import type { PreparedXAxis, PreparedYAxis } from '../../useAxis/types';
2
- import type { ChartScale } from '../../useAxisScales';
2
+ import type { ChartScale } from '../../useAxisScales/types';
3
3
  import type { PreparedScatterSeries } from '../../useSeries/types';
4
4
  import type { PreparedScatterData } from './types';
5
5
  export declare function prepareScatterData(args: {
@@ -1,7 +1,7 @@
1
1
  import type { BaseType } from 'd3';
2
2
  import type { BasicInactiveState } from '../../types';
3
3
  import type { PreparedXAxis, PreparedYAxis } from '../useAxis/types';
4
- import type { ChartScale } from '../useAxisScales';
4
+ import type { ChartScale } from '../useAxisScales/types';
5
5
  export declare function getXValue(args: {
6
6
  point: {
7
7
  x?: number | string | null;
@@ -1,5 +1,5 @@
1
1
  import type { PreparedXAxis, PreparedYAxis } from '../../useAxis/types';
2
- import type { ChartScale } from '../../useAxisScales';
2
+ import type { ChartScale } from '../../useAxisScales/types';
3
3
  import type { PreparedSeriesOptions, PreparedWaterfallSeries } from '../../useSeries/types';
4
4
  import type { PreparedWaterfallData } from './types';
5
5
  export declare const prepareWaterfallData: (args: {
@@ -1,5 +1,5 @@
1
1
  import type { PreparedXAxis, PreparedYAxis } from '../useAxis/types';
2
- import type { ChartScale } from '../useAxisScales';
2
+ import type { ChartScale } from '../useAxisScales/types';
3
3
  import type { PreparedZoom } from '../useChartOptions/types';
4
4
  import type { PreparedSplit } from '../useSplit/types';
5
5
  import type { ZoomState } from './types';
@@ -1,7 +1,7 @@
1
1
  import type { BrushSelection } from 'd3';
2
2
  import type { ZoomType } from '../../constants';
3
3
  import type { PreparedXAxis, PreparedYAxis } from '../useAxis/types';
4
- import type { ChartScale } from '../useAxisScales';
4
+ import type { ChartScale } from '../useAxisScales/types';
5
5
  import type { ZoomState } from './types';
6
6
  export declare function selectionToZoomBounds(args: {
7
7
  selection: BrushSelection;
@@ -1,6 +1,6 @@
1
1
  import type { BarXSeries, BarXSeriesData } from '../../types';
2
2
  import type { PreparedXAxis } from '../useAxis/types';
3
- import type { ChartScale } from '../useAxisScales';
3
+ import type { ChartScale } from '../useAxisScales/types';
4
4
  import type { PreparedBarXSeries, PreparedSeriesOptions } from '../useSeries/types';
5
5
  export declare function groupBarXDataByXValue<T extends BarXSeries | PreparedBarXSeries>(series: T[], xAxis: PreparedXAxis): Record<string | number, Record<string, {
6
6
  data: BarXSeriesData;
@@ -1,6 +1,6 @@
1
1
  import type { BarYSeries, BarYSeriesData } from '../../types';
2
2
  import type { PreparedYAxis } from '../useAxis/types';
3
- import type { ChartScale } from '../useAxisScales';
3
+ import type { ChartScale } from '../useAxisScales/types';
4
4
  import type { PreparedBarYSeries, PreparedSeriesOptions } from '../useSeries/types';
5
5
  /**
6
6
  * BarY always filters out data with null or replace null by zero.
@@ -20,7 +20,8 @@
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
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
+ "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.",
24
+ "label_inconsistent-y-axis-configuration": "It seems you have inconsistent Y-axis configuration. Possible reasons:\n1. Multiple Y axes with the same position and plot index.\n2. At the moment, 'category' axis is not supported in dual Y-axis configurations."
24
25
  },
25
26
  "tooltip": {
26
27
  "label_totals_sum": "Sum",
@@ -20,7 +20,8 @@
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
22
  "label_duplicate-axis-categories": "Похоже, что у вас есть дублирующееся значение категории \"{{duplicate}}\" в оси {{key}}[{{axisIndex}}].",
23
- "label_invalid-axis-categories": "Похоже, что вы пытаетесь использовать недопустимое значение для \"categories\", или указали его неверно. Категории для оси типа \"category\" должны быть непустым массивом."
23
+ "label_invalid-axis-categories": "Похоже, что вы пытаетесь использовать недопустимое значение для \"categories\", или указали его неверно. Категории для оси типа \"category\" должны быть непустым массивом.",
24
+ "label_inconsistent-y-axis-configuration": "Похоже, что конфигурация осей Y неконсистентна. Возможные причины:\n1. Несколько осей Y имеют одинаковые значения position и plotIndex.\n2. На данный момент категорийные оси не поддерживаются в конфигурациях с двумя осями Y."
24
25
  },
25
26
  "tooltip": {
26
27
  "label_totals_sum": "Сумма",
@@ -164,6 +164,31 @@ export interface ChartAxis {
164
164
  * the "reverse" value is needed to use the reverse order without sorting
165
165
  */
166
166
  order?: 'sortAsc' | 'sortDesc' | 'reverse';
167
+ /**
168
+ * Whether to force the axis to start on a tick.
169
+ *
170
+ * When enabled, the axis minimum is rounded to a "nice" value (e.g., round numbers)
171
+ * for better readability. This can create a visual gap at the beginning of the chart
172
+ * if the data minimum doesn't align with the tick.
173
+ *
174
+ * Use `startOnTick: false` to make the chart start exactly at the data minimum,
175
+ * preventing gaps at the chart start.
176
+ *
177
+ * @default true for X axis and datetime Y axis, false for linear/logarithmic Y axis
178
+ */
179
+ startOnTick?: boolean;
180
+ /**
181
+ * Whether to force the axis to end on a tick.
182
+ *
183
+ * When enabled, the axis maximum is rounded to a "nice" value (e.g., round numbers)
184
+ * for better readability. This can create extra space at the end of the chart
185
+ * if the data maximum doesn't align with the tick.
186
+ *
187
+ * Use `endOnTick: false` to make the chart end closer to the data maximum.
188
+ *
189
+ * @default true for X axis and datetime Y axis, false for linear/logarithmic Y axis
190
+ */
191
+ endOnTick?: boolean;
167
192
  }
168
193
  export interface ChartXAxis extends ChartAxis {
169
194
  /**
@@ -104,11 +104,9 @@ export function getDefaultMaxXAxisValue(series) {
104
104
  }
105
105
  export function getDefaultMinXAxisValue(series) {
106
106
  if (series === null || series === void 0 ? void 0 : series.some((s) => CHART_SERIES_WITH_VOLUME_ON_X_AXIS.includes(s.type))) {
107
- return series.reduce((minValue, s) => {
108
- // https://github.com/gravity-ui/charts/issues/160
109
- // @ts-expect-error
110
- const minXValue = s.data.reduce((res, d) => Math.min(res, get(d, 'x', 0)), 0);
111
- return Math.min(minValue, minXValue);
107
+ const domainData = getDomainDataXBySeries(series);
108
+ return domainData.reduce((minValue, d) => {
109
+ return Math.min(minValue, d);
112
110
  }, 0);
113
111
  }
114
112
  return undefined;
@@ -57,6 +57,40 @@ function validateLabelsHtmlOptions(args) {
57
57
  });
58
58
  }
59
59
  }
60
+ function validateYAxesConsistency(yAxis) {
61
+ const axesByPlot = {};
62
+ yAxis.forEach((axis) => {
63
+ const plotIndex = axis.plotIndex || 0;
64
+ if (!axesByPlot[plotIndex]) {
65
+ axesByPlot[plotIndex] = [];
66
+ }
67
+ axesByPlot[plotIndex].push(axis);
68
+ });
69
+ Object.values(axesByPlot).forEach((axes) => {
70
+ const seenPositions = new Set();
71
+ axes.forEach((axis, index) => {
72
+ const isFirstPlotAxis = index === 0;
73
+ const defaultAxisPosition = isFirstPlotAxis ? 'left' : 'right';
74
+ const position = axis.position || defaultAxisPosition;
75
+ if (seenPositions.has(position)) {
76
+ throw new ChartError({
77
+ code: CHART_ERROR_CODE.INVALID_DATA,
78
+ message: i18n('error', 'label_inconsistent-y-axis-configuration'),
79
+ });
80
+ }
81
+ seenPositions.add(position);
82
+ });
83
+ if (axes.length > 1) {
84
+ const hasCategoryAxis = axes.some((axis) => axis.type === 'category');
85
+ if (hasCategoryAxis) {
86
+ throw new ChartError({
87
+ code: CHART_ERROR_CODE.INVALID_DATA,
88
+ message: i18n('error', 'label_inconsistent-y-axis-configuration'),
89
+ });
90
+ }
91
+ }
92
+ });
93
+ }
60
94
  export function validateAxes(args) {
61
95
  const { xAxis, yAxis = [] } = args;
62
96
  if (xAxis) {
@@ -71,6 +105,7 @@ export function validateAxes(args) {
71
105
  });
72
106
  }
73
107
  }
108
+ validateYAxesConsistency(yAxis);
74
109
  yAxis.forEach((axis, axisIndex) => {
75
110
  validateAxisType({ axis, key: 'y' });
76
111
  if (axis.type === 'category') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/charts",
3
- "version": "1.32.0",
3
+ "version": "1.33.0",
4
4
  "description": "A flexible JavaScript library for data visualization and chart rendering using React",
5
5
  "license": "MIT",
6
6
  "main": "dist/cjs/index.js",