@gravity-ui/charts 0.5.0 → 0.6.1

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 (91) hide show
  1. package/dist/cjs/components/ChartInner/index.d.ts +2 -8
  2. package/dist/cjs/components/ChartInner/index.js +22 -117
  3. package/dist/cjs/components/ChartInner/types.d.ts +6 -0
  4. package/dist/cjs/components/ChartInner/useChartInnerHandlers.d.ts +26 -0
  5. package/dist/cjs/components/ChartInner/useChartInnerHandlers.js +94 -0
  6. package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +43 -0
  7. package/dist/cjs/components/ChartInner/useChartInnerProps.js +81 -0
  8. package/dist/cjs/components/ChartInner/useChartInnerState.d.ts +13 -0
  9. package/dist/cjs/components/ChartInner/useChartInnerState.js +34 -0
  10. package/dist/cjs/components/Legend/index.d.ts +1 -0
  11. package/dist/cjs/components/Legend/index.js +3 -2
  12. package/dist/cjs/components/Tooltip/index.d.ts +2 -0
  13. package/dist/cjs/components/Tooltip/index.js +10 -4
  14. package/dist/cjs/components/Tooltip/styles.css +13 -8
  15. package/dist/cjs/hooks/index.d.ts +2 -1
  16. package/dist/cjs/hooks/index.js +2 -1
  17. package/dist/cjs/hooks/usePrevious/index.d.ts +1 -0
  18. package/dist/cjs/hooks/usePrevious/index.js +8 -0
  19. package/dist/cjs/hooks/useShapes/area/index.js +8 -2
  20. package/dist/cjs/hooks/useShapes/area/prepare-data.js +4 -0
  21. package/dist/cjs/hooks/useShapes/bar-x/index.js +8 -2
  22. package/dist/cjs/hooks/useShapes/bar-x/prepare-data.js +1 -0
  23. package/dist/cjs/hooks/useShapes/bar-y/index.js +8 -2
  24. package/dist/cjs/hooks/useShapes/bar-y/prepare-data.js +1 -0
  25. package/dist/cjs/hooks/useShapes/line/index.js +8 -2
  26. package/dist/cjs/hooks/useShapes/line/prepare-data.js +1 -0
  27. package/dist/cjs/hooks/useShapes/pie/index.js +2 -1
  28. package/dist/cjs/hooks/useShapes/pie/prepare-data.js +159 -111
  29. package/dist/cjs/hooks/useShapes/pie/types.d.ts +1 -1
  30. package/dist/cjs/hooks/useShapes/scatter/index.js +8 -2
  31. package/dist/cjs/hooks/useShapes/treemap/index.js +8 -2
  32. package/dist/cjs/hooks/useShapes/treemap/prepare-data.js +1 -0
  33. package/dist/cjs/hooks/useShapes/utils.d.ts +2 -2
  34. package/dist/cjs/hooks/useShapes/utils.js +5 -3
  35. package/dist/cjs/hooks/useShapes/waterfall/index.js +8 -2
  36. package/dist/cjs/hooks/useTooltip/index.d.ts +2 -3
  37. package/dist/cjs/types/chart/chart.d.ts +2 -2
  38. package/dist/cjs/types/chart/series.d.ts +1 -2
  39. package/dist/cjs/types/chart/tooltip.d.ts +6 -6
  40. package/dist/cjs/types/chart-ui.d.ts +4 -0
  41. package/dist/cjs/types/misc.d.ts +1 -0
  42. package/dist/cjs/utils/misc.d.ts +10 -2
  43. package/dist/cjs/utils/misc.js +15 -3
  44. package/dist/esm/components/ChartInner/index.d.ts +2 -8
  45. package/dist/esm/components/ChartInner/index.js +22 -117
  46. package/dist/esm/components/ChartInner/types.d.ts +6 -0
  47. package/dist/esm/components/ChartInner/useChartInnerHandlers.d.ts +26 -0
  48. package/dist/esm/components/ChartInner/useChartInnerHandlers.js +94 -0
  49. package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +43 -0
  50. package/dist/esm/components/ChartInner/useChartInnerProps.js +81 -0
  51. package/dist/esm/components/ChartInner/useChartInnerState.d.ts +13 -0
  52. package/dist/esm/components/ChartInner/useChartInnerState.js +34 -0
  53. package/dist/esm/components/Legend/index.d.ts +1 -0
  54. package/dist/esm/components/Legend/index.js +3 -2
  55. package/dist/esm/components/Tooltip/index.d.ts +2 -0
  56. package/dist/esm/components/Tooltip/index.js +10 -4
  57. package/dist/esm/components/Tooltip/styles.css +13 -8
  58. package/dist/esm/hooks/index.d.ts +2 -1
  59. package/dist/esm/hooks/index.js +2 -1
  60. package/dist/esm/hooks/usePrevious/index.d.ts +1 -0
  61. package/dist/esm/hooks/usePrevious/index.js +8 -0
  62. package/dist/esm/hooks/useShapes/area/index.js +8 -2
  63. package/dist/esm/hooks/useShapes/area/prepare-data.js +4 -0
  64. package/dist/esm/hooks/useShapes/bar-x/index.js +8 -2
  65. package/dist/esm/hooks/useShapes/bar-x/prepare-data.js +1 -0
  66. package/dist/esm/hooks/useShapes/bar-y/index.js +8 -2
  67. package/dist/esm/hooks/useShapes/bar-y/prepare-data.js +1 -0
  68. package/dist/esm/hooks/useShapes/line/index.js +8 -2
  69. package/dist/esm/hooks/useShapes/line/prepare-data.js +1 -0
  70. package/dist/esm/hooks/useShapes/pie/index.js +2 -1
  71. package/dist/esm/hooks/useShapes/pie/prepare-data.js +159 -111
  72. package/dist/esm/hooks/useShapes/pie/types.d.ts +1 -1
  73. package/dist/esm/hooks/useShapes/scatter/index.js +8 -2
  74. package/dist/esm/hooks/useShapes/treemap/index.js +8 -2
  75. package/dist/esm/hooks/useShapes/treemap/prepare-data.js +1 -0
  76. package/dist/esm/hooks/useShapes/utils.d.ts +2 -2
  77. package/dist/esm/hooks/useShapes/utils.js +5 -3
  78. package/dist/esm/hooks/useShapes/waterfall/index.js +8 -2
  79. package/dist/esm/hooks/useTooltip/index.d.ts +2 -3
  80. package/dist/esm/types/chart/chart.d.ts +2 -2
  81. package/dist/esm/types/chart/series.d.ts +1 -2
  82. package/dist/esm/types/chart/tooltip.d.ts +6 -6
  83. package/dist/esm/types/chart-ui.d.ts +4 -0
  84. package/dist/esm/types/misc.d.ts +1 -0
  85. package/dist/esm/utils/misc.d.ts +10 -2
  86. package/dist/esm/utils/misc.js +15 -3
  87. package/package.json +1 -1
  88. package/dist/cjs/hooks/useTooltip/types.d.ts +0 -1
  89. package/dist/esm/hooks/useTooltip/types.d.ts +0 -1
  90. /package/dist/cjs/{hooks/useTooltip → components/ChartInner}/types.js +0 -0
  91. /package/dist/esm/{hooks/useTooltip → components/ChartInner}/types.js +0 -0
