@gravity-ui/charts 1.34.7 → 1.34.9

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/components/AxisX/AxisX.js +0 -1
  2. package/dist/cjs/components/AxisX/prepare-axis-data.js +4 -23
  3. package/dist/cjs/components/AxisX/types.d.ts +1 -9
  4. package/dist/cjs/components/AxisY/prepare-axis-title.js +3 -34
  5. package/dist/cjs/components/AxisY/types.d.ts +1 -9
  6. package/dist/cjs/components/ChartInner/index.js +6 -6
  7. package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +0 -1
  8. package/dist/cjs/components/ChartInner/useChartInnerProps.js +16 -6
  9. package/dist/cjs/components/ChartInner/useChartInnerState.d.ts +2 -3
  10. package/dist/cjs/components/ChartInner/useChartInnerState.js +14 -22
  11. package/dist/cjs/components/RangeSlider/index.d.ts +0 -1
  12. package/dist/cjs/components/RangeSlider/index.js +1 -8
  13. package/dist/cjs/components/types/index.d.ts +9 -0
  14. package/dist/cjs/components/types/index.js +1 -0
  15. package/dist/cjs/components/utils/axis-title.d.ts +6 -0
  16. package/dist/cjs/components/utils/axis-title.js +39 -0
  17. package/dist/cjs/components/{utils.d.ts → utils/index.d.ts} +3 -3
  18. package/dist/cjs/components/{utils.js → utils/index.js} +1 -1
  19. package/dist/cjs/hooks/useAxisScales/index.js +3 -1
  20. package/dist/cjs/hooks/useAxisScales/x-scale.d.ts +0 -6
  21. package/dist/cjs/hooks/useAxisScales/x-scale.js +12 -35
  22. package/dist/cjs/hooks/useAxisScales/y-scale.js +2 -7
  23. package/dist/cjs/hooks/useRangeSlider/types.d.ts +1 -1
  24. package/dist/cjs/hooks/useSeries/prepare-legend.js +2 -1
  25. package/dist/cjs/hooks/useShapes/area/index.js +13 -4
  26. package/dist/cjs/hooks/useShapes/area/prepare-data.js +3 -2
  27. package/dist/cjs/hooks/useShapes/bar-x/index.js +13 -4
  28. package/dist/cjs/hooks/useShapes/bar-x/prepare-data.js +15 -6
  29. package/dist/cjs/hooks/useShapes/line/index.js +13 -4
  30. package/dist/cjs/hooks/useShapes/line/prepare-data.js +4 -2
  31. package/dist/cjs/hooks/useShapes/waterfall/index.js +13 -4
  32. package/dist/cjs/utils/chart/get-closest-data.js +39 -35
  33. package/dist/cjs/utils/chart/zoom.d.ts +2 -1
  34. package/dist/cjs/utils/chart/zoom.js +9 -0
  35. package/dist/esm/components/AxisX/AxisX.js +0 -1
  36. package/dist/esm/components/AxisX/prepare-axis-data.js +4 -23
  37. package/dist/esm/components/AxisX/types.d.ts +1 -9
  38. package/dist/esm/components/AxisY/prepare-axis-title.js +3 -34
  39. package/dist/esm/components/AxisY/types.d.ts +1 -9
  40. package/dist/esm/components/ChartInner/index.js +6 -6
  41. package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +0 -1
  42. package/dist/esm/components/ChartInner/useChartInnerProps.js +16 -6
  43. package/dist/esm/components/ChartInner/useChartInnerState.d.ts +2 -3
  44. package/dist/esm/components/ChartInner/useChartInnerState.js +14 -22
  45. package/dist/esm/components/RangeSlider/index.d.ts +0 -1
  46. package/dist/esm/components/RangeSlider/index.js +1 -8
  47. package/dist/esm/components/types/index.d.ts +9 -0
  48. package/dist/esm/components/types/index.js +1 -0
  49. package/dist/esm/components/utils/axis-title.d.ts +6 -0
  50. package/dist/esm/components/utils/axis-title.js +39 -0
  51. package/dist/esm/components/{utils.d.ts → utils/index.d.ts} +3 -3
  52. package/dist/esm/components/{utils.js → utils/index.js} +1 -1
  53. package/dist/esm/hooks/useAxisScales/index.js +3 -1
  54. package/dist/esm/hooks/useAxisScales/x-scale.d.ts +0 -6
  55. package/dist/esm/hooks/useAxisScales/x-scale.js +12 -35
  56. package/dist/esm/hooks/useAxisScales/y-scale.js +2 -7
  57. package/dist/esm/hooks/useRangeSlider/types.d.ts +1 -1
  58. package/dist/esm/hooks/useSeries/prepare-legend.js +2 -1
  59. package/dist/esm/hooks/useShapes/area/index.js +13 -4
  60. package/dist/esm/hooks/useShapes/area/prepare-data.js +3 -2
  61. package/dist/esm/hooks/useShapes/bar-x/index.js +13 -4
  62. package/dist/esm/hooks/useShapes/bar-x/prepare-data.js +15 -6
  63. package/dist/esm/hooks/useShapes/line/index.js +13 -4
  64. package/dist/esm/hooks/useShapes/line/prepare-data.js +4 -2
  65. package/dist/esm/hooks/useShapes/waterfall/index.js +13 -4
  66. package/dist/esm/utils/chart/get-closest-data.js +39 -35
  67. package/dist/esm/utils/chart/zoom.d.ts +2 -1
  68. package/dist/esm/utils/chart/zoom.js +9 -0
  69. package/package.json +1 -1
