@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
@@ -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,20 +36,32 @@ 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>>[];
43
58
  shapesData: import("../../hooks").ShapeData[];
44
- title: (import("../../types").ChartTitle & {
59
+ title: (import("../..").ChartTitle & {
45
60
  height: number;
46
61
  }) | undefined;
47
- tooltip: import("../../types").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,
@@ -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,13 @@
1
- import type { PreparedAxis } from '../../hooks/useChartOptions/types';
2
- import type { ChartSeries } from '../../types';
3
- export declare function hasAtLeastOneSeriesDataPerPlot(seriesData: ChartSeries[], yAxes?: PreparedAxis[]): boolean;
1
+ import React from 'react';
2
+ import type { PreparedSeries } from '../../hooks';
3
+ import type { PreparedAxis, PreparedZoom } from '../../hooks/useChartOptions/types';
4
+ export declare function hasAtLeastOneSeriesDataPerPlot(seriesData: PreparedSeries[], yAxes?: PreparedAxis[]): boolean;
4
5
  export declare function useAsyncState<T>(value: T, setState: () => Promise<T>): T;
6
+ export declare function getResetZoomButtonStyle(args: {
7
+ boundsHeight: number;
8
+ boundsOffsetLeft: number;
9
+ boundsOffsetTop: number;
10
+ boundsWidth: number;
11
+ node: HTMLElement | null;
12
+ titleHeight?: number;
13
+ } & PreparedZoom['resetButton']): React.CSSProperties;
@@ -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
  }
@@ -47,3 +47,63 @@ export function useAsyncState(value, setState) {
47
47
  }, [setState]);
48
48
  return stateValue;
49
49
  }
50
+ export function getResetZoomButtonStyle(args) {
51
+ const { align, boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, node, offset, relativeTo, titleHeight, } = args;
52
+ const style = {
53
+ position: 'absolute',
54
+ transform: `translate(${offset.x}px, ${offset.y}px)`,
55
+ };
56
+ switch (relativeTo) {
57
+ case 'chart-box': {
58
+ switch (align) {
59
+ case 'bottom-left': {
60
+ style.bottom = 0;
61
+ style.left = 0;
62
+ break;
63
+ }
64
+ case 'bottom-right': {
65
+ style.bottom = 0;
66
+ style.right = 0;
67
+ break;
68
+ }
69
+ case 'top-left': {
70
+ style.top = 0;
71
+ style.left = 0;
72
+ break;
73
+ }
74
+ case 'top-right': {
75
+ style.top = 0;
76
+ style.right = 0;
77
+ break;
78
+ }
79
+ }
80
+ break;
81
+ }
82
+ case 'plot-box': {
83
+ switch (align) {
84
+ case 'bottom-left': {
85
+ style.left = boundsOffsetLeft;
86
+ style.top = boundsHeight - ((node === null || node === void 0 ? void 0 : node.clientHeight) || 0) + (titleHeight || 0);
87
+ break;
88
+ }
89
+ case 'bottom-right': {
90
+ style.left = boundsWidth + boundsOffsetLeft - ((node === null || node === void 0 ? void 0 : node.clientWidth) || 0);
91
+ style.top = boundsHeight - ((node === null || node === void 0 ? void 0 : node.clientHeight) || 0) + (titleHeight || 0);
92
+ break;
93
+ }
94
+ case 'top-left': {
95
+ style.left = boundsOffsetLeft;
96
+ style.top = boundsOffsetTop;
97
+ break;
98
+ }
99
+ case 'top-right': {
100
+ style.left = boundsWidth + boundsOffsetLeft - ((node === null || node === void 0 ? void 0 : node.clientWidth) || 0);
101
+ style.top = boundsOffsetTop;
102
+ break;
103
+ }
104
+ }
105
+ break;
106
+ }
107
+ }
108
+ return style;
109
+ }
@@ -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
  };