@gravity-ui/charts 0.5.0 → 0.6.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.
- package/dist/cjs/components/ChartInner/index.d.ts +2 -8
- package/dist/cjs/components/ChartInner/index.js +22 -117
- package/dist/cjs/components/ChartInner/types.d.ts +6 -0
- package/dist/cjs/components/ChartInner/useChartInnerHandlers.d.ts +26 -0
- package/dist/cjs/components/ChartInner/useChartInnerHandlers.js +94 -0
- package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +43 -0
- package/dist/cjs/components/ChartInner/useChartInnerProps.js +81 -0
- package/dist/cjs/components/ChartInner/useChartInnerState.d.ts +13 -0
- package/dist/cjs/components/ChartInner/useChartInnerState.js +34 -0
- package/dist/cjs/components/Legend/index.d.ts +1 -0
- package/dist/cjs/components/Legend/index.js +3 -2
- package/dist/cjs/components/Tooltip/index.d.ts +2 -0
- package/dist/cjs/components/Tooltip/index.js +9 -3
- package/dist/cjs/components/Tooltip/styles.css +3 -0
- package/dist/cjs/hooks/index.d.ts +2 -1
- package/dist/cjs/hooks/index.js +2 -1
- package/dist/cjs/hooks/usePrevious/index.d.ts +1 -0
- package/dist/cjs/hooks/usePrevious/index.js +8 -0
- package/dist/cjs/hooks/useShapes/area/index.js +8 -2
- package/dist/cjs/hooks/useShapes/bar-x/index.js +8 -2
- package/dist/cjs/hooks/useShapes/bar-y/index.js +8 -2
- package/dist/cjs/hooks/useShapes/line/index.js +8 -2
- package/dist/cjs/hooks/useShapes/scatter/index.js +8 -2
- package/dist/cjs/hooks/useShapes/treemap/index.js +8 -2
- package/dist/cjs/hooks/useShapes/utils.d.ts +2 -2
- package/dist/cjs/hooks/useShapes/utils.js +5 -3
- package/dist/cjs/hooks/useShapes/waterfall/index.js +8 -2
- package/dist/cjs/hooks/useTooltip/index.d.ts +2 -3
- package/dist/cjs/types/chart/chart.d.ts +2 -2
- package/dist/cjs/types/chart/series.d.ts +1 -2
- package/dist/cjs/types/chart/tooltip.d.ts +6 -6
- package/dist/cjs/types/misc.d.ts +1 -0
- package/dist/cjs/utils/misc.d.ts +10 -2
- package/dist/cjs/utils/misc.js +15 -3
- package/dist/esm/components/ChartInner/index.d.ts +2 -8
- package/dist/esm/components/ChartInner/index.js +22 -117
- package/dist/esm/components/ChartInner/types.d.ts +6 -0
- package/dist/esm/components/ChartInner/useChartInnerHandlers.d.ts +26 -0
- package/dist/esm/components/ChartInner/useChartInnerHandlers.js +94 -0
- package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +43 -0
- package/dist/esm/components/ChartInner/useChartInnerProps.js +81 -0
- package/dist/esm/components/ChartInner/useChartInnerState.d.ts +13 -0
- package/dist/esm/components/ChartInner/useChartInnerState.js +34 -0
- package/dist/esm/components/Legend/index.d.ts +1 -0
- package/dist/esm/components/Legend/index.js +3 -2
- package/dist/esm/components/Tooltip/index.d.ts +2 -0
- package/dist/esm/components/Tooltip/index.js +9 -3
- package/dist/esm/components/Tooltip/styles.css +3 -0
- package/dist/esm/hooks/index.d.ts +2 -1
- package/dist/esm/hooks/index.js +2 -1
- package/dist/esm/hooks/usePrevious/index.d.ts +1 -0
- package/dist/esm/hooks/usePrevious/index.js +8 -0
- package/dist/esm/hooks/useShapes/area/index.js +8 -2
- package/dist/esm/hooks/useShapes/bar-x/index.js +8 -2
- package/dist/esm/hooks/useShapes/bar-y/index.js +8 -2
- package/dist/esm/hooks/useShapes/line/index.js +8 -2
- package/dist/esm/hooks/useShapes/scatter/index.js +8 -2
- package/dist/esm/hooks/useShapes/treemap/index.js +8 -2
- package/dist/esm/hooks/useShapes/utils.d.ts +2 -2
- package/dist/esm/hooks/useShapes/utils.js +5 -3
- package/dist/esm/hooks/useShapes/waterfall/index.js +8 -2
- package/dist/esm/hooks/useTooltip/index.d.ts +2 -3
- package/dist/esm/types/chart/chart.d.ts +2 -2
- package/dist/esm/types/chart/series.d.ts +1 -2
- package/dist/esm/types/chart/tooltip.d.ts +6 -6
- package/dist/esm/types/misc.d.ts +1 -0
- package/dist/esm/utils/misc.d.ts +10 -2
- package/dist/esm/utils/misc.js +15 -3
- package/package.json +1 -1
- package/dist/cjs/hooks/useTooltip/types.d.ts +0 -1
- package/dist/esm/hooks/useTooltip/types.d.ts +0 -1
- /package/dist/cjs/{hooks/useTooltip → components/ChartInner}/types.js +0 -0
- /package/dist/esm/{hooks/useTooltip → components/ChartInner}/types.js +0 -0
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type {
|
|
2
|
+
import type { ChartInnerProps } from './types';
|
|
3
3
|
import './styles.css';
|
|
4
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
55
|
-
const { xScale, yScale } = useAxisScales({
|
|
56
|
-
boundsWidth,
|
|
24
|
+
const { handleChartClick, handleMouseLeave, throttledHandleMouseMove, throttledHandleTouchMove } = useChartInnerHandlers({
|
|
57
25
|
boundsHeight,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
yAxis,
|
|
61
|
-
split: preparedSplit,
|
|
62
|
-
});
|
|
63
|
-
const { shapes, shapesData } = useShapes({
|
|
26
|
+
boundsOffsetLeft,
|
|
27
|
+
boundsOffsetTop,
|
|
64
28
|
boundsWidth,
|
|
65
|
-
boundsHeight,
|
|
66
29
|
dispatcher,
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
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,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
|
+
}
|
|
@@ -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
|
};
|
|
@@ -8,6 +8,8 @@ type TooltipProps = {
|
|
|
8
8
|
svgContainer: SVGSVGElement | null;
|
|
9
9
|
xAxis: PreparedAxis;
|
|
10
10
|
yAxis: PreparedAxis;
|
|
11
|
+
tooltipPinned: boolean;
|
|
12
|
+
onOutsideClick?: () => void;
|
|
11
13
|
};
|
|
12
14
|
export declare const Tooltip: (props: TooltipProps) => React.JSX.Element | null;
|
|
13
15
|
export {};
|
|
@@ -6,16 +6,22 @@ import { ChartTooltipContent } from './ChartTooltipContent';
|
|
|
6
6
|
import './styles.css';
|
|
7
7
|
const b = block('d3-tooltip');
|
|
8
8
|
export const Tooltip = (props) => {
|
|
9
|
-
const { tooltip, xAxis, yAxis, svgContainer, dispatcher } = props;
|
|
9
|
+
const { tooltip, xAxis, yAxis, svgContainer, dispatcher, tooltipPinned, onOutsideClick } = props;
|
|
10
10
|
const { hovered, pointerPosition } = useTooltip({ dispatcher, tooltip });
|
|
11
11
|
const containerRect = (svgContainer === null || svgContainer === void 0 ? void 0 : svgContainer.getBoundingClientRect()) || { left: 0, top: 0 };
|
|
12
12
|
const left = ((pointerPosition === null || pointerPosition === void 0 ? void 0 : pointerPosition[0]) || 0) + containerRect.left;
|
|
13
13
|
const top = ((pointerPosition === null || pointerPosition === void 0 ? void 0 : pointerPosition[1]) || 0) + containerRect.top;
|
|
14
|
-
const anchorRef = useVirtualElementRef({ rect: {
|
|
14
|
+
const anchorRef = useVirtualElementRef({ rect: { left, top } });
|
|
15
|
+
const handleOutsideClick = (e) => {
|
|
16
|
+
if (svgContainer === null || svgContainer === void 0 ? void 0 : svgContainer.contains(e.target)) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
onOutsideClick === null || onOutsideClick === void 0 ? void 0 : onOutsideClick();
|
|
20
|
+
};
|
|
15
21
|
React.useEffect(() => {
|
|
16
22
|
window.dispatchEvent(new CustomEvent('scroll'));
|
|
17
23
|
}, [left, top]);
|
|
18
|
-
return (hovered === null || hovered === void 0 ? void 0 : hovered.length) ? (React.createElement(Popup, { className: b(), open: true, anchorRef: anchorRef, offset: [0, 20], placement: ['right', 'left', 'top', 'bottom'], modifiers: [{ name: 'preventOverflow', options: { padding: 10, altAxis: true } }] },
|
|
24
|
+
return (hovered === null || hovered === void 0 ? void 0 : hovered.length) ? (React.createElement(Popup, { className: b({ pinned: tooltipPinned }), open: true, anchorRef: anchorRef, offset: [0, 20], placement: ['right', 'left', 'top', 'bottom'], modifiers: [{ name: 'preventOverflow', options: { padding: 10, altAxis: true } }], onOutsideClick: tooltipPinned ? handleOutsideClick : undefined },
|
|
19
25
|
React.createElement("div", { className: b('content') },
|
|
20
26
|
React.createElement(ChartTooltipContent, { hovered: hovered, xAxis: xAxis, yAxis: yAxis, renderer: tooltip.renderer })))) : null;
|
|
21
27
|
};
|
|
@@ -2,9 +2,10 @@ export * from './useChartDimensions';
|
|
|
2
2
|
export * from './useChartOptions';
|
|
3
3
|
export * from './useChartOptions/types';
|
|
4
4
|
export * from './useAxisScales';
|
|
5
|
+
export * from './usePrevious';
|
|
5
6
|
export * from './useSeries';
|
|
6
7
|
export * from './useSeries/types';
|
|
7
8
|
export * from './useShapes';
|
|
8
9
|
export * from './useTooltip';
|
|
9
|
-
export * from './
|
|
10
|
+
export * from './useSplit';
|
|
10
11
|
export * from './useSplit/types';
|
package/dist/cjs/hooks/index.js
CHANGED
|
@@ -2,9 +2,10 @@ export * from './useChartDimensions';
|
|
|
2
2
|
export * from './useChartOptions';
|
|
3
3
|
export * from './useChartOptions/types';
|
|
4
4
|
export * from './useAxisScales';
|
|
5
|
+
export * from './usePrevious';
|
|
5
6
|
export * from './useSeries';
|
|
6
7
|
export * from './useSeries/types';
|
|
7
8
|
export * from './useShapes';
|
|
8
9
|
export * from './useTooltip';
|
|
9
|
-
export * from './
|
|
10
|
+
export * from './useSplit';
|
|
10
11
|
export * from './useSplit/types';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function usePrevious<T>(value: T): T | undefined;
|
|
@@ -8,6 +8,7 @@ import { setActiveState } from '../utils';
|
|
|
8
8
|
const b = block('d3-area');
|
|
9
9
|
export const AreaSeriesShapes = (args) => {
|
|
10
10
|
const { dispatcher, preparedData, seriesOptions, htmlLayout } = args;
|
|
11
|
+
const hoveredDataRef = React.useRef(null);
|
|
11
12
|
const ref = React.useRef(null);
|
|
12
13
|
React.useEffect(() => {
|
|
13
14
|
var _a;
|
|
@@ -72,7 +73,8 @@ export const AreaSeriesShapes = (args) => {
|
|
|
72
73
|
.call(renderMarker);
|
|
73
74
|
const hoverEnabled = hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.enabled;
|
|
74
75
|
const inactiveEnabled = inactiveOptions === null || inactiveOptions === void 0 ? void 0 : inactiveOptions.enabled;
|
|
75
|
-
|
|
76
|
+
function handleShapeHover(data) {
|
|
77
|
+
hoveredDataRef.current = data;
|
|
76
78
|
const selected = (data === null || data === void 0 ? void 0 : data.filter((d) => d.series.type === 'area')) || [];
|
|
77
79
|
const selectedDataItems = selected.map((d) => d.data);
|
|
78
80
|
const selectedSeriesIds = selected.map((d) => { var _a; return (_a = d.series) === null || _a === void 0 ? void 0 : _a.id; });
|
|
@@ -132,7 +134,11 @@ export const AreaSeriesShapes = (args) => {
|
|
|
132
134
|
}
|
|
133
135
|
return d;
|
|
134
136
|
});
|
|
135
|
-
}
|
|
137
|
+
}
|
|
138
|
+
if (hoveredDataRef.current !== null) {
|
|
139
|
+
handleShapeHover(hoveredDataRef.current);
|
|
140
|
+
}
|
|
141
|
+
dispatcher.on('hover-shape.area', handleShapeHover);
|
|
136
142
|
return () => {
|
|
137
143
|
dispatcher.on('hover-shape.area', null);
|
|
138
144
|
};
|