@@ -1,13 +1,12 @@
1
1
  import type { Dispatch } from 'd3';
2
- import type { TooltipDataChunk } from '../../types';
2
+ import type { PointPosition, TooltipDataChunk } from '../../types';
3
3
  import type { PreparedTooltip } from '../useChartOptions/types';
4
- import type { PointerPosition } from './types';
5
4
  type Args = {
6
5
  dispatcher: Dispatch<object>;
7
6
  tooltip: PreparedTooltip;
8
7
  };
9
8
  export declare const useTooltip: ({ dispatcher, tooltip }: Args) => {
10
9
  hovered: TooltipDataChunk[] | undefined;
11
- pointerPosition: PointerPosition | undefined;
10
+ pointerPosition: PointPosition | undefined;
12
11
  };
13
12
  export {};
@@ -1,5 +1,5 @@
1
1
  import type { MeaningfulAny } from '../misc';
2
- import type { ChartTooltipRendererData } from './tooltip';
2
+ import type { ChartTooltipRendererArgs } from './tooltip';
3
3
  export type ChartMargin = {
4
4
  top: number;
5
5
  right: number;
@@ -13,6 +13,6 @@ export type ChartOptions = {
13
13
  point: MeaningfulAny;
14
14
  series: MeaningfulAny;
15
15
  }, event: PointerEvent) => void;
16
- pointermove?: (data: ChartTooltipRendererData | undefined, event: PointerEvent) => void;
16
+ pointermove?: (data: ChartTooltipRendererArgs | undefined, event: PointerEvent) => void;
17
17
  };
18
18
  };
@@ -16,7 +16,7 @@ export type ChartSeriesData<T = MeaningfulAny> = ScatterSeriesData<T> | PieSerie
16
16
  export type DataLabelRendererData<T = MeaningfulAny> = {
17
17
  data: ChartSeriesData<T>;
18
18
  };
