@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.
- 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 +10 -4
- package/dist/cjs/components/Tooltip/styles.css +13 -8
- 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/area/prepare-data.js +4 -0
- package/dist/cjs/hooks/useShapes/bar-x/index.js +8 -2
- package/dist/cjs/hooks/useShapes/bar-x/prepare-data.js +1 -0
- package/dist/cjs/hooks/useShapes/bar-y/index.js +8 -2
- package/dist/cjs/hooks/useShapes/bar-y/prepare-data.js +1 -0
- package/dist/cjs/hooks/useShapes/line/index.js +8 -2
- package/dist/cjs/hooks/useShapes/line/prepare-data.js +1 -0
- package/dist/cjs/hooks/useShapes/pie/index.js +2 -1
- package/dist/cjs/hooks/useShapes/pie/prepare-data.js +159 -111
- package/dist/cjs/hooks/useShapes/pie/types.d.ts +1 -1
- 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/treemap/prepare-data.js +1 -0
- 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/chart-ui.d.ts +4 -0
- 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 +10 -4
- package/dist/esm/components/Tooltip/styles.css +13 -8
- 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/area/prepare-data.js +4 -0
- package/dist/esm/hooks/useShapes/bar-x/index.js +8 -2
- package/dist/esm/hooks/useShapes/bar-x/prepare-data.js +1 -0
- package/dist/esm/hooks/useShapes/bar-y/index.js +8 -2
- package/dist/esm/hooks/useShapes/bar-y/prepare-data.js +1 -0
- package/dist/esm/hooks/useShapes/line/index.js +8 -2
- package/dist/esm/hooks/useShapes/line/prepare-data.js +1 -0
- package/dist/esm/hooks/useShapes/pie/index.js +2 -1
- package/dist/esm/hooks/useShapes/pie/prepare-data.js +159 -111
- package/dist/esm/hooks/useShapes/pie/types.d.ts +1 -1
- 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/treemap/prepare-data.js +1 -0
- 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/chart-ui.d.ts +4 -0
- 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,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:
|
|
10
|
+
pointerPosition: PointPosition | undefined;
|
|
12
11
|
};
|
|
13
12
|
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MeaningfulAny } from '../misc';
|
|
2
|
-
import type {
|
|
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:
|
|
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
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
70
|
+
renderer?: (args: ChartTooltipRendererArgs<T>) => React.ReactElement | null;
|
|
71
|
+
pin?: {
|
|
72
|
+
enabled?: boolean;
|
|
73
|
+
modifierKey?: 'altKey' | 'metaKey';
|
|
74
|
+
};
|
|
75
75
|
};
|
package/dist/cjs/types/misc.d.ts
CHANGED
package/dist/cjs/utils/misc.d.ts
CHANGED
|
@@ -1,2 +1,10 @@
|
|
|
1
|
-
export declare
|
|
2
|
-
export declare
|
|
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;
|
package/dist/cjs/utils/misc.js
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
|
-
export
|
|
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
|
|
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 {
|
|
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
|
};
|