@gravity-ui/charts 1.20.0 → 1.22.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 (87) hide show
  1. package/dist/cjs/components/AxisY/AxisY.js +8 -1
  2. package/dist/cjs/components/AxisY/prepare-axis-data.js +39 -12
  3. package/dist/cjs/components/AxisY/types.d.ts +3 -0
  4. package/dist/cjs/components/ChartInner/index.js +23 -8
  5. package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +24 -13
  6. package/dist/cjs/components/ChartInner/useChartInnerProps.js +41 -107
  7. package/dist/cjs/components/ChartInner/useChartInnerState.d.ts +4 -2
  8. package/dist/cjs/components/ChartInner/useChartInnerState.js +9 -0
  9. package/dist/cjs/components/ChartInner/utils.d.ts +12 -3
  10. package/dist/cjs/components/ChartInner/utils.js +61 -1
  11. package/dist/cjs/components/Title/index.d.ts +0 -1
  12. package/dist/cjs/components/Title/index.js +6 -4
  13. package/dist/cjs/hooks/index.d.ts +7 -3
  14. package/dist/cjs/hooks/index.js +7 -3
  15. package/dist/cjs/hooks/useAxis/index.d.ts +19 -0
  16. package/dist/cjs/hooks/useAxis/index.js +63 -0
  17. package/dist/cjs/hooks/useChartOptions/chart.js +6 -1
  18. package/dist/cjs/hooks/useChartOptions/index.d.ts +1 -4
  19. package/dist/cjs/hooks/useChartOptions/index.js +2 -5
  20. package/dist/cjs/hooks/useChartOptions/title.js +4 -2
  21. package/dist/cjs/hooks/useChartOptions/types.d.ts +0 -1
  22. package/dist/cjs/hooks/useChartOptions/utils.d.ts +1 -4
  23. package/dist/cjs/hooks/useChartOptions/utils.js +29 -6
  24. package/dist/cjs/hooks/useChartOptions/x-axis.js +2 -2
  25. package/dist/cjs/hooks/useChartOptions/y-axis.js +10 -11
  26. package/dist/cjs/hooks/useNormalizedOriginalData/index.d.ts +40 -0
  27. package/dist/cjs/hooks/useNormalizedOriginalData/index.js +33 -0
  28. package/dist/cjs/hooks/useSeries/index.d.ts +0 -9
  29. package/dist/cjs/hooks/useSeries/index.js +0 -18
  30. package/dist/cjs/hooks/useSeries/types.d.ts +3 -0
  31. package/dist/cjs/hooks/useShapes/bar-x/prepare-data.js +4 -0
  32. package/dist/cjs/hooks/useShapes/bar-y/prepare-data.js +4 -0
  33. package/dist/cjs/hooks/useShapes/scatter/prepare-data.d.ts +2 -2
  34. package/dist/cjs/hooks/useShapes/scatter/prepare-data.js +40 -5
  35. package/dist/cjs/types/chart/axis.d.ts +20 -2
  36. package/dist/cjs/types/chart/zoom.d.ts +29 -0
  37. package/dist/cjs/utils/chart/get-closest-data.js +1 -1
  38. package/dist/cjs/utils/chart/series/sorting.d.ts +2 -2
  39. package/dist/cjs/utils/chart/series/sorting.js +3 -3
  40. package/dist/cjs/utils/chart/text.js +24 -21
  41. package/dist/cjs/utils/chart/zoom.d.ts +7 -6
  42. package/dist/cjs/utils/chart/zoom.js +14 -6
  43. package/dist/esm/components/AxisY/AxisY.js +8 -1
  44. package/dist/esm/components/AxisY/prepare-axis-data.js +39 -12
  45. package/dist/esm/components/AxisY/types.d.ts +3 -0
  46. package/dist/esm/components/ChartInner/index.js +23 -8
  47. package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +23 -12
  48. package/dist/esm/components/ChartInner/useChartInnerProps.js +41 -107
  49. package/dist/esm/components/ChartInner/useChartInnerState.d.ts +4 -2
  50. package/dist/esm/components/ChartInner/useChartInnerState.js +9 -0
  51. package/dist/esm/components/ChartInner/utils.d.ts +12 -3
  52. package/dist/esm/components/ChartInner/utils.js +61 -1
  53. package/dist/esm/components/Title/index.d.ts +0 -1
  54. package/dist/esm/components/Title/index.js +6 -4
  55. package/dist/esm/hooks/index.d.ts +7 -3
  56. package/dist/esm/hooks/index.js +7 -3
  57. package/dist/esm/hooks/useAxis/index.d.ts +19 -0
  58. package/dist/esm/hooks/useAxis/index.js +63 -0
  59. package/dist/esm/hooks/useChartOptions/chart.js +6 -1
  60. package/dist/esm/hooks/useChartOptions/index.d.ts +1 -4
  61. package/dist/esm/hooks/useChartOptions/index.js +2 -5
  62. package/dist/esm/hooks/useChartOptions/title.js +4 -2
  63. package/dist/esm/hooks/useChartOptions/types.d.ts +0 -1
  64. package/dist/esm/hooks/useChartOptions/utils.d.ts +1 -4
  65. package/dist/esm/hooks/useChartOptions/utils.js +29 -6
  66. package/dist/esm/hooks/useChartOptions/x-axis.js +2 -2
  67. package/dist/esm/hooks/useChartOptions/y-axis.js +10 -11
  68. package/dist/esm/hooks/useNormalizedOriginalData/index.d.ts +40 -0
  69. package/dist/esm/hooks/useNormalizedOriginalData/index.js +33 -0
  70. package/dist/esm/hooks/useSeries/index.d.ts +0 -9
  71. package/dist/esm/hooks/useSeries/index.js +0 -18
  72. package/dist/esm/hooks/useSeries/types.d.ts +3 -0
  73. package/dist/esm/hooks/useShapes/bar-x/prepare-data.js +4 -0
  74. package/dist/esm/hooks/useShapes/bar-y/prepare-data.js +4 -0
  75. package/dist/esm/hooks/useShapes/scatter/prepare-data.d.ts +2 -2
  76. package/dist/esm/hooks/useShapes/scatter/prepare-data.js +40 -5
  77. package/dist/esm/types/chart/axis.d.ts +20 -2
  78. package/dist/esm/types/chart/zoom.d.ts +29 -0
  79. package/dist/esm/utils/chart/get-closest-data.js +1 -1
  80. package/dist/esm/utils/chart/series/sorting.d.ts +2 -2
  81. package/dist/esm/utils/chart/series/sorting.js +3 -3
  82. package/dist/esm/utils/chart/text.js +24 -21
  83. package/dist/esm/utils/chart/zoom.d.ts +7 -6
  84. package/dist/esm/utils/chart/zoom.js +14 -6
  85. package/package.json +7 -16
  86. package/dist/cjs/components/Title/styles.css +0 -5
  87. package/dist/esm/components/Title/styles.css +0 -5