19
- type BasicHoverState = {
19
+ export type BasicHoverState = {
20
20
  /**
21
21
  * Enable separate styles for the hovered series.
22
22
  *
@@ -223,4 +223,3 @@ export type ChartSeriesOptions = {
223
223
  };
224
224
  };
225
225
  };
226
- export {};
@@ -59,7 +59,7 @@ export type TooltipDataChunkWaterfall<T = MeaningfulAny> = {
59
59
  export type TooltipDataChunk<T = MeaningfulAny> = (TooltipDataChunkBarX<T> | TooltipDataChunkBarY<T> | TooltipDataChunkPie<T> | TooltipDataChunkScatter<T> | TooltipDataChunkLine<T> | TooltipDataChunkArea<T> | TooltipDataChunkTreemap<T> | TooltipDataChunkWaterfall<T>) & {
60
60
  closest?: boolean;
61
61
  };
62
- export type ChartTooltipRendererData<T = MeaningfulAny> = {
62
+ export type ChartTooltipRendererArgs<T = MeaningfulAny> = {
63
63
  hovered: TooltipDataChunk<T>[];
64
64
  xAxis?: ChartXAxis;
65
65
  yAxis?: ChartYAxis;
@@ -67,9 +67,9 @@ export type ChartTooltipRendererData<T = MeaningfulAny> = {
67
67
  export type ChartTooltip<T = MeaningfulAny> = {
68
68
  enabled?: boolean;
69
69
  /** Specifies the renderer for the tooltip. If returned null default tooltip renderer will be used. */
70
- renderer?: (args: {
71
- hovered: TooltipDataChunk<T>[];
72
- xAxis?: ChartXAxis;
73
- yAxis?: ChartYAxis;
74
- }) => React.ReactElement | null;
70
+ renderer?: (args: ChartTooltipRendererArgs<T>) => React.ReactElement | null;
71
+ pin?: {
72
+ enabled?: boolean;
73
+ modifierKey?: 'altKey' | 'metaKey';
74
+ };
75
75
  };
@@ -18,6 +18,10 @@ export type HtmlItem = {
18
18
  x: number;
19
19
  y: number;
20
20
  content: string;
21
+ size: {
22
+ width: number;
23
+ height: number;
24
+ };
21
25
  };
22
26
  export type ShapeDataWithHtmlItems = {
23
27
  htmlElements: HtmlItem[];
@@ -2,3 +2,4 @@
2
2
  * A utilitarian type to describe `any` when it does not contradict the typescript usage paradigm.
3
3
  */
4
4
  export type MeaningfulAny = any;
5
+ export type PointPosition = [number, number];
@@ -1,2 +1,10 @@
1
- export declare const randomString: (length: number, chars: string) => string;
2
- export declare const getUniqId: () => string;
1
+ export declare function randomString(length: number, chars: string): string;
2
+ export declare function getUniqId(): string;
3
+ /**
4
+ * Checks Macintosh hardware is used.
5
+ *
6
+ * Note: there is no better way to get this information as using depricated property `navigator.platform`.
7
+ *
8
+ * More details [here](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/platform#examples).
9
+ */
10
+ export declare function isMacintosh(): boolean;
@@ -1,8 +1,20 @@
1
- export const randomString = (length, chars) => {
1
+ export function randomString(length, chars) {
2
2
  let result = '';
3
3
  for (let i = length; i > 0; --i) {
4
4
  result += chars[Math.floor(Math.random() * chars.length)];
5
5
  }
6
6
  return result;
7
- };
8
- export const getUniqId = () => `gravity-chart.${randomString(5, '0123456789abcdefghijklmnopqrstuvwxyz')}`;
7
+ }
8
+ export function getUniqId() {
9
+ return `gravity-chart.${randomString(5, '0123456789abcdefghijklmnopqrstuvwxyz')}`;
10
+ }
11
+ /**
12
+ * Checks Macintosh hardware is used.
13
+ *
14
+ * Note: there is no better way to get this information as using depricated property `navigator.platform`.
15
+ *
16
+ * More details [here](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/platform#examples).
17
+ */
18
+ export function isMacintosh() {
19
+ return typeof navigator === 'undefined' ? false : /Mac|iP(hone|[oa]d)/.test(navigator.platform);
20
+ }
@@ -1,10 +1,4 @@
1
1
  import React from 'react';
2
- import type { ChartData } from '../../types';
2
+ import type { ChartInnerProps } from './types';
3
3
  import './styles.css';
4
- type Props = {
5
- width: number;
6
- height: number;
7
- data: ChartData;
8
- };
9
- export declare const ChartInner: (props: Props) => React.JSX.Element;
10
- export {};
4
+ export declare const ChartInner: (props: ChartInnerProps) => React.JSX.Element;
@@ -1,77 +1,39 @@
1
1
  import React from 'react';
2
- import { pointer } from 'd3';
3
- import throttle from 'lodash/throttle';
4
- import { IS_TOUCH_ENABLED } from '../../constants';
5
- import { useAxisScales, useChartDimensions, useChartOptions, useSeries, useShapes, } from '../../hooks';
6
- import { getYAxisWidth } from '../../hooks/useChartDimensions/utils';
7
- import { getPreparedXAxis } from '../../hooks/useChartOptions/x-axis';
8
- import { getPreparedYAxis } from '../../hooks/useChartOptions/y-axis';
9
- import { useSplit } from '../../hooks/useSplit';
10
2
  import { EventType, block, getD3Dispatcher } from '../../utils';
11
- import { getClosestPoints } from '../../utils/chart/get-closest-data';
12
3
  import { AxisX, AxisY } from '../Axis';
13
4
  import { Legend } from '../Legend';
14
5
  import { PlotTitle } from '../PlotTitle';
15
6
  import { Title } from '../Title';
16
7
  import { Tooltip } from '../Tooltip';
8
+ import { useChartInnerHandlers } from './useChartInnerHandlers';
9
+ import { useChartInnerProps } from './useChartInnerProps';
10
+ import { useChartInnerState } from './useChartInnerState';
17
11
  import './styles.css';
18
12
  const b = block('d3');
19
- const THROTTLE_DELAY = 50;
20
13
  export const ChartInner = (props) => {
21
14
  var _a, _b, _c, _d;
22
15
  const { width, height, data } = props;
23
16
  const svgRef = React.useRef(null);
24
17
  const htmlLayerRef = React.useRef(null);
25
- const dispatcher = React.useMemo(() => {
26
- return getD3Dispatcher();
27
- }, []);
28
- const { chart, title, tooltip } = useChartOptions({
29
- data,
30
- });
31
- const xAxis = React.useMemo(() => getPreparedXAxis({ xAxis: data.xAxis, width, series: data.series.data }), [data, width]);
32
- const yAxis = React.useMemo(() => getPreparedYAxis({
33
- series: data.series.data,
34
- yAxis: data.yAxis,
35
- height,
36
- }), [data, height]);
37
- const { legendItems, legendConfig, preparedSeries, preparedSeriesOptions, preparedLegend, handleLegendItemClick, } = useSeries({
38
- chartWidth: width,
39
- chartHeight: height,
40
- chartMargin: chart.margin,
41
- series: data.series,
42
- legend: data.legend,
43
- preparedYAxis: yAxis,
44
- });
45
- const { boundsWidth, boundsHeight } = useChartDimensions({
46
- width,
47
- height,
48
- margin: chart.margin,
49
- preparedLegend,
50
- preparedXAxis: xAxis,
51
- preparedYAxis: yAxis,
52
- preparedSeries: preparedSeries,
18
+ const dispatcher = React.useMemo(() => getD3Dispatcher(), []);
19
+ const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, legendConfig, legendItems, preparedSeries, preparedSplit, preparedLegend, prevHeight, prevWidth, shapes, shapesData, title, tooltip, xAxis, xScale, yAxis, yScale, } = useChartInnerProps(Object.assign(Object.assign({}, props), { dispatcher, htmlLayout: htmlLayerRef.current }));
20
+ const { tooltipPinned, togglePinTooltip, unpinTooltip } = useChartInnerState({
21
+ dispatcher,
22
+ tooltip,
53
23
  });
54
- const preparedSplit = useSplit({ split: data.split, boundsHeight, chartWidth: width });
55
- const { xScale, yScale } = useAxisScales({
56
- boundsWidth,
24
+ const { handleChartClick, handleMouseLeave, throttledHandleMouseMove, throttledHandleTouchMove } = useChartInnerHandlers({
57
25
  boundsHeight,
58
- series: preparedSeries,
59
- xAxis,
60
- yAxis,
61
- split: preparedSplit,
62
- });
63
- const { shapes, shapesData } = useShapes({
26
+ boundsOffsetLeft,
27
+ boundsOffsetTop,
64
28
  boundsWidth,
65
- boundsHeight,
66
29
  dispatcher,
67
- series: preparedSeries,
68
- seriesOptions: preparedSeriesOptions,
30
+ shapesData,
31
+ svgContainer: svgRef.current,
32
+ togglePinTooltip,
33
+ tooltipPinned,
34
+ unpinTooltip,
69
35
  xAxis,
70
- xScale,
71
36
  yAxis,
72
- yScale,
73
- split: preparedSplit,
74
- htmlLayout: htmlLayerRef.current,
75
37
  });
76
38
  const clickHandler = (_b = (_a = data.chart) === null || _a === void 0 ? void 0 : _a.events) === null || _b === void 0 ? void 0 : _b.click;
77
39
  const pointerMoveHandler = (_d = (_c = data.chart) === null || _c === void 0 ? void 0 : _c.events) === null || _d === void 0 ? void 0 : _d.pointermove;
@@ -90,68 +52,11 @@ export const ChartInner = (props) => {
90
52
  dispatcher.on(EventType.POINTERMOVE_CHART, null);
91
53
  };
92
54
  }, [dispatcher, clickHandler, pointerMoveHandler]);
93
- const boundsOffsetTop = chart.margin.top;
94
- // We only need to consider the width of the first left axis
95
- const boundsOffsetLeft = chart.margin.left + getYAxisWidth(yAxis[0]);
96
- const isOutsideBounds = React.useCallback((x, y) => {
97
- return x < 0 || x > boundsWidth || y < 0 || y > boundsHeight;
98
- }, [boundsHeight, boundsWidth]);
99
- const handleMove = ([pointerX, pointerY], event) => {
100
- const x = pointerX - boundsOffsetLeft;
101
- const y = pointerY - boundsOffsetTop;
102
- if (isOutsideBounds(x, y)) {
103
- dispatcher.call(EventType.HOVER_SHAPE, {}, undefined);
104
- dispatcher.call(EventType.POINTERMOVE_CHART, {}, undefined, event);
105
- return;
106
- }
107
- const closest = getClosestPoints({
108
- position: [x, y],
109
- shapesData,
110
- });
111
- dispatcher.call(EventType.HOVER_SHAPE, event.target, closest, [pointerX, pointerY]);
112
- dispatcher.call(EventType.POINTERMOVE_CHART, {}, {
113
- hovered: closest,
114
- xAxis,
115
- yAxis: yAxis[0],
116
- }, event);
117
- };
118
- const handleMouseMove = (event) => {
119
- const [pointerX, pointerY] = pointer(event, svgRef.current);
120
- handleMove([pointerX, pointerY], event);
121
- };
122
- const throttledHandleMouseMove = IS_TOUCH_ENABLED
123
- ? undefined
124
- : throttle(handleMouseMove, THROTTLE_DELAY);
125
- const handleMouseLeave = (event) => {
126
- throttledHandleMouseMove === null || throttledHandleMouseMove === void 0 ? void 0 : throttledHandleMouseMove.cancel();
127
- dispatcher.call(EventType.HOVER_SHAPE, {}, undefined);
128
- dispatcher.call(EventType.POINTERMOVE_CHART, {}, undefined, event);
129
- };
130
- const handleTouchMove = (event) => {
131
- const touch = event.touches[0];
132
- const [pointerX, pointerY] = pointer(touch, svgRef.current);
133
- handleMove([pointerX, pointerY], event);
134
- };
135
- const throttledHandleTouchMove = IS_TOUCH_ENABLED
136
- ? throttle(handleTouchMove, THROTTLE_DELAY)
137
- : undefined;
138
- const handleChartClick = React.useCallback((event) => {
139
- const [pointerX, pointerY] = pointer(event, svgRef.current);
140
- const x = pointerX - boundsOffsetLeft;
141
- const y = pointerY - boundsOffsetTop;
142
- if (isOutsideBounds(x, y)) {
143
- return;
144
- }
145
- const items = getClosestPoints({
146
- position: [x, y],
147
- shapesData,
148
- });
149
- const selected = items === null || items === void 0 ? void 0 : items.find((item) => item.closest);
150
- if (!selected) {
151
- return;
55
+ React.useEffect(() => {
56
+ if ((prevWidth !== width || prevHeight !== height) && tooltipPinned) {
57
+ unpinTooltip === null || unpinTooltip === void 0 ? void 0 : unpinTooltip();
152
58
  }
153
- dispatcher.call('click-chart', undefined, { point: selected.data, series: selected.series }, event);
154
- }, [boundsOffsetLeft, boundsOffsetTop, dispatcher, isOutsideBounds, shapesData]);
59
+ }, [prevWidth, width, prevHeight, height, tooltipPinned, unpinTooltip]);
155
60
  return (React.createElement(React.Fragment, null,
156
61
  React.createElement("svg", { ref: svgRef, className: b(), width: width, height: height, onMouseMove: throttledHandleMouseMove, onMouseLeave: handleMouseLeave, onTouchStart: throttledHandleTouchMove, onTouchMove: throttledHandleTouchMove, onClick: handleChartClick },
157
62
  title && React.createElement(Title, Object.assign({}, title, { chartWidth: width })),
@@ -164,9 +69,9 @@ export const ChartInner = (props) => {
164
69
  React.createElement("g", { transform: `translate(0, ${boundsHeight})` },
165
70
  React.createElement(AxisX, { axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale, split: preparedSplit })))),
166
71
  shapes),
167
- preparedLegend.enabled && (React.createElement(Legend, { chartSeries: preparedSeries, boundsWidth: boundsWidth, legend: preparedLegend, items: legendItems, config: legendConfig, onItemClick: handleLegendItemClick }))),
72
+ preparedLegend.enabled && (React.createElement(Legend, { chartSeries: preparedSeries, boundsWidth: boundsWidth, legend: preparedLegend, items: legendItems, config: legendConfig, onItemClick: handleLegendItemClick, onUpdate: unpinTooltip }))),
168
73
  React.createElement("div", { className: b('html-layer'), ref: htmlLayerRef, style: {
169
74
  transform: `translate(${boundsOffsetLeft}px, ${boundsOffsetTop}px)`,
170
75
  } }),
171
- React.createElement(Tooltip, { dispatcher: dispatcher, tooltip: tooltip, svgContainer: svgRef.current, xAxis: xAxis, yAxis: yAxis[0] })));
76
+ React.createElement(Tooltip, { dispatcher: dispatcher, tooltip: tooltip, svgContainer: svgRef.current, xAxis: xAxis, yAxis: yAxis[0], onOutsideClick: unpinTooltip, tooltipPinned: tooltipPinned })));
172
77
  };
