@gravity-ui/charts 1.20.0 → 1.21.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 (81) 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 +18 -7
  5. package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +8 -11
  6. package/dist/cjs/components/ChartInner/useChartInnerProps.js +39 -104
  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 +2 -2
  10. package/dist/cjs/components/ChartInner/utils.js +1 -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/index.d.ts +1 -4
  18. package/dist/cjs/hooks/useChartOptions/index.js +2 -5
  19. package/dist/cjs/hooks/useChartOptions/title.js +4 -2
  20. package/dist/cjs/hooks/useChartOptions/types.d.ts +0 -1
  21. package/dist/cjs/hooks/useChartOptions/utils.d.ts +1 -4
  22. package/dist/cjs/hooks/useChartOptions/utils.js +29 -6
  23. package/dist/cjs/hooks/useChartOptions/x-axis.js +2 -2
  24. package/dist/cjs/hooks/useChartOptions/y-axis.js +10 -11
  25. package/dist/cjs/hooks/useNormalizedOriginalData/index.d.ts +40 -0
  26. package/dist/cjs/hooks/useNormalizedOriginalData/index.js +33 -0
  27. package/dist/cjs/hooks/useSeries/index.d.ts +0 -9
  28. package/dist/cjs/hooks/useSeries/index.js +0 -18
  29. package/dist/cjs/hooks/useSeries/types.d.ts +3 -0
  30. package/dist/cjs/hooks/useShapes/bar-x/prepare-data.js +4 -0
  31. package/dist/cjs/hooks/useShapes/bar-y/prepare-data.js +4 -0
  32. package/dist/cjs/hooks/useShapes/scatter/prepare-data.d.ts +2 -2
  33. package/dist/cjs/hooks/useShapes/scatter/prepare-data.js +40 -5
  34. package/dist/cjs/types/chart/axis.d.ts +20 -2
  35. package/dist/cjs/utils/chart/get-closest-data.js +1 -1
  36. package/dist/cjs/utils/chart/series/sorting.d.ts +2 -2
  37. package/dist/cjs/utils/chart/series/sorting.js +3 -3
  38. package/dist/cjs/utils/chart/zoom.d.ts +7 -6
  39. package/dist/cjs/utils/chart/zoom.js +14 -6
  40. package/dist/esm/components/AxisY/AxisY.js +8 -1
  41. package/dist/esm/components/AxisY/prepare-axis-data.js +39 -12
  42. package/dist/esm/components/AxisY/types.d.ts +3 -0
  43. package/dist/esm/components/ChartInner/index.js +18 -7
  44. package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +7 -10
  45. package/dist/esm/components/ChartInner/useChartInnerProps.js +39 -104
  46. package/dist/esm/components/ChartInner/useChartInnerState.d.ts +4 -2
  47. package/dist/esm/components/ChartInner/useChartInnerState.js +9 -0
  48. package/dist/esm/components/ChartInner/utils.d.ts +2 -2
  49. package/dist/esm/components/ChartInner/utils.js +1 -1
  50. package/dist/esm/components/Title/index.d.ts +0 -1
  51. package/dist/esm/components/Title/index.js +6 -4
  52. package/dist/esm/hooks/index.d.ts +7 -3
  53. package/dist/esm/hooks/index.js +7 -3
  54. package/dist/esm/hooks/useAxis/index.d.ts +19 -0
  55. package/dist/esm/hooks/useAxis/index.js +63 -0
  56. package/dist/esm/hooks/useChartOptions/index.d.ts +1 -4
  57. package/dist/esm/hooks/useChartOptions/index.js +2 -5
  58. package/dist/esm/hooks/useChartOptions/title.js +4 -2
  59. package/dist/esm/hooks/useChartOptions/types.d.ts +0 -1
  60. package/dist/esm/hooks/useChartOptions/utils.d.ts +1 -4
  61. package/dist/esm/hooks/useChartOptions/utils.js +29 -6
  62. package/dist/esm/hooks/useChartOptions/x-axis.js +2 -2
  63. package/dist/esm/hooks/useChartOptions/y-axis.js +10 -11
  64. package/dist/esm/hooks/useNormalizedOriginalData/index.d.ts +40 -0
  65. package/dist/esm/hooks/useNormalizedOriginalData/index.js +33 -0
  66. package/dist/esm/hooks/useSeries/index.d.ts +0 -9
  67. package/dist/esm/hooks/useSeries/index.js +0 -18
  68. package/dist/esm/hooks/useSeries/types.d.ts +3 -0
  69. package/dist/esm/hooks/useShapes/bar-x/prepare-data.js +4 -0
  70. package/dist/esm/hooks/useShapes/bar-y/prepare-data.js +4 -0
  71. package/dist/esm/hooks/useShapes/scatter/prepare-data.d.ts +2 -2
  72. package/dist/esm/hooks/useShapes/scatter/prepare-data.js +40 -5
  73. package/dist/esm/types/chart/axis.d.ts +20 -2
  74. package/dist/esm/utils/chart/get-closest-data.js +1 -1
  75. package/dist/esm/utils/chart/series/sorting.d.ts +2 -2
  76. package/dist/esm/utils/chart/series/sorting.js +3 -3
  77. package/dist/esm/utils/chart/zoom.d.ts +7 -6
  78. package/dist/esm/utils/chart/zoom.js +14 -6
  79. package/package.json +7 -16
  80. package/dist/cjs/components/Title/styles.css +0 -5
  81. package/dist/esm/components/Title/styles.css +0 -5