@@ -11,8 +11,10 @@ export const AreaSeriesShapes = (args) => {
11
11
  const hoveredDataRef = React.useRef(null);
12
12
  const plotRef = React.useRef(null);
13
13
  const markersRef = React.useRef(null);
14
+ const allowOverlapDataLabels = React.useMemo(() => {
15
+ return preparedData.some((d) => d === null || d === void 0 ? void 0 : d.series.dataLabels.allowOverlap);
16
+ }, [preparedData]);
14
17
  React.useEffect(() => {
15
- var _a;
16
18
  if (!plotRef.current || !markersRef.current) {
17
19
  return () => { };
18
20
  }
@@ -55,7 +57,7 @@ export const AreaSeriesShapes = (args) => {
55
57
  let dataLabels = preparedData.reduce((acc, d) => {
56
58
  return acc.concat(d.labels);
57
59
  }, []);
58
- if (!((_a = preparedData[0]) === null || _a === void 0 ? void 0 : _a.series.dataLabels.allowOverlap)) {
60
+ if (!allowOverlapDataLabels) {
59
61
  dataLabels = filterOverlappingLabels(dataLabels);
60
62
  }
61
63
  const labelsSelection = plotSvgElement
@@ -147,9 +149,16 @@ export const AreaSeriesShapes = (args) => {
147
149
  return () => {
148
150
  dispatcher === null || dispatcher === void 0 ? void 0 : dispatcher.on('hover-shape.area', null);
149
151
  };
150
- }, [dispatcher, preparedData, seriesOptions]);
152
+ }, [allowOverlapDataLabels, dispatcher, preparedData, seriesOptions]);
153
+ const htmlLayerData = React.useMemo(() => {
154
+ const items = preparedData.map((d) => d === null || d === void 0 ? void 0 : d.htmlElements).flat();
155
+ if (allowOverlapDataLabels) {
156
+ return { htmlElements: items };
157
+ }
158
+ return { htmlElements: filterOverlappingLabels(items) };
159
+ }, [allowOverlapDataLabels, preparedData]);
151
160
  return (React.createElement(React.Fragment, null,
152
161
  React.createElement("g", { ref: plotRef, className: b(), clipPath: `url(#${clipPathId})` }),
153
162
  React.createElement("g", { ref: markersRef }),
154
- React.createElement(HtmlLayer, { preparedData: preparedData, htmlLayout: htmlLayout })));
163
+ React.createElement(HtmlLayer, { preparedData: htmlLayerData, htmlLayout: htmlLayout })));
155
164
  };
@@ -27,14 +27,14 @@ function getXValues(series, xAxis, xScale) {
27
27
  }
28
28
  return Array.from(xValues);
29
29
  }