@@ -0,0 +1,6 @@
1
+ import type { ChartData } from '../../types';
2
+ export type ChartInnerProps = {
3
+ width: number;
4
+ height: number;
5
+ data: ChartData;
6
+ };
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+ import type { Dispatch } from 'd3';
3
+ import type { PreparedAxis, ShapeData } from '../../hooks';
4
+ import type { useChartInnerState } from './useChartInnerState';
5
+ type ChartInnerState = ReturnType<typeof useChartInnerState>;
6
+ type Props = {
7
+ boundsHeight: number;
8
+ boundsOffsetLeft: number;
9
+ boundsOffsetTop: number;
10
+ boundsWidth: number;
11
+ dispatcher: Dispatch<object>;
12
+ shapesData: ShapeData[];
13
+ svgContainer: SVGSVGElement | null;
14
+ togglePinTooltip: ChartInnerState['togglePinTooltip'];
15
+ tooltipPinned: boolean;
16
+ unpinTooltip: ChartInnerState['unpinTooltip'];
17
+ xAxis: PreparedAxis;
18
+ yAxis: PreparedAxis[];
19
+ };
20
+ export declare function useChartInnerHandlers(props: Props): {
21
+ handleChartClick: (event: React.MouseEvent<SVGSVGElement>) => void;
22
+ handleMouseLeave: React.MouseEventHandler<SVGSVGElement>;
23
+ throttledHandleMouseMove: import("lodash").DebouncedFuncLeading<React.MouseEventHandler<SVGSVGElement>> | undefined;
24
+ throttledHandleTouchMove: import("lodash").DebouncedFuncLeading<React.TouchEventHandler<SVGSVGElement>> | undefined;
25
+ };
26
+ export {};
@@ -0,0 +1,94 @@
1
+ import React from 'react';
2
+ import { pointer } from 'd3';
3
+ import throttle from 'lodash/throttle';
4
+ import { IS_TOUCH_ENABLED } from '../../constants';
5
+ import { EventType } from '../../utils';
6
+ import { getClosestPoints } from '../../utils/chart/get-closest-data';
7
+ const THROTTLE_DELAY = 50;
8
+ export function useChartInnerHandlers(props) {
9
+ const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, dispatcher, shapesData, svgContainer, togglePinTooltip, tooltipPinned, unpinTooltip, xAxis, yAxis, } = props;
10
+ const isOutsideBounds = React.useCallback((x, y) => {
11
+ return x < 0 || x > boundsWidth || y < 0 || y > boundsHeight;
12
+ }, [boundsHeight, boundsWidth]);
13
+ const handleMove = ([pointerX, pointerY], event) => {
14
+ if (tooltipPinned) {
15
+ return;
16
+ }
17
+ const x = pointerX - boundsOffsetLeft;
18
+ const y = pointerY - boundsOffsetTop;
19
+ if (isOutsideBounds(x, y)) {
20
+ dispatcher.call(EventType.HOVER_SHAPE, {}, undefined);
21
+ dispatcher.call(EventType.POINTERMOVE_CHART, {}, undefined, event);
22
+ return;
23
+ }
24
+ const closest = getClosestPoints({
25
+ position: [x, y],
26
+ shapesData,
27
+ });
28
+ dispatcher.call(EventType.HOVER_SHAPE, event.target, closest, [pointerX, pointerY]);
29
+ dispatcher.call(EventType.POINTERMOVE_CHART, {}, {
30
+ hovered: closest,
31
+ xAxis,
32
+ yAxis: yAxis[0],
33
+ }, event);
34
+ };
35
+ const handleMouseMove = (event) => {
36
+ const [pointerX, pointerY] = pointer(event, svgContainer);
37
+ handleMove([pointerX, pointerY], event);
38
+ };
39
+ const throttledHandleMouseMove = IS_TOUCH_ENABLED
40
+ ? undefined
41
+ : throttle(handleMouseMove, THROTTLE_DELAY);
42
+ const handleMouseLeave = (event) => {
43
+ if (tooltipPinned) {
44
+ return;
45
+ }
46
+ throttledHandleMouseMove === null || throttledHandleMouseMove === void 0 ? void 0 : throttledHandleMouseMove.cancel();
47
+ dispatcher.call(EventType.HOVER_SHAPE, {}, undefined);
48
+ dispatcher.call(EventType.POINTERMOVE_CHART, {}, undefined, event);
49
+ };
50
+ const handleTouchMove = (event) => {
51
+ const touch = event.touches[0];
52
+ const [pointerX, pointerY] = pointer(touch, svgContainer);
53
+ handleMove([pointerX, pointerY], event);
54
+ };
55
+ const throttledHandleTouchMove = IS_TOUCH_ENABLED
56
+ ? throttle(handleTouchMove, THROTTLE_DELAY)
57
+ : undefined;
58
+ const handleChartClick = (event) => {
59
+ const [pointerX, pointerY] = pointer(event, svgContainer);
60
+ const x = pointerX - boundsOffsetLeft;
61
+ const y = pointerY - boundsOffsetTop;
62
+ if (isOutsideBounds(x, y)) {
63
+ return;
64
+ }
65
+ const items = getClosestPoints({
66
+ position: [x, y],
67
+ shapesData,
68
+ });
69
+ const selected = items === null || items === void 0 ? void 0 : items.find((item) => item.closest);
70
+ if (!selected) {
71
+ if (tooltipPinned) {
72
+ unpinTooltip === null || unpinTooltip === void 0 ? void 0 : unpinTooltip();
73
+ }
74
+ return;
75
+ }
76
+ dispatcher.call(EventType.CLICK_CHART, undefined, { point: selected.data, series: selected.series }, event);
77
+ const nextTooltipFixed = !tooltipPinned;
78
+ if (!nextTooltipFixed) {
79
+ dispatcher.call(EventType.HOVER_SHAPE, event.target, items, [pointerX, pointerY]);
80
+ dispatcher.call(EventType.POINTERMOVE_CHART, {}, {
81
+ hovered: items,
82
+ xAxis,
83
+ yAxis: yAxis[0],
84
+ }, event);
85
+ }
86
+ togglePinTooltip === null || togglePinTooltip === void 0 ? void 0 : togglePinTooltip(nextTooltipFixed, event);
87
+ };
88
+ return {
89
+ handleChartClick,
90
+ handleMouseLeave,
91
+ throttledHandleMouseMove,
92
+ throttledHandleTouchMove,
93
+ };
94
+ }
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+ import type { Dispatch } from 'd3';
3
+ import type { ChartInnerProps } from './types';
4
+ type Props = ChartInnerProps & {
5
+ dispatcher: Dispatch<object>;
6
+ htmlLayout: HTMLElement | null;
7
+ };
8
+ export declare function useChartInnerProps(props: Props): {
9
+ boundsHeight: number;
10
+ boundsOffsetLeft: number;
11
+ boundsOffsetTop: number;
12
+ boundsWidth: number;
13
+ handleLegendItemClick: import("../../hooks").OnLegendItemClick;
14
+ legendConfig: {
15
+ offset: {
16
+ left: number;
17
+ top: number;
18
+ };
19
+ pagination: {
20
+ limit: number;
21
+ maxPage: number;
22
+ } | undefined;
23
+ };
24
+ legendItems: import("../../hooks").LegendItem[][];
25
+ preparedLegend: import("../../hooks").PreparedLegend;
26
+ preparedSeries: import("../../hooks").PreparedSeries[];
27
+ preparedSplit: import("../../hooks").PreparedSplit;
28
+ prevHeight: number | undefined;
29
+ prevWidth: number | undefined;
30
+ shapes: React.ReactElement<any, string | React.JSXElementConstructor<any>>[];
31
+ shapesData: import("../../hooks").ShapeData[];
32
+ title: (import("../../types").ChartTitle & {
33
+ height: number;
34
+ }) | undefined;
35
+ tooltip: import("../../types").ChartTooltip<any> & {
36
+ enabled: boolean;
37
+ };
38
+ xAxis: import("../../hooks").PreparedAxis;
39
+ xScale: import("../../hooks").ChartScale | undefined;
40
+ yAxis: import("../../hooks").PreparedAxis[];
41
+ yScale: import("../../hooks").ChartScale[] | undefined;
42
+ };
43
+ export {};
@@ -0,0 +1,81 @@
1
+ import React from 'react';
2
+ import { useAxisScales, useChartDimensions, useChartOptions, usePrevious, useSeries, useShapes, useSplit, } from '../../hooks';
3
+ import { getYAxisWidth } from '../../hooks/useChartDimensions/utils';
4
+ import { getPreparedXAxis } from '../../hooks/useChartOptions/x-axis';
5
+ import { getPreparedYAxis } from '../../hooks/useChartOptions/y-axis';
6
+ export function useChartInnerProps(props) {
7
+ const { width, height, data, dispatcher, htmlLayout } = props;
8
+ const prevWidth = usePrevious(width);
9
+ const prevHeight = usePrevious(height);
10
+ const { chart, title, tooltip } = useChartOptions({ data });
11
+ const xAxis = React.useMemo(() => getPreparedXAxis({ xAxis: data.xAxis, width, series: data.series.data }), [data, width]);
12
+ const yAxis = React.useMemo(() => getPreparedYAxis({
13
+ series: data.series.data,
14
+ yAxis: data.yAxis,
15
+ height,
16
+ }), [data, height]);
17
+ const { legendItems, legendConfig, preparedSeries, preparedSeriesOptions, preparedLegend, handleLegendItemClick, } = useSeries({
18
+ chartWidth: width,
19
+ chartHeight: height,
20
+ chartMargin: chart.margin,
21
+ series: data.series,
22
+ legend: data.legend,
23
+ preparedYAxis: yAxis,
24
+ });
25
+ const { boundsWidth, boundsHeight } = useChartDimensions({
26
+ width,
27
+ height,
28
+ margin: chart.margin,
29
+ preparedLegend,
30
+ preparedXAxis: xAxis,
31
+ preparedYAxis: yAxis,
32
+ preparedSeries: preparedSeries,
33
+ });
34
+ const preparedSplit = useSplit({ split: data.split, boundsHeight, chartWidth: width });
35
+ const { xScale, yScale } = useAxisScales({
36
+ boundsWidth,
37
+ boundsHeight,
38
+ series: preparedSeries,
39
+ xAxis,
40
+ yAxis,
41
+ split: preparedSplit,
42
+ });
43
+ const { shapes, shapesData } = useShapes({
44
+ boundsWidth,
45
+ boundsHeight,
46
+ dispatcher,
47
+ series: preparedSeries,
48
+ seriesOptions: preparedSeriesOptions,
49
+ xAxis,
50
+ xScale,
51
+ yAxis,
52
+ yScale,
53
+ split: preparedSplit,
54
+ htmlLayout,
55
+ });
56
+ const boundsOffsetTop = chart.margin.top;
57
+ // We only need to consider the width of the first left axis
58
+ const boundsOffsetLeft = chart.margin.left + getYAxisWidth(yAxis[0]);
59
+ return {
60
+ boundsHeight,
61
+ boundsOffsetLeft,
62
+ boundsOffsetTop,
63
+ boundsWidth,
64
+ handleLegendItemClick,
65
+ legendConfig,
66
+ legendItems,
67
+ preparedLegend,
68
+ preparedSeries,
69
+ preparedSplit,
70
+ prevHeight,
71
+ prevWidth,
72
+ shapes,
73
+ shapesData,
74
+ title,
75
+ tooltip,
76
+ xAxis,
77
+ xScale,
78
+ yAxis,
79
+ yScale,
80
+ };
81
+ }
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import type { Dispatch } from 'd3';
3
+ import type { ChartTooltip } from '../../types';
4
+ type Props = {
5
+ dispatcher: Dispatch<object>;
6
+ tooltip?: ChartTooltip;
7
+ };
8
+ export declare function useChartInnerState(props: Props): {
9
+ tooltipPinned: boolean;
10
+ togglePinTooltip: ((value: boolean, event: React.MouseEvent) => void) | undefined;
11
+ unpinTooltip: (() => void) | undefined;
12
+ };
13
+ export {};
@@ -0,0 +1,34 @@
1
+ import React from 'react';
2
+ import { EventType, isMacintosh } from '../../utils';
3
+ export function useChartInnerState(props) {
4
+ var _a, _b;
5
+ const { dispatcher, tooltip } = props;
6
+ const [tooltipPinned, setTooltipPinned] = React.useState(false);
7
+ const tooltipEnabled = tooltip === null || tooltip === void 0 ? void 0 : tooltip.enabled;
8
+ const tooltipPinEnabled = (_a = tooltip === null || tooltip === void 0 ? void 0 : tooltip.pin) === null || _a === void 0 ? void 0 : _a.enabled;
9
+ const modifierKey = (_b = tooltip === null || tooltip === void 0 ? void 0 : tooltip.pin) === null || _b === void 0 ? void 0 : _b.modifierKey;
10
+ const togglePinTooltip = React.useCallback((value, event) => {
11
+ let resultValue = value;
12
+ if (value && modifierKey) {
13
+ switch (modifierKey) {
14
+ case 'altKey': {
15
+ resultValue = event.altKey;
16
+ break;
17
+ }
18
+ case 'metaKey': {
19
+ resultValue = isMacintosh() ? event.metaKey : event.ctrlKey;
20
+ }
21
+ }
22
+ }
23
+ setTooltipPinned(resultValue);
24
+ }, [modifierKey]);
25
+ const unpinTooltip = React.useCallback(() => {
26
+ setTooltipPinned(false);
27
+ dispatcher.call(EventType.HOVER_SHAPE, {}, undefined);
28
+ }, [dispatcher]);
29
+ return {
30
+ tooltipPinned,
31
+ togglePinTooltip: tooltipEnabled && tooltipPinEnabled ? togglePinTooltip : undefined,
32
+ unpinTooltip: tooltipEnabled && tooltipPinEnabled ? unpinTooltip : undefined,
33
+ };
34
+ }
@@ -8,6 +8,7 @@ type Props = {
8
8
  items: LegendItem[][];
9
9
  config: LegendConfig;
10
10
  onItemClick: OnLegendItemClick;
11
+ onUpdate?: () => void;
11
12
  };