@@ -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';
@@ -26,12 +27,22 @@ export const ChartInner = (props) => {
26
27
  const plotAfterRef = React.useRef(null);
27
28
  const dispatcher = React.useMemo(() => getDispatcher(), []);
28
29
  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({
30
+ const preparedTooltip = React.useMemo(() => {
31
+ return getPreparedTooltip({
32
+ tooltip: data.tooltip,
33
+ seriesData: data.series.data,
34
+ yAxes: data.yAxis,
35
+ xAxis: data.xAxis,
36
+ });
37
+ }, [data.series.data, data.tooltip, data.yAxis, data.xAxis]);
38
+ const { tooltipPinned, togglePinTooltip, unpinTooltip, updateZoomState, zoomState } = useChartInnerState({
32
39
  dispatcher,
33
- tooltip,
40
+ tooltip: preparedTooltip,
34
41
  });
42
+ const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, isOutsideBounds, legendConfig, legendItems, preparedSeries, preparedSplit, preparedLegend, prevHeight, prevWidth, shapes, shapesData, title, xAxis, xScale, yAxis, yScale, svgXPos, } = useChartInnerProps(Object.assign(Object.assign({}, props), { clipPathId,
43
+ dispatcher,
44
+ htmlLayout, plotNode: plotRef.current, svgContainer: svgRef.current, updateZoomState,
45
+ zoomState }));
35
46
  const { handleChartClick, handleMouseLeave, throttledHandleMouseMove, throttledHandleTouchMove } = useChartInnerHandlers({
36
47
  boundsHeight,
37
48
  boundsOffsetLeft,
@@ -45,7 +56,7 @@ export const ChartInner = (props) => {
45
56
  unpinTooltip,
46
57
  xAxis,
47
58
  yAxis,
48
- tooltipThrottle: tooltip.throttle,
59
+ tooltipThrottle: preparedTooltip.throttle,
49
60
  isOutsideBounds,
50
61
  });
51
62
  const clickHandler = (_b = (_a = data.chart) === null || _a === void 0 ? void 0 : _a.events) === null || _b === void 0 ? void 0 : _b.click;
@@ -132,8 +143,8 @@ export const ChartInner = (props) => {
132
143
  React.createElement("div", { className: b('html-layer'), ref: setHtmlLayout, style: {
133
144
  '--g-html-layout-transform': `translate(${boundsOffsetLeft}px, ${boundsOffsetTop}px)`,
134
145
  } }),
135
- handleZoomReset && (React.createElement(Button, { style: { position: 'absolute', top: 0, right: 0 }, onClick: handleZoomReset },
146
+ Object.keys(zoomState).length > 0 && (React.createElement(Button, { style: { position: 'absolute', top: 0, right: 0 }, onClick: () => updateZoomState({}) },
136
147
  React.createElement(ButtonIcon, null,
137
148
  React.createElement(ArrowRotateLeft, null)))),
138
- React.createElement(Tooltip, { dispatcher: dispatcher, tooltip: tooltip, svgContainer: svgRef.current, xAxis: xAxis, yAxis: yAxis[0], onOutsideClick: unpinTooltip, tooltipPinned: tooltipPinned })));
149
+ React.createElement(Tooltip, { dispatcher: dispatcher, tooltip: preparedTooltip, svgContainer: svgRef.current, xAxis: xAxis, yAxis: yAxis[0], onOutsideClick: unpinTooltip, tooltipPinned: tooltipPinned })));
139
150
  };
@@ -1,13 +1,15 @@
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
15
  svgBottomPos: number | undefined;
@@ -18,7 +20,6 @@ export declare function useChartInnerProps(props: Props): {
18
20
  boundsOffsetTop: number;
19
21
  boundsWidth: number;
20
22
  handleLegendItemClick: import("../../hooks").OnLegendItemClick;
21
- handleZoomReset: (() => void) | undefined;
22
23
  isOutsideBounds: (x: number, y: number) => boolean;
23
24
  legendConfig: {
24
25
  offset: {
@@ -41,16 +42,12 @@ export declare function useChartInnerProps(props: Props): {
41
42
  prevWidth: number | undefined;
42
43
  shapes: React.ReactElement<any, string | React.JSXElementConstructor<any>>[];
43
44
  shapesData: import("../../hooks").ShapeData[];
44
- title: (import("../../types").ChartTitle & {
45
+ title: (import("../..").ChartTitle & {
45
46
  height: number;
46
47
  }) | undefined;
47
- tooltip: import("../../types").ChartTooltip<any> & {
48
- enabled: boolean;
49
- throttle: number;
50
- };
51
- xAxis: PreparedAxis | null;
48
+ xAxis: import("../../hooks").PreparedAxis | null;
52
49
  xScale: import("../../hooks").ChartScale | undefined;
53
- yAxis: PreparedAxis[];
50
+ yAxis: import("../../hooks").PreparedAxis[];
54
51
  yScale: (import("../../hooks").ChartScale | undefined)[] | undefined;
55
52
  };
56
53
  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,
47
- seriesOptions: data.series.options,
48
- });
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,
31
+ originalSeriesData: normalizedSeriesData,
32
+ seriesData: normalizedSeriesData,
109
33
  seriesOptions: data.series.options,
110
- activeLegendItems,
111
- preparedLegend,
112
34
  });
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,
@@ -199,9 +139,6 @@ export function useChartInnerProps(props) {
199
139
  return acc;
200
140
  }, 0);
201
141
  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
- }, []);
205
142
  return {
206
143
  svgBottomPos: bottom,
207
144
  svgTopPos: top,
@@ -211,7 +148,6 @@ export function useChartInnerProps(props) {
211
148
  boundsOffsetTop,
212
149
  boundsWidth,
213
150
  handleLegendItemClick,
214
- handleZoomReset: Object.keys(zoomState).length > 0 ? handleZoomReset : undefined,
215
151
  isOutsideBounds,
216
152
  legendConfig,
217
153
  legendItems,
@@ -223,7 +159,6 @@ export function useChartInnerProps(props) {
223
159
  shapes,
224
160
  shapesData,
225
161
  title,
226
- tooltip,
227
162
  xAxis,
228
163
  xScale,
229
164
  yAxis,
@@ -1,13 +1,15 @@
1
1
  import React from 'react';
2
2
  import type { Dispatch } from 'd3';
3
- import type { ChartTooltip } from '../../types';
3
+ import type { PreparedTooltip, ZoomState } from '../../hooks';
4
4
  type Props = {
5
5
  dispatcher: Dispatch<object>;
6
- tooltip?: ChartTooltip;
6
+ tooltip?: PreparedTooltip;
7
7
  };
8
8
  export declare function useChartInnerState(props: Props): {
9
9
  tooltipPinned: boolean;
10
10
  togglePinTooltip: ((value: boolean, event: React.MouseEvent) => void) | undefined;
11
11
  unpinTooltip: (() => void) | undefined;
12
+ updateZoomState: (nextZoomState: Partial<ZoomState>) => void;
13
+ zoomState: Partial<ZoomState>;
12
14
  };
13
15
  export {};
@@ -1,9 +1,11 @@
1
1
  import React from 'react';
2
+ import isEqual from 'lodash/isEqual';
2
3
  import { EventType, isMacintosh } from '../../utils';
3
4
  export function useChartInnerState(props) {
4
5
  var _a, _b;
5
6
  const { dispatcher, tooltip } = props;
6
7
  const [tooltipPinned, setTooltipPinned] = React.useState(false);
8
+ const [zoomState, setZoomState] = React.useState({});
7
9
  const tooltipEnabled = tooltip === null || tooltip === void 0 ? void 0 : tooltip.enabled;
8
10
  const tooltipPinEnabled = (_a = tooltip === null || tooltip === void 0 ? void 0 : tooltip.pin) === null || _a === void 0 ? void 0 : _a.enabled;
9
11
  const modifierKey = (_b = tooltip === null || tooltip === void 0 ? void 0 : tooltip.pin) === null || _b === void 0 ? void 0 : _b.modifierKey;
@@ -26,9 +28,16 @@ export function useChartInnerState(props) {
26
28
  setTooltipPinned(false);
27
29
  dispatcher.call(EventType.HOVER_SHAPE, {}, undefined);
28
30
  }, [dispatcher]);
31
+ const updateZoomState = React.useCallback((nextZoomState) => {
32
+ if (!isEqual(zoomState, nextZoomState)) {
33
+ setZoomState(nextZoomState);
34
+ }
35
+ }, [zoomState]);
29
36
  return {
30
37
  tooltipPinned,
31
38
  togglePinTooltip: tooltipEnabled && tooltipPinEnabled ? togglePinTooltip : undefined,
32
39
  unpinTooltip: tooltipEnabled && tooltipPinEnabled ? unpinTooltip : undefined,
40
+ updateZoomState,
41
+ zoomState,
33
42
  };
34
43
  }
@@ -1,4 +1,4 @@
1
+ import type { PreparedSeries } from '../../hooks';
1
2
  import type { PreparedAxis } from '../../hooks/useChartOptions/types';
2
- import type { ChartSeries } from '../../types';
3
- export declare function hasAtLeastOneSeriesDataPerPlot(seriesData: ChartSeries[], yAxes?: PreparedAxis[]): boolean;
3
+ export declare function hasAtLeastOneSeriesDataPerPlot(seriesData: PreparedSeries[], yAxes?: PreparedAxis[]): boolean;
4
4
  export declare function useAsyncState<T>(value: T, setState: () => Promise<T>): T;
@@ -21,7 +21,7 @@ export function hasAtLeastOneSeriesDataPerPlot(seriesData, yAxes = []) {
21
21
  const yAxis = yAxes[yAxisIndex];
22
22
  const plotIndex = (_a = yAxis === null || yAxis === void 0 ? void 0 : yAxis.plotIndex) !== null && _a !== void 0 ? _a : 0;
23
23
  if (!hasDataMap.get(plotIndex)) {
24
- if (seriesDataChunk.data.length > 0) {
24
+ if (Array.isArray(seriesDataChunk.data) && seriesDataChunk.data.length > 0) {
25
25
  hasDataMap.set(plotIndex, true);
26
26
  }
27
27
  }
@@ -1,6 +1,5 @@
1
1
  import React from 'react';
2
2
  import type { PreparedTitle } from '../../hooks';
3
- import './styles.css';
4
3
  type Props = PreparedTitle & {
5
4
  chartWidth: number;
6
5
  };
@@ -1,9 +1,11 @@
1
1
  import React from 'react';
2
- import { block } from '../../utils';
3
- import './styles.css';
4
- const b = block('title');
5
2
  export const Title = (props) => {
6
3
  const { chartWidth, text, height, style } = props;
7
- return (React.createElement("text", { className: b(), dx: chartWidth / 2, dy: height / 2, dominantBaseline: "middle", textAnchor: "middle", style: Object.assign({ lineHeight: `${height}px` }, style) },
4
+ return (React.createElement("text", { dx: chartWidth / 2, dy: height / 2, dominantBaseline: "middle", textAnchor: "middle", style: {
5
+ fill: style === null || style === void 0 ? void 0 : style.fontColor,
6
+ fontSize: style === null || style === void 0 ? void 0 : style.fontSize,
7
+ fontWeight: style === null || style === void 0 ? void 0 : style.fontWeight,
8
+ lineHeight: `${height}px`,
9
+ } },
8
10
  React.createElement("tspan", { dangerouslySetInnerHTML: { __html: text } })));
9
11
  };
@@ -1,12 +1,16 @@
1
+ export * from './useAxis';
2
+ export * from './useAxisScales';
1
3
  export * from './useChartDimensions';
2
4
  export * from './useChartOptions';
3
5
  export * from './useChartOptions/types';
4
- export * from './useAxisScales';
6
+ export * from './useCrosshair';
7
+ export * from './useNormalizedOriginalData';
5
8
  export * from './usePrevious';
6
9
  export * from './useSeries';
7
10
  export * from './useSeries/types';
8
11
  export * from './useShapes';
9
- export * from './useTooltip';
10
12
  export * from './useSplit';
11
13
  export * from './useSplit/types';
12
- export * from './useCrosshair';
14
+ export * from './useTooltip';
15
+ export * from './useZoom';
16
+ export * from './useZoom/types';
@@ -1,12 +1,16 @@
1
+ export * from './useAxis';
2
+ export * from './useAxisScales';
1
3
  export * from './useChartDimensions';
2
4
  export * from './useChartOptions';
3
5
  export * from './useChartOptions/types';
4
- export * from './useAxisScales';
6
+ export * from './useCrosshair';
7
+ export * from './useNormalizedOriginalData';
5
8
  export * from './usePrevious';
6
9
  export * from './useSeries';
7
10
  export * from './useSeries/types';
8
11
  export * from './useShapes';
9
- export * from './useTooltip';
10
12
  export * from './useSplit';
11
13
  export * from './useSplit/types';
12
- export * from './useCrosshair';
14
+ export * from './useTooltip';
15
+ export * from './useZoom';
16
+ export * from './useZoom/types';
@@ -0,0 +1,19 @@
1
+ import type { ChartXAxis, ChartYAxis } from '../../types';
2
+ import type { PreparedAxis, PreparedChart } from '../useChartOptions/types';
3
+ import type { PreparedLegend, PreparedSeries, PreparedSeriesOptions } from '../useSeries/types';
4
+ interface UseAxesProps {
5
+ height: number;
6
+ preparedChart: PreparedChart;
7
+ preparedLegend: PreparedLegend | null;
8
+ preparedSeries: PreparedSeries[];
9
+ preparedSeriesOptions: PreparedSeriesOptions;
10
+ width: number;
11
+ boundsHeight?: number;
12
+ xAxis?: ChartXAxis;
13
+ yAxis?: ChartYAxis[];
14
+ }
15
+ export declare function useAxis(props: UseAxesProps): {
16
+ xAxis: PreparedAxis | null;
17
+ yAxis: PreparedAxis[];
18
+ };
19
+ export {};
@@ -0,0 +1,63 @@
1
+ import React from 'react';
2
+ import isEqual from 'lodash/isEqual';
3
+ import { getPreparedXAxis } from '../useChartOptions/x-axis';
4
+ import { getPreparedYAxis } from '../useChartOptions/y-axis';
5
+ export function useAxis(props) {
6
+ const { boundsHeight, height, preparedChart, preparedLegend, preparedSeries, preparedSeriesOptions, width, xAxis, yAxis, } = props;
7
+ const [axesState, setValue] = React.useState({ xAxis: null, yAxis: [] });
8
+ const axesStateRunRef = React.useRef(0);
9
+ const prevAxesStateValue = React.useRef(axesState);
10
+ const axesStateReady = React.useRef(false);
11
+ React.useEffect(() => {
12
+ axesStateRunRef.current++;
13
+ axesStateReady.current = false;
14
+ (async function () {
15
+ const currentRun = axesStateRunRef.current;
16
+ const seriesData = preparedSeries.filter((s) => s.visible);
17
+ const preparedXAxis = await getPreparedXAxis({
18
+ xAxis,
19
+ width,
20
+ seriesData,
21
+ seriesOptions: preparedSeriesOptions,
22
+ });
23
+ let estimatedBoundsHeight = boundsHeight !== null && boundsHeight !== void 0 ? boundsHeight : height;
24
+ if (preparedXAxis && typeof boundsHeight !== 'number') {
25
+ estimatedBoundsHeight =
26
+ height -
27
+ (preparedXAxis.title.height +
28
+ preparedXAxis.title.margin +
29
+ preparedXAxis.labels.margin +
30
+ preparedXAxis.labels.height +
31
+ (preparedLegend ? preparedLegend.height + preparedLegend.margin : 0) +
32
+ preparedChart.margin.top +
33
+ preparedChart.margin.bottom);
34
+ }
35
+ const preparedYAxis = await getPreparedYAxis({
36
+ height,
37
+ boundsHeight: estimatedBoundsHeight,
38
+ width,
39
+ seriesData,
40
+ yAxis,
41
+ });
42
+ const newStateValue = { xAxis: preparedXAxis, yAxis: preparedYAxis };
43
+ if (axesStateRunRef.current === currentRun) {
44
+ if (!isEqual(prevAxesStateValue.current, newStateValue)) {
45
+ setValue(newStateValue);
46
+ prevAxesStateValue.current = newStateValue;
47
+ }
48
+ axesStateReady.current = true;
49
+ }
50
+ })();
51
+ }, [
52
+ boundsHeight,
53
+ height,
54
+ preparedChart.margin,
55
+ preparedLegend,
56
+ preparedSeries,
57
+ preparedSeriesOptions,
58
+ width,
59
+ xAxis,
60
+ yAxis,
61
+ ]);
62
+ return axesStateReady.current ? axesState : { xAxis: null, yAxis: [] };
63
+ }