@gravity-ui/charts 1.19.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 (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 +18 -7
  5. package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +8 -11
  6. package/dist/cjs/components/ChartInner/useChartInnerProps.js +39 -86
  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/i18n/keysets/en.json +2 -1
  35. package/dist/cjs/i18n/keysets/ru.json +2 -1
  36. package/dist/cjs/types/chart/axis.d.ts +20 -2
  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/zoom.d.ts +7 -6
  41. package/dist/cjs/utils/chart/zoom.js +14 -6
  42. package/dist/cjs/validation/validate-axes.js +31 -1
  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 +18 -7
  47. package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +7 -10
  48. package/dist/esm/components/ChartInner/useChartInnerProps.js +39 -86
  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 +2 -2
  52. package/dist/esm/components/ChartInner/utils.js +1 -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/index.d.ts +1 -4
  60. package/dist/esm/hooks/useChartOptions/index.js +2 -5
  61. package/dist/esm/hooks/useChartOptions/title.js +4 -2
  62. package/dist/esm/hooks/useChartOptions/types.d.ts +0 -1
  63. package/dist/esm/hooks/useChartOptions/utils.d.ts +1 -4
  64. package/dist/esm/hooks/useChartOptions/utils.js +29 -6
  65. package/dist/esm/hooks/useChartOptions/x-axis.js +2 -2
  66. package/dist/esm/hooks/useChartOptions/y-axis.js +10 -11
  67. package/dist/esm/hooks/useNormalizedOriginalData/index.d.ts +40 -0
  68. package/dist/esm/hooks/useNormalizedOriginalData/index.js +33 -0
  69. package/dist/esm/hooks/useSeries/index.d.ts +0 -9
  70. package/dist/esm/hooks/useSeries/index.js +0 -18
  71. package/dist/esm/hooks/useSeries/types.d.ts +3 -0
  72. package/dist/esm/hooks/useShapes/bar-x/prepare-data.js +4 -0
  73. package/dist/esm/hooks/useShapes/bar-y/prepare-data.js +4 -0
  74. package/dist/esm/hooks/useShapes/scatter/prepare-data.d.ts +2 -2
  75. package/dist/esm/hooks/useShapes/scatter/prepare-data.js +40 -5
  76. package/dist/esm/i18n/keysets/en.json +2 -1
  77. package/dist/esm/i18n/keysets/ru.json +2 -1
  78. package/dist/esm/types/chart/axis.d.ts +20 -2
  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/zoom.d.ts +7 -6
  83. package/dist/esm/utils/chart/zoom.js +14 -6
  84. package/dist/esm/validation/validate-axes.js +31 -1
  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
@@ -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,97 +1,45 @@
1
1
  import React from 'react';
2
- import { useAxisScales, useChartDimensions, useChartOptions, usePrevious, useSeries, useShapeSeries, useShapes, useSplit, } from '../../hooks';
2
+ import { useAxis, useAxisScales, useChartDimensions, useChartOptions, useNormalizedOriginalData, usePrevious, useSeries, useShapes, useSplit, } from '../../hooks';
3
3
  import { getYAxisWidth } from '../../hooks/useChartDimensions/utils';
4
- import { getPreparedXAxis } from '../../hooks/useChartOptions/x-axis';
5
- import { getPreparedYAxis } from '../../hooks/useChartOptions/y-axis';
6
4
  import { getLegendComponents } from '../../hooks/useSeries/prepare-legend';
7
5
  import { getPreparedOptions } from '../../hooks/useSeries/prepare-options';
8
- import { getActiveLegendItems } from '../../hooks/useSeries/utils';
9
6
  import { useZoom } from '../../hooks/useZoom';
10
- import { getSortedSeriesData, getZoomedSeriesData } from '../../utils';
11
- import { hasAtLeastOneSeriesDataPerPlot, useAsyncState } from './utils';
7
+ import { getZoomedSeriesData } from '../../utils';
8
+ import { hasAtLeastOneSeriesDataPerPlot } from './utils';
12
9
  export function useChartInnerProps(props) {
13
10
  var _a;
14
- const { width, height, data, dispatcher, htmlLayout, svgContainer, plotNode, clipPathId } = props;
11
+ const { clipPathId, data, dispatcher, height, htmlLayout, plotNode, svgContainer, width, updateZoomState, zoomState, } = props;
15
12
  const prevWidth = usePrevious(width);
16
13
  const prevHeight = usePrevious(height);
17
- const { chart, title, tooltip, colors } = useChartOptions({
14
+ const { normalizedSeriesData, normalizedXAxis, normalizedYAxis } = useNormalizedOriginalData({
18
15
  seriesData: data.series.data,
16
+ xAxis: data.xAxis,
17
+ yAxis: data.yAxis,
18
+ });
19
+ const { chart, title, colors } = useChartOptions({
19
20
  chart: data.chart,
20
21
  colors: data.colors,
22
+ seriesData: normalizedSeriesData,
21
23
  title: data.title,
22
- tooltip: data.tooltip,
23
- xAxis: data.xAxis,
24
- yAxes: data.yAxis,
25
24
  });
26
25
  const preparedSeriesOptions = React.useMemo(() => {
27
26
  return getPreparedOptions(data.series.options);
28
27
  }, [data.series.options]);
29
- const [zoomState, setZoomState] = React.useState({});
30
- const sortedSeriesData = React.useMemo(() => {
31
- return getSortedSeriesData({ seriesData: data.series.data, yAxes: data.yAxis });
32
- }, [data.series.data, data.yAxis]);
33
- const { zoomedSeriesData, zoomedShapesSeriesData } = React.useMemo(() => {
34
- return getZoomedSeriesData({
35
- seriesData: sortedSeriesData,
36
- xAxis: data.xAxis,
37
- yAxes: data.yAxis,
38
- zoomState,
39
- });
40
- }, [data.xAxis, data.yAxis, sortedSeriesData, zoomState]);
41
- const { preparedSeries, preparedLegend, handleLegendItemClick } = useSeries({
28
+ const { preparedSeries: basePreparedSeries, preparedLegend, handleLegendItemClick, } = useSeries({
42
29
  colors,
43
30
  legend: data.legend,
44
- originalSeriesData: sortedSeriesData,
45
- seriesData: zoomedSeriesData,
31
+ originalSeriesData: normalizedSeriesData,
32
+ seriesData: normalizedSeriesData,
46
33
  seriesOptions: data.series.options,
47
34
  });
48
- const setAxes = React.useCallback(async () => {
49
- const seriesData = preparedSeries.filter((s) => s.visible);
50
- const xAxis = await getPreparedXAxis({
51
- xAxis: data.xAxis,
52
- width,
53
- seriesData,
54
- seriesOptions: preparedSeriesOptions,
55
- });
56
- let estimatedBoundsHeight = height;
57
- if (xAxis) {
58
- estimatedBoundsHeight =
59
- height -
60
- (xAxis.title.height +
61
- xAxis.title.margin +
62
- xAxis.labels.margin +
63
- xAxis.labels.height +
64
- (preparedLegend ? preparedLegend.height + preparedLegend.margin : 0) +
65
- chart.margin.top +
66
- chart.margin.bottom);
67
- }
68
- const yAxis = await getPreparedYAxis({
69
- height,
70
- boundsHeight: estimatedBoundsHeight,
71
- width,
72
- seriesData,
73
- yAxis: data.yAxis,
35
+ const { preparedSeries, preparedShapesSeries } = React.useMemo(() => {
36
+ return getZoomedSeriesData({
37
+ seriesData: basePreparedSeries,
38
+ xAxis: normalizedXAxis,
39
+ yAxis: normalizedYAxis,
40
+ zoomState,
74
41
  });
75
- return { xAxis, yAxis };
76
- }, [
77
- chart.margin,
78
- data.xAxis,
79
- data.yAxis,
80
- height,
81
- preparedLegend,
82
- preparedSeries,
83
- preparedSeriesOptions,
84
- width,
85
- ]);
86
- const { xAxis, yAxis } = useAsyncState({ xAxis: null, yAxis: [] }, setAxes);
87
- const activeLegendItems = React.useMemo(() => getActiveLegendItems(preparedSeries), [preparedSeries]);
88
- const { preparedSeries: preparedShapesSeries } = useShapeSeries({
89
- colors,
90
- seriesData: zoomedShapesSeriesData,
91
- seriesOptions: data.series.options,
92
- activeLegendItems,
93
- preparedLegend,
94
- });
42
+ }, [basePreparedSeries, normalizedXAxis, normalizedYAxis, zoomState]);
95
43
  const { legendConfig, legendItems } = React.useMemo(() => {
96
44
  if (!preparedLegend) {
97
45
  return { legendConfig: undefined, legendItems: [] };
@@ -104,14 +52,24 @@ export function useChartInnerProps(props) {
104
52
  preparedLegend,
105
53
  });
106
54
  }, [width, height, chart.margin, preparedSeries, preparedLegend]);
107
- const { boundsWidth, boundsHeight } = useChartDimensions({
55
+ const { xAxis, yAxis } = useAxis({
56
+ height,
57
+ preparedChart: chart,
58
+ preparedLegend,
59
+ preparedSeries,
60
+ preparedSeriesOptions,
108
61
  width,
62
+ xAxis: normalizedXAxis,
63
+ yAxis: normalizedYAxis,
64
+ });
65
+ const { boundsWidth, boundsHeight } = useChartDimensions({
109
66
  height,
110
67
  margin: chart.margin,
111
68
  preparedLegend,
112
- preparedXAxis: xAxis,
113
- preparedYAxis: yAxis,
114
69
  preparedSeries: preparedSeries,
70
+ preparedYAxis: yAxis,
71
+ preparedXAxis: xAxis,
72
+ width,
115
73
  });
116
74
  const preparedSplit = useSplit({ split: data.split, boundsHeight, chartWidth: width });
117
75
  const { xScale, yScale } = useAxisScales({
@@ -144,17 +102,17 @@ export function useChartInnerProps(props) {
144
102
  isOutsideBounds,
145
103
  });
146
104
  const handleAttemptToSetZoomState = React.useCallback((nextZoomState) => {
147
- const { zoomedSeriesData: nextZoomedSeriesData } = getZoomedSeriesData({
148
- seriesData: zoomedSeriesData,
149
- xAxis: data.xAxis,
150
- yAxes: data.yAxis,
105
+ const { preparedSeries: nextZoomedSeriesData } = getZoomedSeriesData({
106
+ seriesData: preparedSeries,
107
+ xAxis,
108
+ yAxis,
151
109
  zoomState: nextZoomState,
152
110
  });
153
111
  const hasData = hasAtLeastOneSeriesDataPerPlot(nextZoomedSeriesData, yAxis);
154
112
  if (hasData) {
155
- setZoomState(nextZoomState);
113
+ updateZoomState(nextZoomState);
156
114
  }
157
- }, [data.xAxis, data.yAxis, yAxis, zoomedSeriesData]);
115
+ }, [xAxis, yAxis, preparedSeries, updateZoomState]);
158
116
  useZoom({
159
117
  node: plotNode,
160
118
  onUpdate: handleAttemptToSetZoomState,
@@ -181,9 +139,6 @@ export function useChartInnerProps(props) {
181
139
  return acc;
182
140
  }, 0);
183
141
  const { bottom, top, x } = (_a = svgContainer === null || svgContainer === void 0 ? void 0 : svgContainer.getBoundingClientRect()) !== null && _a !== void 0 ? _a : {};
184
- const handleZoomReset = React.useCallback(() => {
185
- setZoomState({});
186
- }, []);
187
142
  return {
188
143
  svgBottomPos: bottom,
189
144
  svgTopPos: top,
@@ -193,7 +148,6 @@ export function useChartInnerProps(props) {
193
148
  boundsOffsetTop,
194
149
  boundsWidth,
195
150
  handleLegendItemClick,
196
- handleZoomReset: Object.keys(zoomState).length > 0 ? handleZoomReset : undefined,
197
151
  isOutsideBounds,
198
152
  legendConfig,
199
153
  legendItems,
@@ -205,7 +159,6 @@ export function useChartInnerProps(props) {
205
159
  shapes,
206
160
  shapesData,
207
161
  title,
208
- tooltip,
209
162
  xAxis,
210
163
  xScale,
211
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
+ }
@@ -1,13 +1,10 @@
1
- import type { ChartSeries, ChartTitle, ChartTooltip, ChartXAxis, ChartYAxis, ChartOptions as GeneralChartOptions } from '../../types';
1
+ import type { ChartSeries, ChartTitle, ChartOptions as GeneralChartOptions } from '../../types';
2
2
  import type { ChartOptions } from './types';
3
3
  type Args = {
4
4
  seriesData: ChartSeries[];
5
5
  chart?: GeneralChartOptions;
6
6
  colors?: string[];
7
7
  title?: ChartTitle;
8
- tooltip?: ChartTooltip;
9
- yAxes?: ChartYAxis[];
10
- xAxis?: ChartXAxis;
11
8
  };
12
9
  export declare const useChartOptions: (args: Args) => ChartOptions;
13
10
  export {};