@@ -105,7 +105,7 @@ export function getClosestPoints(args) {
105
105
  case 'bar-y': {
106
106
  const points = list;
107
107
  const sorted = sort(points, (p) => p.y);
108
- const closestYIndex = bisector((p) => p.y).center(sorted, pointerY);
108
+ const closestYIndex = bisector((p) => p.y + p.height / 2).center(sorted, pointerY);
109
109
  const closestYPoint = sorted[closestYIndex];
110
110
  let selectedPoints = [];
111
111
  let closestPointXValue = -1;
@@ -1,6 +1,6 @@
1
1
  import type { ChartAxis, ChartSeries } from '../../../types';
2
- export declare function getSortedSeriesData({ seriesData, yAxes, xAxis, }: {
2
+ export declare function getSortedSeriesData({ seriesData, xAxis, yAxis, }: {
3
3
  seriesData: ChartSeries[];
4
- yAxes?: ChartAxis[];
5
4
  xAxis?: ChartAxis;
5
+ yAxis?: ChartAxis[];
6
6
  }): ChartSeries[];
@@ -20,11 +20,11 @@ function applyAxisCategoriesOrder({ series, axis, key, }) {
20
20
  });
21
21
  return Object.assign(Object.assign({}, series), { data: newSeriesData });
22
22
  }
23
- export function getSortedSeriesData({ seriesData, yAxes, xAxis, }) {
23
+ export function getSortedSeriesData({ seriesData, xAxis, yAxis, }) {
24
24
  return seriesData.map((s) => {
25
- const yAxis = yAxes === null || yAxes === void 0 ? void 0 : yAxes[0];
25
+ const yAxisItem = yAxis === null || yAxis === void 0 ? void 0 : yAxis[0];
26
26
  let sortedSeries = s;
27
- sortedSeries = applyAxisCategoriesOrder({ series: sortedSeries, axis: yAxis, key: 'y' });
27
+ sortedSeries = applyAxisCategoriesOrder({ series: sortedSeries, axis: yAxisItem, key: 'y' });
28
28
  sortedSeries = applyAxisCategoriesOrder({ series: sortedSeries, axis: xAxis, key: 'x' });
29
29
  switch (sortedSeries.type) {
30
30
  case SeriesType.Area: {
@@ -1,4 +1,6 @@
1
1
  import { select } from 'd3-selection';
2
+ import { block } from '../cn';
3
+ const b = block('chart');
2
4
  export function handleOverflowingText(tSpan, maxWidth, textWidth) {
3
5
  var _a, _b, _c;
4
6
  if (!tSpan) {
@@ -186,29 +188,30 @@ function unescapeHtml(str) {
186
188
  return result.replace(value, key);
187
189
  }, str);
188
190
  }
191
+ function getCssStyle(prop, el = document.body) {
192
+ return window.getComputedStyle(el, null).getPropertyValue(prop);
193
+ }
194
+ let measureCanvas = null;
189
195
  export function getTextSizeFn({ style }) {
190
- const map = {};
191
- const setSymbolSize = async (s) => {
192
- const labels = [s === ' ' ? ' ' : s];
193
- const size = await getLabelsSize({
194
- labels,
195
- style,
196
- });
197
- map[s] = { width: size.maxWidth, height: size.maxHeight };
198
- };
196
+ var _a;
197
+ const canvas = measureCanvas || (measureCanvas = document.createElement('canvas'));
198
+ const context = canvas.getContext('2d');
199
+ if (!context) {
200
+ throw new Error("Couldn't get canvas context");
201
+ }
202
+ const element = (_a = document.getElementsByClassName(b())[0]) !== null && _a !== void 0 ? _a : document.body;
203
+ const defaultFontFamily = getCssStyle('font-family', element);
204
+ const defaultFontSize = getCssStyle('font-size', element);
205
+ const defaultFontWeight = getCssStyle('font-weight', element);
199
206
  return async (str) => {
200
- let width = 0;
201
- let height = 0;
202
- const symbols = unescapeHtml(str);
203
- for (let i = 0; i < symbols.length; i++) {
204
- const s = symbols[i];
205
- if (!map[s]) {
206
- await setSymbolSize(s);
207
- }
208
- width += map[s].width;
209
- height = Math.max(height, map[s].height);
210
- }
211
- return { width, height };
207
+ var _a, _b;
208
+ await document.fonts.ready;
209
+ context.font = `${(_a = style === null || style === void 0 ? void 0 : style.fontWeight) !== null && _a !== void 0 ? _a : defaultFontWeight} ${(_b = style === null || style === void 0 ? void 0 : style.fontSize) !== null && _b !== void 0 ? _b : defaultFontSize} ${defaultFontFamily}`;
210
+ const textMetric = context.measureText(unescapeHtml(str));
211
+ return {
212
+ width: textMetric.width,
213
+ height: textMetric.fontBoundingBoxDescent + textMetric.fontBoundingBoxAscent,
214
+ };
212
215
  };
213
216
  }
214
217
  // We ignore an inaccuracy of less than a pixel.
@@ -1,11 +1,12 @@
1
+ import type { PreparedAxis, PreparedSeries } from '../../hooks';
1
2
  import type { ZoomState } from '../../hooks/useZoom/types';
2
- import type { ChartSeries, ChartXAxis, ChartYAxis } from '../../types';
3
+ import type { ChartXAxis, ChartYAxis } from '../../types';
3
4
  export declare function getZoomedSeriesData(args: {
4
- seriesData: ChartSeries[];
5
+ seriesData: PreparedSeries[];
5
6
  zoomState: Partial<ZoomState>;
6
- xAxis?: ChartXAxis;
7
- yAxes?: ChartYAxis[];
7
+ xAxis?: ChartXAxis | PreparedAxis | null;
8
+ yAxis?: ChartYAxis[] | PreparedAxis[] | null;
8
9
  }): {
9
- zoomedSeriesData: ChartSeries[];
10
- zoomedShapesSeriesData: ChartSeries[];
10
+ preparedSeries: PreparedSeries[];
11
+ preparedShapesSeries: PreparedSeries[];
11
12
  };
@@ -1,5 +1,4 @@
1
1
  import { SeriesType } from '../../constants';
2
- import { getAxisCategories } from '../../hooks/useChartOptions/utils';
3
2
  const SERIES_TYPE_WITH_HIDDEN_POINTS = [SeriesType.Area, SeriesType.Line];
4
3
  // eslint-disable-next-line complexity
5
4
  function isValueInRange(args) {
@@ -21,7 +20,7 @@ function isValueInRange(args) {
21
20
  return numValue >= numMin && numValue <= numMax;
22
21
  }
23
22
  case 'category': {
24
- const categories = getAxisCategories(axis) || [];
23
+ const categories = (axis === null || axis === void 0 ? void 0 : axis.categories) || [];
25
24
  if (typeof value === 'string' && typeof min === 'number' && typeof max === 'number') {
26
25
  const valueIndex = categories.indexOf(value);
27
26
  if (min === -1 || max === -1 || valueIndex === -1) {
@@ -42,10 +41,13 @@ function isValueInRange(args) {
42
41
  }
43
42
  }
44
43
  }
44
+ function isPreparedZoomableSeries(series) {
45
+ return Array.isArray(series.data);
46
+ }
45
47
  export function getZoomedSeriesData(args) {
46
- const { seriesData, xAxis, yAxes, zoomState } = args;
48
+ const { seriesData, xAxis, yAxis, zoomState } = args;
47
49
  if (Object.keys(zoomState).length <= 0) {
48
- return { zoomedSeriesData: seriesData, zoomedShapesSeriesData: seriesData };
50
+ return { preparedSeries: seriesData, preparedShapesSeries: seriesData };
49
51
  }
50
52
  const zoomedSeriesData = [];
51
53
  const zoomedShapesSeriesData = [];
@@ -56,6 +58,9 @@ export function getZoomedSeriesData(args) {
56
58
  const filteredShapesData = SERIES_TYPE_WITH_HIDDEN_POINTS.includes(seriesItem.type) && (xAxis === null || xAxis === void 0 ? void 0 : xAxis.type) !== 'category'
57
59
  ? []
58
60
  : undefined;
61
+ if (!isPreparedZoomableSeries(seriesItem)) {
62
+ return;
63
+ }
59
64
  seriesItem.data.forEach((point, i) => {
60
65
  const prevPoint = seriesItem.data[i - 1];
61
66
  const isFirstPoint = i === 0;
@@ -81,7 +86,7 @@ export function getZoomedSeriesData(args) {
81
86
  const [yMin, yMax] = zoomStateY;
82
87
  const y = 'y' in point ? point.y : undefined;
83
88
  inYRange = isValueInRange({
84
- axis: yAxes === null || yAxes === void 0 ? void 0 : yAxes[yAxisIndex],
89
+ axis: yAxis === null || yAxis === void 0 ? void 0 : yAxis[yAxisIndex],
85
90
  value: y,
86
91
  min: yMin,
87
92
  max: yMax,
@@ -111,5 +116,8 @@ export function getZoomedSeriesData(args) {
111
116
  zoomedSeriesData.push(Object.assign(Object.assign({}, seriesItem), { data: filteredData }));
112
117
  zoomedShapesSeriesData.push(Object.assign(Object.assign({}, seriesItem), { data: filteredShapesData || filteredData }));
113
118
  });
114
- return { zoomedSeriesData, zoomedShapesSeriesData };
119
+ return {
120
+ preparedSeries: zoomedSeriesData,
121
+ preparedShapesSeries: zoomedShapesSeriesData,
122
+ };
115
123
  }
@@ -67,7 +67,14 @@ export const AxisY = (props) => {
67
67
  }
68
68
  if (tickData.svgLabel) {
69
69
  const label = tickData.svgLabel;
70
- const textSelection = tickSelection.append('text');
70
+ const textSelection = tickSelection
71
+ .append('text')
72
+ .style('transform', [
73
+ `translate(${label.x}px, ${label.y}px)`,
74
+ label.angle ? `rotate(${label.angle}deg)` : '',
75
+ ]
76
+ .filter(Boolean)
77
+ .join(' '));
71
78
  if (label.title) {
72
79
  textSelection.append('title').html(label.title);
73
80
  }
@@ -9,15 +9,19 @@ async function getSvgAxisLabel({ getTextSize, text, axis, top, left, labelMaxHei
9
9
  const labelMaxWidth = axis.labels.width; //axis.labels.maxWidth;
10
10
  const size = originalTextSize;
11
11
  const content = [];
12
- // Warp label text only for categories - it will look strange for numbers or dates.
13
- if (originalTextSize.width > labelMaxWidth && axis.type === 'category') {
12
+ // line breaks in the text are supported only
13
+ // 1. for the category axis - it will look strange for numbers or dates
14
+ // 2. for labels without rotation - it is unclear how to handle long strings correctly
15
+ if (originalTextSize.width > labelMaxWidth &&
16
+ axis.type === 'category' &&
17
+ axis.labels.rotation === 0) {
14
18
  const textRows = await wrapText({
15
19
  text,
16
20
  style: axis.labels.style,
17
21
  width: labelMaxWidth,
18
22
  getTextSize,
19
23
  });
20
- let labelTopOffset = top;
24
+ let labelTopOffset = 0;
21
25
  let newLabelWidth = 0;
22
26
  let newLabelHeight = 0;
23
27
  for (let textRowIndex = 0; textRowIndex < textRows.length; textRowIndex++) {
@@ -43,9 +47,7 @@ async function getSvgAxisLabel({ getTextSize, text, axis, top, left, labelMaxHei
43
47
  });
44
48
  textSize = await getTextSize(rowText);
45
49
  }
46
- const x = axis.position === 'left'
47
- ? left - textSize.width - axis.labels.margin
48
- : left + axis.labels.margin;
50
+ const x = axis.position === 'left' ? -textSize.width : 0;
49
51
  content.push({
50
52
  text: rowText,
51
53
  x,
@@ -62,21 +64,46 @@ async function getSvgAxisLabel({ getTextSize, text, axis, top, left, labelMaxHei
62
64
  size.height = newLabelHeight;
63
65
  }
64
66
  else {
65
- const x = axis.position === 'left'
66
- ? left - size.width - axis.labels.margin
67
- : left + axis.labels.margin;
67
+ let rowText = text;
68
+ let textSize = await getTextSize(rowText);
69
+ const textMaxWidth = Math.min(labelMaxWidth / calculateCos(axis.labels.rotation) -
70
+ textSize.height * calculateCos(90 - axis.labels.rotation),
71
+ // for vertical labels, we need to take into account the available height, otherwise there may be intersections
72
+ axis.labels.rotation === 90 ? labelMaxHeight : Infinity,
73
+ // if there is no rotation, then the height of the label does not affect the width of the text
74
+ axis.labels.rotation === 0
75
+ ? Infinity
76
+ : (top + topOffset - textSize.height / 2) / calculateSin(axis.labels.rotation));
77
+ if (textSize.width > textMaxWidth) {
78
+ rowText = await getTextWithElipsis({
79
+ text: rowText,
80
+ getTextWidth: async (str) => (await getTextSize(str)).width,
81
+ maxWidth: textMaxWidth,
82
+ });
83
+ textSize = await getTextSize(rowText);
84
+ }
85
+ const actualTextHeight = axis.labels.rotation
86
+ ? textSize.height / calculateSin(axis.labels.rotation)
87
+ : textSize.height;
88
+ const x = axis.position === 'left' ? -textSize.width : 0;
89
+ const y = Math.max(-topOffset - top, -actualTextHeight / 2);
68
90
  content.push({
69
- text,
91
+ text: rowText,
70
92
  x,
71
- y: Math.max(-topOffset, top - size.height / 2),
72
- size,
93
+ y,
94
+ size: textSize,
73
95
  });
74
96
  }
97
+ const x = axis.position === 'left' ? left - axis.labels.margin : left + axis.labels.margin;
98
+ const y = top;
75
99
  const svgLabel = {
76
100
  title: content.length > 1 || ((_a = content[0]) === null || _a === void 0 ? void 0 : _a.text) !== text ? text : undefined,
77
101
  content: content,
78
102
  style: axis.labels.style,
79
103
  size: size,
104
+ x,
105
+ y,
106
+ angle: axis.labels.rotation,
80
107
  };
81
108
  return svgLabel;
82
109
  }
@@ -10,6 +10,9 @@ export type TextRowData = {
10
10
  };
11
11
  };
12
12
  export type AxisSvgLabelData = {
13
+ x: number;
14
+ y: number;
15
+ angle: number;
13
16
  content: TextRowData[];
14
17
  title?: string;
15
18
  style: BaseTextStyle;
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  import { ArrowRotateLeft } from '@gravity-ui/icons';
3
3
  import { Button, ButtonIcon, useUniqId } from '@gravity-ui/uikit';
4
4
  import { useCrosshair } from '../../hooks';
5
+ import { getPreparedTooltip } from '../../hooks/useChartOptions/tooltip';
5
6
  import { EventType, block, getDispatcher } from '../../utils';
6
7
  import { AxisX } from '../AxisX/AxisX';
7
8
  import { AxisY } from '../AxisY/AxisY';
@@ -13,25 +14,36 @@ import { Tooltip } from '../Tooltip';
13
14
  import { useChartInnerHandlers } from './useChartInnerHandlers';
14
15
  import { useChartInnerProps } from './useChartInnerProps';
15
16
  import { useChartInnerState } from './useChartInnerState';
16
- import { useAsyncState } from './utils';
17
+ import { getResetZoomButtonStyle, useAsyncState } from './utils';
17
18
  import './styles.css';
18
19
  const b = block('chart');
19
20
  export const ChartInner = (props) => {
20
21
  var _a, _b, _c, _d;
21
22
  const { width, height, data } = props;
22
23
  const svgRef = React.useRef(null);
24
+ const resetZoomButtonRef = React.useRef(null);
23
25
  const [htmlLayout, setHtmlLayout] = React.useState(null);
24
26
  const plotRef = React.useRef(null);
25
27
  const plotBeforeRef = React.useRef(null);
26
28
  const plotAfterRef = React.useRef(null);
27
29
  const dispatcher = React.useMemo(() => getDispatcher(), []);
28
30
  const clipPathId = useUniqId();
29
- const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, handleZoomReset, isOutsideBounds, legendConfig, legendItems, preparedSeries, preparedSplit, preparedLegend, prevHeight, prevWidth, shapes, shapesData, title, tooltip, xAxis, xScale, yAxis, yScale, svgXPos, } = useChartInnerProps(Object.assign(Object.assign({}, props), { dispatcher,
30
- htmlLayout, svgContainer: svgRef.current, plotNode: plotRef.current, clipPathId }));
31
- const { tooltipPinned, togglePinTooltip, unpinTooltip } = useChartInnerState({
31
+ const preparedTooltip = React.useMemo(() => {
32
+ return getPreparedTooltip({
33
+ tooltip: data.tooltip,
34
+ seriesData: data.series.data,
35
+ yAxes: data.yAxis,
36
+ xAxis: data.xAxis,
37
+ });
38
+ }, [data.series.data, data.tooltip, data.yAxis, data.xAxis]);
39
+ const { tooltipPinned, togglePinTooltip, unpinTooltip, updateZoomState, zoomState } = useChartInnerState({
32
40
  dispatcher,
33
- tooltip,
41
+ tooltip: preparedTooltip,
34
42
  });
43
+ const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, isOutsideBounds, legendConfig, legendItems, preparedSeries, preparedSplit, preparedLegend, preparedZoom, prevHeight, prevWidth, shapes, shapesData, svgXPos, title, xAxis, xScale, yAxis, yScale, } = useChartInnerProps(Object.assign(Object.assign({}, props), { clipPathId,
44
+ dispatcher,
45
+ htmlLayout, plotNode: plotRef.current, svgContainer: svgRef.current, updateZoomState,
46
+ zoomState }));
35
47
  const { handleChartClick, handleMouseLeave, throttledHandleMouseMove, throttledHandleTouchMove } = useChartInnerHandlers({
36
48
  boundsHeight,
37
49
  boundsOffsetLeft,
@@ -45,7 +57,7 @@ export const ChartInner = (props) => {
45
57
  unpinTooltip,
46
58
  xAxis,
47
59
  yAxis,
48
- tooltipThrottle: tooltip.throttle,
60
+ tooltipThrottle: preparedTooltip.throttle,
49
61
  isOutsideBounds,
50
62
  });
51
63
  const clickHandler = (_b = (_a = data.chart) === null || _a === void 0 ? void 0 : _a.events) === null || _b === void 0 ? void 0 : _b.click;
@@ -132,8 +144,11 @@ export const ChartInner = (props) => {
132
144
  React.createElement("div", { className: b('html-layer'), ref: setHtmlLayout, style: {
133
145
  '--g-html-layout-transform': `translate(${boundsOffsetLeft}px, ${boundsOffsetTop}px)`,
134
146
  } }),
135
- handleZoomReset && (React.createElement(Button, { style: { position: 'absolute', top: 0, right: 0 }, onClick: handleZoomReset },
147
+ Object.keys(zoomState).length > 0 && preparedZoom && (React.createElement(Button, { onClick: () => updateZoomState({}), ref: resetZoomButtonRef, style: getResetZoomButtonStyle(Object.assign({ boundsHeight,
148
+ boundsOffsetLeft,
149
+ boundsOffsetTop,
150
+ boundsWidth, node: resetZoomButtonRef.current, titleHeight: title === null || title === void 0 ? void 0 : title.height }, preparedZoom.resetButton)) },
136
151
  React.createElement(ButtonIcon, null,
137
152
  React.createElement(ArrowRotateLeft, null)))),
138
- React.createElement(Tooltip, { dispatcher: dispatcher, tooltip: tooltip, svgContainer: svgRef.current, xAxis: xAxis, yAxis: yAxis[0], onOutsideClick: unpinTooltip, tooltipPinned: tooltipPinned })));
153
+ React.createElement(Tooltip, { dispatcher: dispatcher, tooltip: preparedTooltip, svgContainer: svgRef.current, xAxis: xAxis, yAxis: yAxis[0], onOutsideClick: unpinTooltip, tooltipPinned: tooltipPinned })));
139
154
  };
@@ -1,24 +1,23 @@
1
1
  import React from 'react';
2
2
  import type { Dispatch } from 'd3';
3
- import type { PreparedAxis } from '../../hooks';
3
+ import type { ZoomState } from '../../hooks/useZoom/types';
4
4
  import type { ChartInnerProps } from './types';
5
5
  type Props = ChartInnerProps & {
6
+ clipPathId: string;
6
7
  dispatcher: Dispatch<object>;
7
8
  htmlLayout: HTMLElement | null;
8
- svgContainer: SVGGElement | null;
9
9
  plotNode: SVGGElement | null;
10
- clipPathId: string;
10
+ svgContainer: SVGGElement | null;
11
+ updateZoomState: (nextZoomState: Partial<ZoomState>) => void;
12
+ zoomState: Partial<ZoomState>;
11
13
  };
12
14
  export declare function useChartInnerProps(props: Props): {
13
- svgBottomPos: number | undefined;
14
- svgTopPos: number | undefined;
15
15
  svgXPos: number | undefined;
16
16
  boundsHeight: number;
17
17
  boundsOffsetLeft: number;
18
18
  boundsOffsetTop: number;
19
19
  boundsWidth: number;
20
20
  handleLegendItemClick: import("../../hooks").OnLegendItemClick;
21
- handleZoomReset: (() => void) | undefined;
22
21
  isOutsideBounds: (x: number, y: number) => boolean;
23
22
  legendConfig: {
24
23
  offset: {
@@ -37,6 +36,22 @@ export declare function useChartInnerProps(props: Props): {
37
36
  preparedLegend: import("../../hooks").PreparedLegend | null;
38
37
  preparedSeries: import("../../hooks").PreparedSeries[];
39
38
  preparedSplit: import("../../hooks").PreparedSplit;
39
+ preparedZoom: Required<{
40
+ type?: ("x" | "y" | "xy") | undefined;
41
+ brush?: Required<{
42
+ style?: Required<{
43
+ fillOpacity?: number | undefined;
44
+ } | undefined>;
45
+ } | undefined>;
46
+ resetButton?: Required<{
47
+ align?: ("bottom-left" | "bottom-right" | "top-left" | "top-right") | undefined;
48
+ offset?: Required<{
49
+ x?: number | undefined;
50
+ y?: number | undefined;
51
+ } | undefined>;
52
+ relativeTo?: ("chart-box" | "plot-box") | undefined;
53
+ } | undefined>;
54
+ }> | null;
40
55
  prevHeight: number | undefined;
41
56
  prevWidth: number | undefined;
42
57
  shapes: React.ReactElement<any, string | React.JSXElementConstructor<any>>[];
@@ -44,13 +59,9 @@ export declare function useChartInnerProps(props: Props): {
44
59
  title: (import("../..").ChartTitle & {
45
60
  height: number;
46
61
  }) | undefined;
47
- tooltip: import("../..").ChartTooltip<any> & {
48
- enabled: boolean;
49
- throttle: number;
50
- };
51
- xAxis: PreparedAxis | null;
62
+ xAxis: import("../../hooks").PreparedAxis | null;
52
63
  xScale: import("../../hooks").ChartScale | undefined;
53
- yAxis: PreparedAxis[];
64
+ yAxis: import("../../hooks").PreparedAxis[];
54
65
  yScale: (import("../../hooks").ChartScale | undefined)[] | undefined;
55
66
  };
56
67
  export {};
@@ -1,115 +1,45 @@
1
1
  import React from 'react';
2
- import isEqual from 'lodash/isEqual';
3
- import { useAxisScales, useChartDimensions, useChartOptions, usePrevious, useSeries, useShapeSeries, useShapes, useSplit, } from '../../hooks';
2
+ import { useAxis, useAxisScales, useChartDimensions, useChartOptions, useNormalizedOriginalData, usePrevious, useSeries, useShapes, useSplit, } from '../../hooks';
4
3
  import { getYAxisWidth } from '../../hooks/useChartDimensions/utils';
5
- import { getPreparedXAxis } from '../../hooks/useChartOptions/x-axis';
6
- import { getPreparedYAxis } from '../../hooks/useChartOptions/y-axis';
7
4
  import { getLegendComponents } from '../../hooks/useSeries/prepare-legend';
8
5
  import { getPreparedOptions } from '../../hooks/useSeries/prepare-options';
9
- import { getActiveLegendItems } from '../../hooks/useSeries/utils';
10
6
  import { useZoom } from '../../hooks/useZoom';
11
- import { getSortedSeriesData, getZoomedSeriesData } from '../../utils';
7
+ import { getZoomedSeriesData } from '../../utils';
12
8
  import { hasAtLeastOneSeriesDataPerPlot } from './utils';
13
9
  export function useChartInnerProps(props) {
14
10
  var _a;
15
- const { width, height, data, dispatcher, htmlLayout, svgContainer, plotNode, clipPathId } = props;
11
+ const { clipPathId, data, dispatcher, height, htmlLayout, plotNode, svgContainer, width, updateZoomState, zoomState, } = props;
16
12
  const prevWidth = usePrevious(width);
17
13
  const prevHeight = usePrevious(height);
18
- const { chart, title, tooltip, colors } = useChartOptions({
14
+ const { normalizedSeriesData, normalizedXAxis, normalizedYAxis } = useNormalizedOriginalData({
19
15
  seriesData: data.series.data,
16
+ xAxis: data.xAxis,
17
+ yAxis: data.yAxis,
18
+ });
19
+ const { chart, title, colors } = useChartOptions({
20
20
  chart: data.chart,
21
21
  colors: data.colors,
22
+ seriesData: normalizedSeriesData,
22
23
  title: data.title,
23
- tooltip: data.tooltip,
24
- xAxis: data.xAxis,
25
- yAxes: data.yAxis,
26
24
  });
27
25
  const preparedSeriesOptions = React.useMemo(() => {
28
26
  return getPreparedOptions(data.series.options);
29
27
  }, [data.series.options]);
30
- const [zoomState, setZoomState] = React.useState({});
31
- const sortedSeriesData = React.useMemo(() => {
32
- return getSortedSeriesData({ seriesData: data.series.data, yAxes: data.yAxis });
33
- }, [data.series.data, data.yAxis]);
34
- const { zoomedSeriesData, zoomedShapesSeriesData } = React.useMemo(() => {
35
- return getZoomedSeriesData({
36
- seriesData: sortedSeriesData,
37
- xAxis: data.xAxis,
38
- yAxes: data.yAxis,
39
- zoomState,
40
- });
41
- }, [data.xAxis, data.yAxis, sortedSeriesData, zoomState]);
42
- const { preparedSeries, preparedLegend, handleLegendItemClick } = useSeries({
28
+ const { preparedSeries: basePreparedSeries, preparedLegend, handleLegendItemClick, } = useSeries({
43
29
  colors,
44
30
  legend: data.legend,
45
- originalSeriesData: sortedSeriesData,
46
- seriesData: zoomedSeriesData,
31
+ originalSeriesData: normalizedSeriesData,
32
+ seriesData: normalizedSeriesData,
47
33
  seriesOptions: data.series.options,
48
34
  });
49
- // preparing the X and Y axes
50
- const [axesState, setValue] = React.useState({ xAxis: null, yAxis: [] });
51
- const axesStateRunRef = React.useRef(0);
52
- const prevAxesStateValue = React.useRef(axesState);
53
- const axesStateReady = React.useRef(false);
54
- React.useEffect(() => {
55
- axesStateRunRef.current++;
56
- axesStateReady.current = false;
57
- (async function () {
58
- const currentRun = axesStateRunRef.current;
59
- const seriesData = preparedSeries.filter((s) => s.visible);
60
- const xAxis = await getPreparedXAxis({
61
- xAxis: data.xAxis,
62
- width,
63
- seriesData,
64
- seriesOptions: preparedSeriesOptions,
65
- });
66
- let estimatedBoundsHeight = height;
67
- if (xAxis) {
68
- estimatedBoundsHeight =
69
- height -
70
- (xAxis.title.height +
71
- xAxis.title.margin +
72
- xAxis.labels.margin +
73
- xAxis.labels.height +
74
- (preparedLegend ? preparedLegend.height + preparedLegend.margin : 0) +
75
- chart.margin.top +
76
- chart.margin.bottom);
77
- }
78
- const yAxis = await getPreparedYAxis({
79
- height,
80
- boundsHeight: estimatedBoundsHeight,
81
- width,
82
- seriesData,
83
- yAxis: data.yAxis,
84
- });
85
- const newStateValue = { xAxis, yAxis };
86
- if (axesStateRunRef.current === currentRun) {
87
- if (!isEqual(prevAxesStateValue.current, newStateValue)) {
88
- setValue(newStateValue);
89
- prevAxesStateValue.current = newStateValue;
90
- }
91
- axesStateReady.current = true;
92
- }
93
- })();
94
- }, [
95
- chart.margin,
96
- data.xAxis,
97
- data.yAxis,
98
- height,
99
- preparedLegend,
100
- preparedSeries,
101
- preparedSeriesOptions,
102
- width,
103
- ]);
104
- const { xAxis, yAxis } = axesStateReady.current ? axesState : { xAxis: null, yAxis: [] };
105
- const activeLegendItems = React.useMemo(() => getActiveLegendItems(preparedSeries), [preparedSeries]);
106
- const { preparedSeries: preparedShapesSeries } = useShapeSeries({
107
- colors,
108
- seriesData: zoomedShapesSeriesData,
109
- seriesOptions: data.series.options,
110
- activeLegendItems,
111
- preparedLegend,
112
- });
35
+ const { preparedSeries, preparedShapesSeries } = React.useMemo(() => {
36
+ return getZoomedSeriesData({
37
+ seriesData: basePreparedSeries,
38
+ xAxis: normalizedXAxis,
39
+ yAxis: normalizedYAxis,
40
+ zoomState,
41
+ });
42
+ }, [basePreparedSeries, normalizedXAxis, normalizedYAxis, zoomState]);
113
43
  const { legendConfig, legendItems } = React.useMemo(() => {
114
44
  if (!preparedLegend) {
115
45
  return { legendConfig: undefined, legendItems: [] };
@@ -122,14 +52,24 @@ export function useChartInnerProps(props) {
122
52
  preparedLegend,
123
53
  });
124
54
  }, [width, height, chart.margin, preparedSeries, preparedLegend]);
125
- const { boundsWidth, boundsHeight } = useChartDimensions({
55
+ const { xAxis, yAxis } = useAxis({
56
+ height,
57
+ preparedChart: chart,
58
+ preparedLegend,
59
+ preparedSeries,
60
+ preparedSeriesOptions,
126
61
  width,
62
+ xAxis: normalizedXAxis,
63
+ yAxis: normalizedYAxis,
64
+ });
65
+ const { boundsWidth, boundsHeight } = useChartDimensions({
127
66
  height,
128
67
  margin: chart.margin,
129
68
  preparedLegend,
130
- preparedXAxis: xAxis,
131
- preparedYAxis: yAxis,
132
69
  preparedSeries: preparedSeries,
70
+ preparedYAxis: yAxis,
71
+ preparedXAxis: xAxis,
72
+ width,
133
73
  });
134
74
  const preparedSplit = useSplit({ split: data.split, boundsHeight, chartWidth: width });
135
75
  const { xScale, yScale } = useAxisScales({
@@ -162,17 +102,17 @@ export function useChartInnerProps(props) {
162
102
  isOutsideBounds,
163
103
  });
164
104
  const handleAttemptToSetZoomState = React.useCallback((nextZoomState) => {
165
- const { zoomedSeriesData: nextZoomedSeriesData } = getZoomedSeriesData({
166
- seriesData: zoomedSeriesData,
167
- xAxis: data.xAxis,
168
- yAxes: data.yAxis,
105
+ const { preparedSeries: nextZoomedSeriesData } = getZoomedSeriesData({
106
+ seriesData: preparedSeries,
107
+ xAxis,
108
+ yAxis,
169
109
  zoomState: nextZoomState,
170
110
  });
171
111
  const hasData = hasAtLeastOneSeriesDataPerPlot(nextZoomedSeriesData, yAxis);
172
112
  if (hasData) {
173
- setZoomState(nextZoomState);
113
+ updateZoomState(nextZoomState);
174
114
  }
175
- }, [data.xAxis, data.yAxis, yAxis, zoomedSeriesData]);
115
+ }, [xAxis, yAxis, preparedSeries, updateZoomState]);
176
116
  useZoom({
177
117
  node: plotNode,
178
118
  onUpdate: handleAttemptToSetZoomState,
@@ -198,32 +138,26 @@ export function useChartInnerProps(props) {
198
138
  }
199
139
  return acc;
200
140
  }, 0);
201
- const { bottom, top, x } = (_a = svgContainer === null || svgContainer === void 0 ? void 0 : svgContainer.getBoundingClientRect()) !== null && _a !== void 0 ? _a : {};
202
- const handleZoomReset = React.useCallback(() => {
203
- setZoomState({});
204
- }, []);
141
+ const { x } = (_a = svgContainer === null || svgContainer === void 0 ? void 0 : svgContainer.getBoundingClientRect()) !== null && _a !== void 0 ? _a : {};
205
142
  return {
206
- svgBottomPos: bottom,
207
- svgTopPos: top,
208
143
  svgXPos: x,
209
144
  boundsHeight,
210
145
  boundsOffsetLeft,
211
146
  boundsOffsetTop,
212
147
  boundsWidth,
213
148
  handleLegendItemClick,
214
- handleZoomReset: Object.keys(zoomState).length > 0 ? handleZoomReset : undefined,
215
149
  isOutsideBounds,
216
150
  legendConfig,
217
151
  legendItems,
218
152
  preparedLegend,
219
153
  preparedSeries,
220
154
  preparedSplit,
155
+ preparedZoom: chart.zoom,
221
156
  prevHeight,
222
157
  prevWidth,
223
158
  shapes,
224
159
  shapesData,
225
160
  title,
226
- tooltip,
227
161
  xAxis,
228
162
  xScale,
229
163
  yAxis,