12
13
  export declare const Legend: (props: Props) => React.JSX.Element;
13
14
  export {};
@@ -134,7 +134,7 @@ function renderLegendSymbol(args) {
134
134
  });
135
135
  }
136
136
  export const Legend = (props) => {
137
- const { boundsWidth, chartSeries, legend, items, config, onItemClick } = props;
137
+ const { boundsWidth, chartSeries, legend, items, config, onItemClick, onUpdate } = props;
138
138
  const ref = React.useRef(null);
139
139
  const [paginationOffset, setPaginationOffset] = React.useState(0);
140
140
  React.useEffect(() => {
@@ -164,6 +164,7 @@ export const Legend = (props) => {
164
164
  .attr('class', b('item'))
165
165
  .on('click', function (e, d) {
166
166
  onItemClick({ name: d.name, metaKey: e.metaKey });
167
+ onUpdate === null || onUpdate === void 0 ? void 0 : onUpdate();
167
168
  });
168
169
  const getXPosition = (i) => {
169
170
  return line.slice(0, i).reduce((acc, legendItem) => {
@@ -292,6 +293,6 @@ export const Legend = (props) => {
292
293
  contentWidth: legendWidth,
293
294
  });
294
295
  svgElement.attr('transform', `translate(${[left, config.offset.top].join(',')})`);
295
- }, [boundsWidth, chartSeries, onItemClick, legend, items, config, paginationOffset]);
296
+ }, [boundsWidth, chartSeries, onItemClick, onUpdate, legend, items, config, paginationOffset]);
296
297
  return React.createElement("g", { className: b(), ref: ref, width: boundsWidth, height: legend.height });
297
298
  };