30
- async function prepareDataLabels({ series, points, xMax, yAxisTop, }) {
30
+ async function prepareDataLabels({ series, points, xMax, yAxisTop, isOutsideBounds, }) {
31
31
  var _a;
32
32
  const svgLabels = [];
33
33
  const htmlLabels = [];
34
34
  const getTextSize = getTextSizeFn({ style: series.dataLabels.style });
35
35
  for (let pointsIndex = 0; pointsIndex < points.length; pointsIndex++) {
36
36
  const point = points[pointsIndex];
37
- if (point.y === null) {
37
+ if (point.y === null || isOutsideBounds(point.x, point.y)) {
38
38
  continue;
39
39
  }
40
40
  const text = getFormattedValue(Object.assign({ value: (_a = point.data.label) !== null && _a !== void 0 ? _a : point.data.y }, series.dataLabels));
@@ -281,6 +281,7 @@ export const prepareAreaData = async (args) => {
281
281
  points: item.points,
282
282
  xMax,
283
283
  yAxisTop: itemYAxisTop,
284
+ isOutsideBounds,
284
285
  });
285
286
  item.labels.push(...labelsData.svgLabels);
286
287
  item.htmlElements.push(...labelsData.htmlLabels);
@@ -11,8 +11,10 @@ export const BarXSeriesShapes = (args) => {
11
11
  const { dispatcher, preparedData, seriesOptions, htmlLayout, clipPathId } = args;
12
12
  const hoveredDataRef = React.useRef(null);
13
13
  const ref = React.useRef(null);
14
+ const allowOverlapDataLabels = React.useMemo(() => {
15
+ return preparedData.some((d) => d === null || d === void 0 ? void 0 : d.series.dataLabels.allowOverlap);
16
+ }, [preparedData]);
14
17
  React.useEffect(() => {
15
- var _a;
16
18
  if (!ref.current) {
17
19
  return () => { };
18
20
  }
@@ -46,7 +48,7 @@ export const BarXSeriesShapes = (args) => {
46
48
  .attr('opacity', (d) => d.opacity)
47
49
  .attr('cursor', (d) => d.series.cursor);
48
50
  let dataLabels = preparedData.map((d) => d.label).filter(Boolean);
49
- if (!((_a = preparedData[0]) === null || _a === void 0 ? void 0 : _a.series.dataLabels.allowOverlap)) {
51
+ if (!allowOverlapDataLabels) {
50
52
  dataLabels = filterOverlappingLabels(dataLabels);
51
53
  }
52
54
  const labelSelection = svgElement
@@ -108,8 +110,15 @@ export const BarXSeriesShapes = (args) => {
108
110
  return () => {
109
111
  dispatcher === null || dispatcher === void 0 ? void 0 : dispatcher.on('hover-shape.bar-x', null);
110
112
  };
111
- }, [dispatcher, preparedData, seriesOptions]);
113
+ }, [allowOverlapDataLabels, dispatcher, preparedData, seriesOptions]);
114
+ const htmlLayerData = React.useMemo(() => {
115
+ const items = preparedData.map((d) => d === null || d === void 0 ? void 0 : d.htmlElements).flat();
116
+ if (allowOverlapDataLabels) {
117
+ return { htmlElements: items };
118
+ }
119
+ return { htmlElements: filterOverlappingLabels(items) };
120
+ }, [allowOverlapDataLabels, preparedData]);
112
121
  return (React.createElement(React.Fragment, null,
113
122
  React.createElement("g", { ref: ref, className: b(), clipPath: `url(#${clipPathId})` }),
114
- React.createElement(HtmlLayer, { preparedData: preparedData, htmlLayout: htmlLayout })));
123
+ React.createElement(HtmlLayer, { preparedData: htmlLayerData, htmlLayout: htmlLayout })));
115
124
  };
@@ -5,7 +5,7 @@ import { getFormattedValue } from '../../../utils/chart/format';
5
5
  import { getSeriesStackId } from '../../useSeries/utils';
6
6
  import { getBarXLayout } from '../../utils/bar-x';
7
7
  const isSeriesDataValid = (d) => d.y !== null;
8
- async function getLabelData(d) {
8
+ async function getLabelData(d, xMax) {
9
9
  var _a;
10
10
  if (!d.series.dataLabels.enabled) {
11
11
  return undefined;
@@ -22,10 +22,10 @@ async function getLabelData(d) {
22
22
  if (d.series.dataLabels.inside) {
23
23
  y = d.y + d.height / 2;
24
24
  }
25
- const x = d.x + d.width / 2;
25
+ const centerX = Math.min(xMax - width / 2, Math.max(width / 2, d.x + d.width / 2));
26
26
  return {
27
27
  text,
28
- x: html ? x - width / 2 : x,
28
+ x: html ? centerX - width / 2 : centerX,
29
29
  y: html ? y - height : y,
30
30
  style,
31
31
  size: { width, height },
@@ -35,7 +35,7 @@ async function getLabelData(d) {
35
35
  }
36
36
  // eslint-disable-next-line complexity
37
37
  export const prepareBarXData = async (args) => {
38
- var _a, _b, _c, _d;
38
+ var _a, _b, _c, _d, _e;
39
39
  const { series, seriesOptions, xAxis, xScale, yAxis, yScale, boundsHeight: plotHeight, split, isRangeSlider, } = args;
40
40
  const stackGap = seriesOptions['bar-x'].stackGap;
41
41
  const categories = (_a = xAxis === null || xAxis === void 0 ? void 0 : xAxis.categories) !== null && _a !== void 0 ? _a : [];
@@ -176,10 +176,19 @@ export const prepareBarXData = async (args) => {
176
176
  }
177
177
  }
178
178
  }
179
+ const [_xMin, xRangeMax] = xScale.range();
180
+ const xMax = xRangeMax;
179
181
  for (let i = 0; i < result.length; i++) {
180
182
  const barData = result[i];
181
- if (barData.series.dataLabels.enabled && !isRangeSlider) {
182
- const label = await getLabelData(barData);
183
+ const isBarOutsideBounds = barData.x + barData.width <= 0 ||
184
+ barData.x >= xMax ||
185
+ barData.y + barData.height <= 0 ||
186
+ barData.y >= plotHeight;
187
+ const isZeroValue = ((_e = barData.data.y) !== null && _e !== void 0 ? _e : 0) === 0;
188
+ if (barData.series.dataLabels.enabled &&
189
+ !isRangeSlider &&
190
+ (!isBarOutsideBounds || isZeroValue)) {
191
+ const label = await getLabelData(barData, xMax);
183
192
  if (barData.series.dataLabels.html && label) {
184
193
  barData.htmlElements.push({
185
194
  x: label.x,
@@ -11,8 +11,10 @@ export const LineSeriesShapes = (args) => {
11
11
  const hoveredDataRef = React.useRef(null);
12
12
  const plotRef = React.useRef(null);
13
13
  const markersRef = React.useRef(null);
14
+ const allowOverlapDataLabels = React.useMemo(() => {
15
+ return preparedData.some((d) => d === null || d === void 0 ? void 0 : d.series.dataLabels.allowOverlap);
16
+ }, [preparedData]);
14
17
  React.useEffect(() => {
15
- var _a;
16
18
  if (!plotRef.current || !markersRef.current) {
17
19
  return () => { };
18
20
  }
@@ -42,7 +44,7 @@ export const LineSeriesShapes = (args) => {
42
44
  let dataLabels = preparedData.reduce((acc, d) => {
43
45
  return acc.concat(d.labels);
44
46
  }, []);
45
- if (!((_a = preparedData[0]) === null || _a === void 0 ? void 0 : _a.series.dataLabels.allowOverlap)) {
47
+ if (!allowOverlapDataLabels) {
46
48
  dataLabels = filterOverlappingLabels(dataLabels);
47
49
  }
48
50
  const labelsSelection = plotSvgElement
@@ -133,9 +135,16 @@ export const LineSeriesShapes = (args) => {
133
135
  return () => {
134
136
  dispatcher === null || dispatcher === void 0 ? void 0 : dispatcher.on('hover-shape.line', null);
135
137
  };
136
- }, [dispatcher, preparedData, seriesOptions]);
138
+ }, [allowOverlapDataLabels, dispatcher, preparedData, seriesOptions]);
139
+ const htmlLayerData = React.useMemo(() => {
140
+ const items = preparedData.map((d) => d === null || d === void 0 ? void 0 : d.htmlElements).flat();
141
+ if (allowOverlapDataLabels) {
142
+ return { htmlElements: items };
143
+ }
144
+ return { htmlElements: filterOverlappingLabels(items) };
145
+ }, [allowOverlapDataLabels, preparedData]);
137
146
  return (React.createElement(React.Fragment, null,
138
147
  React.createElement("g", { ref: plotRef, className: b(), clipPath: `url(#${clipPathId})` }),
139
148
  React.createElement("g", { ref: markersRef }),
140
- React.createElement(HtmlLayer, { preparedData: preparedData, htmlLayout: htmlLayout })));
149
+ React.createElement(HtmlLayer, { preparedData: htmlLayerData, htmlLayout: htmlLayout })));
141
150
  };
@@ -49,7 +49,7 @@ export const prepareLineData = async (args) => {
49
49
  if (s.dataLabels.enabled && !isRangeSlider) {
50
50
  if (s.dataLabels.html) {
51
51
  const list = await Promise.all(points.reduce((result, p) => {
52
- if (p.y === null) {
52
+ if (p.y === null || p.x === null || isOutsideBounds(p.x, p.y)) {
53
53
  return result;
54
54
  }
55
55
  result.push(getHtmlLabel(p, s, xMax));
@@ -61,7 +61,9 @@ export const prepareLineData = async (args) => {
61
61
  const getTextSize = getTextSizeFn({ style: s.dataLabels.style });
62
62
  for (let index = 0; index < points.length; index++) {
63
63
  const point = points[index];
64
- if (point.y !== null && point.x !== null) {
64
+ if (point.y !== null &&
65
+ point.x !== null &&
66
+ !isOutsideBounds(point.x, point.y)) {
65
67
  const labelValue = (_b = point.data.label) !== null && _b !== void 0 ? _b : point.data.y;
66
68
  const text = getFormattedValue(Object.assign({ value: labelValue }, s.dataLabels));
67
69
  const labelSize = await getTextSize(text);
@@ -12,8 +12,10 @@ export const WaterfallSeriesShapes = (args) => {
12
12
  const hoveredDataRef = React.useRef(null);
13
13
  const ref = React.useRef(null);
14
14
  const connectorSelector = `.${b('connector')}`;
15
+ const allowOverlapDataLabels = React.useMemo(() => {
16
+ return preparedData.some((d) => d === null || d === void 0 ? void 0 : d.series.dataLabels.allowOverlap);
17
+ }, [preparedData]);
15
18
  React.useEffect(() => {
16
- var _a;
17
19
  if (!ref.current) {
18
20
  return () => { };
19
21
  }
@@ -34,7 +36,7 @@ export const WaterfallSeriesShapes = (args) => {
34
36
  .attr('opacity', (d) => d.opacity)
35
37
  .attr('cursor', (d) => d.series.cursor);
36
38
  let dataLabels = preparedData.map((d) => d.label).filter(Boolean);
37
- if (!((_a = preparedData[0]) === null || _a === void 0 ? void 0 : _a.series.dataLabels.allowOverlap)) {
39
+ if (!allowOverlapDataLabels) {
38
40
  dataLabels = filterOverlappingLabels(dataLabels);
39
41
  }
40
42
  const labelSelection = svgElement
@@ -125,8 +127,15 @@ export const WaterfallSeriesShapes = (args) => {
125
127
  return () => {
126
128
  dispatcher === null || dispatcher === void 0 ? void 0 : dispatcher.on('hover-shape.waterfall', null);
127
129
  };
128
- }, [connectorSelector, dispatcher, preparedData, seriesOptions]);
130
+ }, [allowOverlapDataLabels, connectorSelector, dispatcher, preparedData, seriesOptions]);
131
+ const htmlLayerData = React.useMemo(() => {
132
+ const items = preparedData.map((d) => d === null || d === void 0 ? void 0 : d.htmlElements).flat();
133
+ if (allowOverlapDataLabels) {
134
+ return { htmlElements: items };
135
+ }
136
+ return { htmlElements: filterOverlappingLabels(items) };
137
+ }, [allowOverlapDataLabels, preparedData]);
129
138
  return (React.createElement(React.Fragment, null,
130
139
  React.createElement("g", { ref: ref, className: b(), clipPath: `url(#${clipPathId})` }),
131
- React.createElement(HtmlLayer, { preparedData: preparedData, htmlLayout: htmlLayout })));
140
+ React.createElement(HtmlLayer, { preparedData: htmlLayerData, htmlLayout: htmlLayout })));
132
141
  };
@@ -47,19 +47,53 @@ export function getClosestPoints(args) {
47
47
  const [pointerX, pointerY] = position;
48
48
  const result = [];
49
49
  const groups = groupBy(shapesData, getSeriesType);
50
+ const closestPointsByXValue = [];
50
51
  // eslint-disable-next-line complexity
51
52
  Object.entries(groups).forEach(([seriesType, list]) => {
52
53
  var _a, _b, _c, _d, _e;
53
54
  switch (seriesType) {
55
+ case 'line': {
56
+ const linePoints = list.reduce((acc, d) => {
57
+ acc.push(...d.points.reduce((accPoints, p) => {
58
+ if (p.y !== null && p.x !== null) {
59
+ accPoints.push({
60
+ data: p.data,
61
+ series: p.series,
62
+ x: p.x,
63
+ y0: p.y,
64
+ y1: p.y,
65
+ });
66
+ }
67
+ return accPoints;
68
+ }, []));
69
+ return acc;
70
+ }, []);
71
+ closestPointsByXValue.push(...linePoints);
72
+ break;
73
+ }
74
+ case 'area': {
75
+ const areaPoints = list.reduce((acc, d) => {
76
+ Array.prototype.push.apply(acc, d.points.map((p) => ({
77
+ data: p.data,
78
+ series: p.series,
79
+ x: p.x,
80
+ y0: p.y0,
81
+ y1: p.y,
82
+ })));
83
+ return acc;
84
+ }, []);
85
+ closestPointsByXValue.push(...areaPoints);
86
+ break;
87
+ }
54
88
  case 'bar-x': {
55
- const points = list.map((d) => ({
89
+ const barXPoints = list.map((d) => ({
56
90
  data: d.data,
57
91
  series: d.series,
58
92
  x: d.x + d.width / 2,
59
93
  y0: d.y,
60
94
  y1: d.y + d.height,
61
95
  }));
62
- result.push(...getClosestPointsByXValue(pointerX, pointerY, points));
96
+ closestPointsByXValue.push(...barXPoints);
63
97
  break;
64
98
  }
65
99
  case 'waterfall': {
@@ -74,39 +108,6 @@ export function getClosestPoints(args) {
74
108
  result.push(...getClosestPointsByXValue(pointerX, pointerY, points));
75
109
  break;
76
110
  }
77
- case 'area': {
78
- const points = list.reduce((acc, d) => {
79
- Array.prototype.push.apply(acc, d.points.map((p) => ({
80
- data: p.data,
81
- series: p.series,
82
- x: p.x,
83
- y0: p.y0,
84
- y1: p.y,
85
- })));
86
- return acc;
87
- }, []);
88
- result.push(...getClosestPointsByXValue(pointerX, pointerY, points));
89
- break;
90
- }
91
- case 'line': {
92
- const points = list.reduce((acc, d) => {
93
- acc.push(...d.points.reduce((accPoints, p) => {
94
- if (p.y !== null && p.x !== null) {
95
- accPoints.push({
96
- data: p.data,
97
- series: p.series,
98
- x: p.x,
99
- y0: p.y,
100
- y1: p.y,
101
- });
102
- }
103
- return accPoints;
104
- }, []));
105
- return acc;
106
- }, []);
107
- result.push(...getClosestPointsByXValue(pointerX, pointerY, points));
108
- break;
109
- }
110
111
  case 'bar-y': {
111
112
  const points = list;
112
113
  const sorted = sort(points, (p) => p.y);
@@ -267,6 +268,9 @@ export function getClosestPoints(args) {
267
268
  }
268
269
  }
269
270
  });
271
+ if (closestPointsByXValue.length) {
272
+ result.push(...getClosestPointsByXValue(pointerX, pointerY, closestPointsByXValue));
273
+ }
270
274
  return result;
271
275
  }
272
276
  function isInsidePath(args) {
@@ -1,4 +1,4 @@
1
- import type { PreparedSeries, PreparedXAxis, PreparedYAxis } from '../../hooks';
1
+ import type { PreparedSeries, PreparedXAxis, PreparedYAxis, RangeSliderState } from '../../hooks';
2
2
  import type { ZoomState } from '../../hooks/useZoom/types';
3
3
  import type { ChartXAxis, ChartYAxis } from '../../types';
4
4
  export declare function getZoomedSeriesData(args: {
@@ -10,3 +10,4 @@ export declare function getZoomedSeriesData(args: {
10
10
  preparedSeries: PreparedSeries[];
11
11
  preparedShapesSeries: PreparedSeries[];
12
12
  };
13
+ export declare function getEffectiveXRange(zoomStateX: [number, number] | undefined, rangeSliderState: RangeSliderState | undefined): [number, number] | undefined;
@@ -123,3 +123,12 @@ export function getZoomedSeriesData(args) {
123
123
  preparedShapesSeries: zoomedShapesSeriesData,
124
124
  };
125
125
  }
126
+ export function getEffectiveXRange(zoomStateX, rangeSliderState) {
127
+ if (zoomStateX && rangeSliderState) {
128
+ return [
129
+ Math.max(zoomStateX[0], rangeSliderState.min),
130
+ Math.min(zoomStateX[1], rangeSliderState.max),
131
+ ];
132
+ }
133
+ return (zoomStateX !== null && zoomStateX !== void 0 ? zoomStateX : (rangeSliderState ? [rangeSliderState.min, rangeSliderState.max] : undefined));
134
+ }
@@ -32,7 +32,6 @@ export const AxisX = (props) => {
32
32
  .attr('class', b('title'))
33
33
  .append('text')
34
34
  .attr('text-anchor', 'start')
35
- .style('dominant-baseline', 'text-after-edge')
36
35
  .style('transform', `translate(${preparedAxisData.title.x}px, ${preparedAxisData.title.y}px) rotate(${preparedAxisData.title.rotate}deg) translate(0px, ${preparedAxisData.title.offset}px)`)
37
36
  .attr('font-size', preparedAxisData.title.style.fontSize)
38
37
  .selectAll('tspan')
@@ -1,6 +1,7 @@
1
1
  import { getUniqId } from '@gravity-ui/uikit';
2
- import { calculateSin, formatAxisTickLabel, getBandsPosition, getLabelsSize, getMinSpaceBetween, getTextSizeFn, getTextWithElipsis, wrapText, } from '../../utils';
2
+ import { calculateSin, formatAxisTickLabel, getBandsPosition, getLabelsSize, getMinSpaceBetween, getTextSizeFn, getTextWithElipsis, } from '../../utils';
3
3
  import { getXAxisTickValues } from '../../utils/chart/axis/x-axis';
4
+ import { getMultilineTitleContentRows } from '../utils/axis-title';
4
5
  async function getSvgAxisLabel({ getTextSize, text, axis, top, left, labelMaxWidth, axisWidth, boundsOffsetLeft, boundsOffsetRight, }) {
5
6
  var _a;
6
7
  const rotation = axis.labels.rotation;
@@ -189,23 +190,7 @@ export async function prepareXAxisData({ axis, boundsOffsetLeft, boundsOffsetRig
189
190
  const titleContent = [];
190
191
  const titleMaxWidth = axisWidth;
191
192
  if (axis.title.maxRowCount > 1) {
192
- const titleTextRows = await wrapText({
193
- text: axis.title.text,
194
- style: axis.title.style,
195
- width: titleMaxWidth,
196
- getTextSize: getTitleTextSize,
197
- });
198
- for (let i = 0; i < axis.title.maxRowCount && i < titleTextRows.length; i++) {
199
- const textRow = titleTextRows[i];
200
- const textRowContent = textRow.text.trim();
201
- const textRowSize = await getTitleTextSize(textRowContent);
202
- titleContent.push({
203
- text: textRowContent,
204
- x: 0,
205
- y: textRow.y,
206
- size: textRowSize,
207
- });
208
- }
193
+ titleContent.push(...(await getMultilineTitleContentRows({ axis, titleMaxWidth })));
209
194
  }
210
195
  else {
211
196
  const text = await getTextWithElipsis({
@@ -245,11 +230,7 @@ export async function prepareXAxisData({ axis, boundsOffsetLeft, boundsOffsetRig
245
230
  style: axis.title.style,
246
231
  size: titleTextSize,
247
232
  x,
248
- y: height +
249
- axis.labels.margin +
250
- axis.labels.height +
251
- axis.title.margin +
252
- titleTextSize.height,
233
+ y: height + axis.labels.margin + axis.labels.height + axis.title.margin,
253
234
  rotate: 0,
254
235
  offset: 0,
255
236
  };
@@ -1,14 +1,6 @@
1
1
  import type { DashStyle } from 'src/constants';
2
2
  import type { BaseTextStyle, HtmlItem, PlotLayerPlacement, PointPosition } from '../../types';
3
- export type TextRowData = {
4
- text: string;
5
- x: number;
6
- y: number;
7
- size: {
8
- width: number;
9
- height: number;
10
- };
11
- };
3
+ import type { TextRowData } from '../types';
12
4
  export type AxisSvgLabelData = {
13
5
  x: number;
14
6
  y: number;
@@ -1,4 +1,5 @@
1
- import { calculateCos, calculateSin, getLabelsSize, getTextSizeFn, getTextWithElipsis, wrapText, } from '../../utils';
1
+ import { calculateCos, calculateSin, getLabelsSize, getTextSizeFn, getTextWithElipsis, } from '../../utils';
2
+ import { getMultilineTitleContentRows } from '../utils/axis-title';
2
3
  export async function prepareSvgYAxisTitle({ axis, axisTop, axisHeight, axisWidth, axisLabelsWidth, }) {
3
4
  if (!axis.title.text || axis.title.html) {
4
5
  return null;
@@ -10,39 +11,7 @@ export async function prepareSvgYAxisTitle({ axis, axisTop, axisHeight, axisWidt
10
11
  const titleContent = [];
11
12
  const titleMaxWidth = rotateAngle === 0 ? axis.title.maxWidth : sin * axisHeight;
12
13
  if (axis.title.maxRowCount > 1) {
13
- let titleTextRows = await wrapText({
14
- text: axis.title.text,
15
- style: axis.title.style,
16
- width: titleMaxWidth,
17
- getTextSize: getTitleTextSize,
18
- });
19
- titleTextRows = titleTextRows.reduce((acc, row, index) => {
20
- if (index < axis.title.maxRowCount) {
21
- acc.push(row);
22
- }
23
- else {
24
- acc[axis.title.maxRowCount - 1].text += row.text;
25
- }
26
- return acc;
27
- }, []);
28
- for (let i = 0; i < titleTextRows.length; i++) {
29
- const textRow = titleTextRows[i];
30
- let textRowContent = textRow.text.trim();
31
- if (i === titleTextRows.length - 1) {
32
- textRowContent = await getTextWithElipsis({
33
- text: textRowContent,
34
- maxWidth: titleMaxWidth,
35
- getTextWidth: async (s) => (await getTitleTextSize(s)).width,
36
- });
37
- }
38
- const textRowSize = await getTitleTextSize(textRowContent);
39
- titleContent.push({
40
- text: textRowContent,
41
- x: 0,
42
- y: textRow.y,
43
- size: textRowSize,
44
- });
45
- }
14
+ titleContent.push(...(await getMultilineTitleContentRows({ axis, titleMaxWidth })));
46
15
  }
47
16
  else {
48
17
  const text = await getTextWithElipsis({
@@ -1,14 +1,6 @@
1
1
  import type { DashStyle } from 'src/constants';
2
2
  import type { BaseTextStyle, HtmlItem, PlotLayerPlacement, PointPosition } from '../../types';
3
- export type TextRowData = {
4
- text: string;
5
- x: number;
6
- y: number;
7
- size: {
8
- width: number;
9
- height: number;
10
- };
11
- };
3
+ import type { TextRowData } from '../types';
12
4
  export type AxisSvgLabelData = {
13
5
  x: number;
14
6
  y: number;
@@ -26,7 +26,7 @@ import './styles.css';
26
26
  const b = block('chart');
27
27
  const DEBOUNCED_VALUE_DELAY = 10;
28
28
  export const ChartInner = (props) => {
29
- var _a, _b, _c, _d, _e, _f;
29
+ var _a, _b, _c, _d, _e;
30
30
  const { width, height, data, onReady } = props;
31
31
  const svgRef = React.useRef(null);
32
32
  const resetZoomButtonRef = React.useRef(null);
@@ -60,13 +60,13 @@ export const ChartInner = (props) => {
60
60
  }, [data.xAxis]);
61
61
  const { initialized, setInitialized, tooltipPinned, togglePinTooltip, unpinTooltip, rangeSliderState, updateRangeSliderState, updateZoomState, zoomState, } = useChartInnerState({
62
62
  dispatcher,
63
- preparedChart,
64
63
  preparedRangeSlider,
65
64
  tooltip: preparedTooltip,
66
65
  });
67
66
  const { allPreparedSeries, boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, isOutsideBounds, legendConfig, legendItems, preparedLegend, preparedSeries, preparedSeriesOptions, preparedSplit, prevHeight, prevWidth, shapes, shapesData, shapesReady, xAxis, xScale, yAxis, yScale, } = useChartInnerProps(Object.assign(Object.assign({}, props), { clipPathId,
68
67
  dispatcher,
69
- htmlLayout, plotNode: plotRef.current, preparedChart, rangeSliderDomain: (_a = rangeSliderRef.current) === null || _a === void 0 ? void 0 : _a.getDomain(), rangeSliderState, svgContainer: svgRef.current, updateZoomState,
68
+ htmlLayout, plotNode: plotRef.current, preparedChart,
69
+ rangeSliderState, svgContainer: svgRef.current, updateZoomState,
70
70
  zoomState }));
71
71
  const debouncedBoundsWidth = useDebouncedValue({
72
72
  value: boundsWidth,
@@ -96,8 +96,8 @@ export const ChartInner = (props) => {
96
96
  tooltipThrottle: preparedTooltip.throttle,
97
97
  isOutsideBounds,
98
98
  });
99
- const clickHandler = (_c = (_b = data.chart) === null || _b === void 0 ? void 0 : _b.events) === null || _c === void 0 ? void 0 : _c.click;
100
- const pointerMoveHandler = (_e = (_d = data.chart) === null || _d === void 0 ? void 0 : _d.events) === null || _e === void 0 ? void 0 : _e.pointermove;
99
+ const clickHandler = (_b = (_a = data.chart) === null || _a === void 0 ? void 0 : _a.events) === null || _b === void 0 ? void 0 : _b.click;
100
+ const pointerMoveHandler = (_d = (_c = data.chart) === null || _c === void 0 ? void 0 : _c.events) === null || _d === void 0 ? void 0 : _d.pointermove;
101
101
  const prevRangeSliderDefaultRange = usePrevious(preparedRangeSlider.defaultRange);
102
102
  useCrosshair({
103
103
  split: preparedSplit,
@@ -246,7 +246,7 @@ export const ChartInner = (props) => {
246
246
  React.createElement("g", { ref: plotBeforeRef }),
247
247
  shapes,
248
248
  React.createElement("g", { ref: plotAfterRef })),
249
- ((_f = xAxis === null || xAxis === void 0 ? void 0 : xAxis.rangeSlider) === null || _f === void 0 ? void 0 : _f.enabled) && (React.createElement(RangeSlider, { boundsOffsetLeft: debouncedOffsetLeft, boundsWidth: debouncedBoundsWidth, height: height, htmlLayout: htmlLayout, onUpdate: updateRangeSliderState, preparedChart: preparedChart, preparedLegend: preparedLegend, preparedSeries: debouncedAllPreparedSeries, preparedSeriesOptions: preparedSeriesOptions, preparedRangeSlider: xAxis.rangeSlider, rangeSliderState: rangeSliderState, ref: rangeSliderRef, width: width, xAxis: data.xAxis, yAxis: data.yAxis })),
249
+ ((_e = xAxis === null || xAxis === void 0 ? void 0 : xAxis.rangeSlider) === null || _e === void 0 ? void 0 : _e.enabled) && (React.createElement(RangeSlider, { boundsOffsetLeft: debouncedOffsetLeft, boundsWidth: debouncedBoundsWidth, height: height, htmlLayout: htmlLayout, onUpdate: updateRangeSliderState, preparedChart: preparedChart, preparedLegend: preparedLegend, preparedSeries: debouncedAllPreparedSeries, preparedSeriesOptions: preparedSeriesOptions, preparedRangeSlider: xAxis.rangeSlider, rangeSliderState: rangeSliderState, ref: rangeSliderRef, width: width, xAxis: data.xAxis, yAxis: data.yAxis })),
250
250
  (preparedLegend === null || preparedLegend === void 0 ? void 0 : preparedLegend.enabled) && legendConfig && (React.createElement(Legend, { chartSeries: preparedSeries, legend: preparedLegend, items: legendItems, config: legendConfig, onItemClick: handleLegendItemClick, onUpdate: unpinTooltip, htmlLayout: htmlLayout }))));
251
251
  return (React.createElement("div", { className: b() },
252
252
  React.createElement("svg", { ref: svgRef, width: width, height: height,
@@ -11,7 +11,6 @@ type Props = ChartInnerProps & {
11
11
  svgContainer: SVGGElement | null;
12
12
  updateZoomState: (nextZoomState: Partial<ZoomState>) => void;
13
13
  zoomState: Partial<ZoomState>;
14
- rangeSliderDomain?: [number, number];
15
14
  rangeSliderState?: RangeSliderState;
16
15
  };
17
16
  export declare function useChartInnerProps(props: Props): {
@@ -4,7 +4,7 @@ import { useAxis, useAxisScales, useChartDimensions, useNormalizedOriginalData,
4
4
  import { getYAxisWidth } from '../../hooks/useChartDimensions/utils';
5
5
  import { getLegendComponents } from '../../hooks/useSeries/prepare-legend';
6
6
  import { getPreparedOptions } from '../../hooks/useSeries/prepare-options';
7
- import { getZoomedSeriesData } from '../../utils';
7
+ import { getEffectiveXRange, getZoomedSeriesData } from '../../utils';
8
8
  import { hasAtLeastOneSeriesDataPerPlot } from './utils';
9
9
  const CLIP_PATH_BY_SERIES_TYPE = {
10
10
  [SERIES_TYPE.Scatter]: false,
@@ -35,7 +35,7 @@ function getBoundsOffsetLeft(args) {
35
35
  }
36
36
  export function useChartInnerProps(props) {
37
37
  var _a;
38
- const { clipPathId, data, dispatcher, height, htmlLayout, plotNode, preparedChart, rangeSliderDomain, rangeSliderState, svgContainer, width, updateZoomState, zoomState, } = props;
38
+ const { clipPathId, data, dispatcher, height, htmlLayout, plotNode, preparedChart, rangeSliderState, svgContainer, width, updateZoomState, zoomState, } = props;
39
39
  const prevWidth = usePrevious(width);
40
40
  const prevHeight = usePrevious(height);
41
41
  const colors = React.useMemo(() => {
@@ -57,14 +57,25 @@ export function useChartInnerProps(props) {
57
57
  seriesData: normalizedSeriesData,
58
58
  seriesOptions: data.series.options,
59
59
  });
60
+ const effectiveZoomState = React.useMemo(() => {
61
+ const result = {};
62
+ const effectiveX = getEffectiveXRange(zoomState.x, rangeSliderState);
63
+ if (effectiveX !== undefined) {
64
+ result.x = effectiveX;
65
+ }
66
+ if (zoomState.y !== undefined) {
67
+ result.y = zoomState.y;
68
+ }
69
+ return result;
70
+ }, [zoomState, rangeSliderState]);
60
71
  const { preparedSeries, preparedShapesSeries } = React.useMemo(() => {
61
72
  return getZoomedSeriesData({
62
73
  seriesData: allPreparedSeries,
63
74
  xAxis: normalizedXAxis,
64
75
  yAxis: normalizedYAxis,
65
- zoomState,
76
+ zoomState: effectiveZoomState,
66
77
  });
67
- }, [allPreparedSeries, normalizedXAxis, normalizedYAxis, zoomState]);
78
+ }, [allPreparedSeries, normalizedXAxis, normalizedYAxis, effectiveZoomState]);
68
79
  const { legendConfig, legendItems } = React.useMemo(() => {
69
80
  if (!preparedLegend) {
70
81
  return { legendConfig: undefined, legendItems: [] };
@@ -126,7 +137,7 @@ export function useChartInnerProps(props) {
126
137
  htmlLayout,
127
138
  clipPathId,
128
139
  isOutsideBounds,
129
- zoomState,
140
+ zoomState: effectiveZoomState,
130
141
  });
131
142
  const handleAttemptToSetZoomState = React.useCallback((nextZoomState) => {
132
143
  const { preparedSeries: nextZoomedSeriesData } = getZoomedSeriesData({
@@ -147,7 +158,6 @@ export function useChartInnerProps(props) {
147
158
  plotContainerWidth: boundsWidth,
148
159
  preparedSplit,
149
160
  preparedZoom: preparedChart.zoom,
150
- rangeSliderDomain,
151
161
  xAxis,
152
162
  xScale,
153
163
  yAxis,