@gravity-ui/chartkit 5.3.3 → 5.4.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/build/plugins/d3/renderer/components/Chart.js +28 -8
- package/build/plugins/d3/renderer/components/Tooltip/DefaultContent.js +53 -40
- package/build/plugins/d3/renderer/components/Tooltip/index.d.ts +2 -5
- package/build/plugins/d3/renderer/components/Tooltip/index.js +4 -3
- package/build/plugins/d3/renderer/components/styles.css +14 -0
- package/build/plugins/d3/renderer/hooks/useShapes/area/index.js +13 -10
- package/build/plugins/d3/renderer/hooks/useShapes/index.d.ts +0 -1
- package/build/plugins/d3/renderer/hooks/useShapes/index.js +7 -5
- package/build/plugins/d3/renderer/hooks/useShapes/line/index.js +13 -10
- package/build/plugins/d3/renderer/hooks/useShapes/pie/index.d.ts +0 -1
- package/build/plugins/d3/renderer/hooks/useShapes/pie/index.js +6 -23
- package/build/plugins/d3/renderer/hooks/useShapes/scatter/index.d.ts +0 -1
- package/build/plugins/d3/renderer/hooks/useShapes/scatter/index.js +4 -24
- package/build/plugins/d3/renderer/hooks/useShapes/treemap/index.d.ts +0 -1
- package/build/plugins/d3/renderer/hooks/useShapes/treemap/index.js +6 -13
- package/build/plugins/d3/renderer/utils/get-closest-data.d.ts +15 -0
- package/build/plugins/d3/renderer/utils/get-closest-data.js +156 -0
- package/build/plugins/d3/renderer/utils/index.d.ts +0 -5
- package/build/plugins/d3/renderer/utils/index.js +0 -7
- package/build/plugins/highcharts/renderer/components/HighchartsComponent.d.ts +0 -5
- package/build/plugins/highcharts/renderer/helpers/config/config.d.ts +0 -5
- package/build/plugins/highcharts/renderer/helpers/config/config.js +0 -12
- package/build/plugins/highcharts/renderer/helpers/config/options.js +1 -1
- package/build/plugins/highcharts/renderer/helpers/graph.d.ts +0 -5
- package/build/types/widget-data/tooltip.d.ts +3 -1
- package/package.json +1 -1
- package/build/plugins/d3/renderer/components/Tooltip/TooltipTriggerArea.d.ts +0 -12
- package/build/plugins/d3/renderer/components/Tooltip/TooltipTriggerArea.js +0 -153
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { pointer } from 'd3';
|
|
3
|
+
import throttle from 'lodash/throttle';
|
|
2
4
|
import { block } from '../../../../utils/cn';
|
|
3
5
|
import { getD3Dispatcher } from '../d3-dispatcher';
|
|
4
|
-
import { useAxisScales, useChartDimensions, useChartOptions, useSeries, useShapes
|
|
6
|
+
import { useAxisScales, useChartDimensions, useChartOptions, useSeries, useShapes } from '../hooks';
|
|
5
7
|
import { getWidthOccupiedByYAxis } from '../hooks/useChartDimensions/utils';
|
|
6
8
|
import { getPreparedXAxis } from '../hooks/useChartOptions/x-axis';
|
|
7
9
|
import { getPreparedYAxis } from '../hooks/useChartOptions/y-axis';
|
|
10
|
+
import { getClosestPoints } from '../utils/get-closest-data';
|
|
8
11
|
import { AxisX } from './AxisX';
|
|
9
12
|
import { AxisY } from './AxisY';
|
|
10
13
|
import { Legend } from './Legend';
|
|
11
14
|
import { Title } from './Title';
|
|
12
|
-
import { Tooltip
|
|
15
|
+
import { Tooltip } from './Tooltip';
|
|
13
16
|
import './styles.css';
|
|
14
17
|
const b = block('d3');
|
|
18
|
+
const THROTTLE_DELAY = 50;
|
|
15
19
|
export const Chart = (props) => {
|
|
16
20
|
var _a, _b;
|
|
17
21
|
const { width, height, data } = props;
|
|
@@ -48,7 +52,6 @@ export const Chart = (props) => {
|
|
|
48
52
|
xAxis,
|
|
49
53
|
yAxis,
|
|
50
54
|
});
|
|
51
|
-
const { hovered, pointerPosition } = useTooltip({ dispatcher, tooltip });
|
|
52
55
|
const { shapes, shapesData } = useShapes({
|
|
53
56
|
boundsWidth,
|
|
54
57
|
boundsHeight,
|
|
@@ -59,7 +62,6 @@ export const Chart = (props) => {
|
|
|
59
62
|
xScale,
|
|
60
63
|
yAxis,
|
|
61
64
|
yScale,
|
|
62
|
-
svgContainer: svgRef.current,
|
|
63
65
|
});
|
|
64
66
|
const clickHandler = (_b = (_a = data.chart) === null || _a === void 0 ? void 0 : _a.events) === null || _b === void 0 ? void 0 : _b.click;
|
|
65
67
|
React.useEffect(() => {
|
|
@@ -72,16 +74,34 @@ export const Chart = (props) => {
|
|
|
72
74
|
}, [dispatcher, clickHandler]);
|
|
73
75
|
const boundsOffsetTop = chart.margin.top;
|
|
74
76
|
const boundsOffsetLeft = chart.margin.left + getWidthOccupiedByYAxis({ preparedAxis: yAxis });
|
|
77
|
+
const handleMouseMove = (event) => {
|
|
78
|
+
const [pointerX, pointerY] = pointer(event, svgRef.current);
|
|
79
|
+
const x = pointerX - boundsOffsetLeft;
|
|
80
|
+
const y = pointerY - boundsOffsetTop;
|
|
81
|
+
if (x < 0 || x > boundsWidth || y < 0 || y > boundsHeight) {
|
|
82
|
+
dispatcher.call('hover-shape', {}, undefined);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const closest = getClosestPoints({
|
|
86
|
+
position: [x, y],
|
|
87
|
+
shapesData,
|
|
88
|
+
});
|
|
89
|
+
dispatcher.call('hover-shape', event.target, closest, [pointerX, pointerY]);
|
|
90
|
+
};
|
|
91
|
+
const throttledHandleMouseMove = throttle(handleMouseMove, THROTTLE_DELAY);
|
|
92
|
+
const handleMouseLeave = () => {
|
|
93
|
+
throttledHandleMouseMove.cancel();
|
|
94
|
+
dispatcher.call('hover-shape', {}, undefined);
|
|
95
|
+
};
|
|
75
96
|
return (React.createElement(React.Fragment, null,
|
|
76
|
-
React.createElement("svg", { ref: svgRef, className: b(), width: width, height: height },
|
|
97
|
+
React.createElement("svg", { ref: svgRef, className: b(), width: width, height: height, onMouseMove: throttledHandleMouseMove, onMouseLeave: handleMouseLeave },
|
|
77
98
|
title && React.createElement(Title, Object.assign({}, title, { chartWidth: width })),
|
|
78
99
|
React.createElement("g", { width: boundsWidth, height: boundsHeight, transform: `translate(${[boundsOffsetLeft, boundsOffsetTop].join(',')})` },
|
|
79
100
|
xScale && yScale && (React.createElement(React.Fragment, null,
|
|
80
101
|
React.createElement(AxisY, { axises: yAxis, width: boundsWidth, height: boundsHeight, scale: yScale }),
|
|
81
102
|
React.createElement("g", { transform: `translate(0, ${boundsHeight})` },
|
|
82
103
|
React.createElement(AxisX, { axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale })))),
|
|
83
|
-
shapes,
|
|
84
|
-
(tooltip === null || tooltip === void 0 ? void 0 : tooltip.enabled) && Boolean(shapesData.length) && (React.createElement(TooltipTriggerArea, { boundsWidth: boundsWidth, boundsHeight: boundsHeight, dispatcher: dispatcher, shapesData: shapesData, svgContainer: svgRef.current }))),
|
|
104
|
+
shapes),
|
|
85
105
|
preparedLegend.enabled && (React.createElement(Legend, { chartSeries: preparedSeries, boundsWidth: boundsWidth, legend: preparedLegend, items: legendItems, config: legendConfig, onItemClick: handleLegendItemClick }))),
|
|
86
|
-
React.createElement(Tooltip, { dispatcher: dispatcher, tooltip: tooltip, svgContainer: svgRef.current, xAxis: xAxis, yAxis: yAxis[0]
|
|
106
|
+
React.createElement(Tooltip, { dispatcher: dispatcher, tooltip: tooltip, svgContainer: svgRef.current, xAxis: xAxis, yAxis: yAxis[0] })));
|
|
87
107
|
};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { dateTime } from '@gravity-ui/date-utils';
|
|
3
3
|
import get from 'lodash/get';
|
|
4
|
+
import { block } from '../../../../../utils/cn';
|
|
4
5
|
import { formatNumber } from '../../../../shared';
|
|
5
6
|
import { getDataCategoryValue } from '../../utils';
|
|
7
|
+
const b = block('d3-tooltip');
|
|
6
8
|
const DEFAULT_DATE_FORMAT = 'DD.MM.YY';
|
|
7
9
|
const getRowData = (fieldName, axis, data) => {
|
|
8
10
|
switch (axis.type) {
|
|
@@ -26,47 +28,58 @@ const getRowData = (fieldName, axis, data) => {
|
|
|
26
28
|
};
|
|
27
29
|
const getXRowData = (xAxis, data) => getRowData('x', xAxis, data);
|
|
28
30
|
const getYRowData = (yAxis, data) => getRowData('y', yAxis, data);
|
|
31
|
+
const getMeasureValue = (data, xAxis, yAxis) => {
|
|
32
|
+
var _a, _b;
|
|
33
|
+
if (data.every((item) => item.series.type === 'pie' || item.series.type === 'treemap')) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
if (data.some((item) => item.series.type === 'bar-y')) {
|
|
37
|
+
return getYRowData(yAxis, (_a = data[0]) === null || _a === void 0 ? void 0 : _a.data);
|
|
38
|
+
}
|
|
39
|
+
return getXRowData(xAxis, (_b = data[0]) === null || _b === void 0 ? void 0 : _b.data);
|
|
40
|
+
};
|
|
29
41
|
export const DefaultContent = ({ hovered, xAxis, yAxis }) => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
React.createElement(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
42
|
+
const measureValue = getMeasureValue(hovered, xAxis, yAxis);
|
|
43
|
+
return (React.createElement(React.Fragment, null,
|
|
44
|
+
measureValue && React.createElement("div", null, measureValue),
|
|
45
|
+
hovered.map(({ data, series, closest }, i) => {
|
|
46
|
+
const id = `${get(series, 'id')}_${i}`;
|
|
47
|
+
const color = get(series, 'color');
|
|
48
|
+
switch (series.type) {
|
|
49
|
+
case 'scatter':
|
|
50
|
+
case 'line':
|
|
51
|
+
case 'area':
|
|
52
|
+
case 'bar-x': {
|
|
53
|
+
const value = (React.createElement(React.Fragment, null,
|
|
54
|
+
series.name,
|
|
55
|
+
": ",
|
|
56
|
+
getYRowData(yAxis, data)));
|
|
57
|
+
return (React.createElement("div", { key: id, className: b('content-row') },
|
|
58
|
+
React.createElement("div", { className: b('color'), style: { backgroundColor: color } }),
|
|
59
|
+
React.createElement("div", null, closest ? React.createElement("b", null, value) : React.createElement("span", null, value))));
|
|
60
|
+
}
|
|
61
|
+
case 'bar-y': {
|
|
62
|
+
const value = (React.createElement(React.Fragment, null,
|
|
63
|
+
series.name,
|
|
64
|
+
": ",
|
|
65
|
+
getXRowData(xAxis, data)));
|
|
66
|
+
return (React.createElement("div", { key: id, className: b('content-row') },
|
|
67
|
+
React.createElement("div", { className: b('color'), style: { backgroundColor: color } }),
|
|
68
|
+
React.createElement("div", null, closest ? React.createElement("b", null, value) : React.createElement("span", null, value))));
|
|
69
|
+
}
|
|
70
|
+
case 'pie':
|
|
71
|
+
case 'treemap': {
|
|
72
|
+
const seriesData = data;
|
|
73
|
+
return (React.createElement("div", { key: id, className: b('content-row') },
|
|
74
|
+
React.createElement("div", { className: b('color'), style: { backgroundColor: color } }),
|
|
53
75
|
React.createElement("span", null,
|
|
54
|
-
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return (React.createElement("div", { key: id },
|
|
62
|
-
React.createElement("span", null,
|
|
63
|
-
pieSeriesData.name || pieSeriesData.id,
|
|
64
|
-
"\u00A0"),
|
|
65
|
-
React.createElement("span", null, pieSeriesData.value)));
|
|
76
|
+
seriesData.name || seriesData.id,
|
|
77
|
+
"\u00A0"),
|
|
78
|
+
React.createElement("span", null, seriesData.value)));
|
|
79
|
+
}
|
|
80
|
+
default: {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
66
83
|
}
|
|
67
|
-
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
})));
|
|
84
|
+
})));
|
|
72
85
|
};
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { Dispatch } from 'd3';
|
|
3
|
-
import type {
|
|
4
|
-
import type { PointerPosition, PreparedAxis, PreparedTooltip } from '../../hooks';
|
|
5
|
-
export * from './TooltipTriggerArea';
|
|
3
|
+
import type { PreparedAxis, PreparedTooltip } from '../../hooks';
|
|
6
4
|
type TooltipProps = {
|
|
7
5
|
dispatcher: Dispatch<object>;
|
|
8
6
|
tooltip: PreparedTooltip;
|
|
9
7
|
svgContainer: SVGSVGElement | null;
|
|
10
8
|
xAxis: PreparedAxis;
|
|
11
9
|
yAxis: PreparedAxis;
|
|
12
|
-
hovered?: TooltipDataChunk[];
|
|
13
|
-
pointerPosition?: PointerPosition;
|
|
14
10
|
};
|
|
15
11
|
export declare const Tooltip: (props: TooltipProps) => React.JSX.Element | null;
|
|
12
|
+
export {};
|
|
@@ -2,11 +2,12 @@ import React from 'react';
|
|
|
2
2
|
import { Popup, useVirtualElementRef } from '@gravity-ui/uikit';
|
|
3
3
|
import isNil from 'lodash/isNil';
|
|
4
4
|
import { block } from '../../../../../utils/cn';
|
|
5
|
+
import { useTooltip } from '../../hooks';
|
|
5
6
|
import { DefaultContent } from './DefaultContent';
|
|
6
|
-
export * from './TooltipTriggerArea';
|
|
7
7
|
const b = block('d3-tooltip');
|
|
8
8
|
export const Tooltip = (props) => {
|
|
9
|
-
const { tooltip, xAxis, yAxis,
|
|
9
|
+
const { tooltip, xAxis, yAxis, svgContainer, dispatcher } = props;
|
|
10
|
+
const { hovered, pointerPosition } = useTooltip({ dispatcher, tooltip });
|
|
10
11
|
const containerRect = (svgContainer === null || svgContainer === void 0 ? void 0 : svgContainer.getBoundingClientRect()) || { left: 0, top: 0 };
|
|
11
12
|
const left = ((pointerPosition === null || pointerPosition === void 0 ? void 0 : pointerPosition[0]) || 0) + containerRect.left;
|
|
12
13
|
const top = ((pointerPosition === null || pointerPosition === void 0 ? void 0 : pointerPosition[1]) || 0) + containerRect.top;
|
|
@@ -22,6 +23,6 @@ export const Tooltip = (props) => {
|
|
|
22
23
|
React.useEffect(() => {
|
|
23
24
|
window.dispatchEvent(new CustomEvent('scroll'));
|
|
24
25
|
}, [left, top]);
|
|
25
|
-
return hovered ? (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 } }] },
|
|
26
|
+
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 } }] },
|
|
26
27
|
React.createElement("div", { className: b('content') }, content))) : null;
|
|
27
28
|
};
|
|
@@ -97,4 +97,18 @@
|
|
|
97
97
|
border-radius: 3px;
|
|
98
98
|
box-shadow: 0 2px 12px var(--g-color-sfx-shadow);
|
|
99
99
|
text-wrap: nowrap;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.chartkit-d3-tooltip__content-row {
|
|
103
|
+
display: flex;
|
|
104
|
+
align-items: center;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.chartkit-d3-tooltip__color {
|
|
108
|
+
height: 8px;
|
|
109
|
+
width: 16px;
|
|
110
|
+
display: inline-block;
|
|
111
|
+
margin-right: 8px;
|
|
112
|
+
border-radius: 2px;
|
|
113
|
+
background-color: #dddddd;
|
|
100
114
|
}
|
|
@@ -73,14 +73,13 @@ export const AreaSeriesShapes = (args) => {
|
|
|
73
73
|
const hoverEnabled = hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.enabled;
|
|
74
74
|
const inactiveEnabled = inactiveOptions === null || inactiveOptions === void 0 ? void 0 : inactiveOptions.enabled;
|
|
75
75
|
dispatcher.on('hover-shape.area', (data) => {
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
const selectedSeriesId = (_a = selected === null || selected === void 0 ? void 0 : selected.series) === null || _a === void 0 ? void 0 : _a.id;
|
|
76
|
+
const selected = (data === null || data === void 0 ? void 0 : data.filter((d) => d.series.type === 'area')) || [];
|
|
77
|
+
const selectedDataItems = selected.map((d) => d.data);
|
|
78
|
+
const selectedSeriesIds = selected.map((d) => { var _a; return (_a = d.series) === null || _a === void 0 ? void 0 : _a.id; });
|
|
80
79
|
shapeSelection.datum((d, index, list) => {
|
|
81
80
|
var _a;
|
|
82
81
|
const elementSelection = select(list[index]);
|
|
83
|
-
const hovered = Boolean(hoverEnabled && d.id
|
|
82
|
+
const hovered = Boolean(hoverEnabled && selectedSeriesIds.includes(d.id));
|
|
84
83
|
if (d.hovered !== hovered) {
|
|
85
84
|
d.hovered = hovered;
|
|
86
85
|
let strokeColor = d.color || '';
|
|
@@ -95,7 +94,9 @@ export const AreaSeriesShapes = (args) => {
|
|
|
95
94
|
return setActiveState({
|
|
96
95
|
element: list[index],
|
|
97
96
|
state: inactiveOptions,
|
|
98
|
-
active: Boolean(!inactiveEnabled ||
|
|
97
|
+
active: Boolean(!inactiveEnabled ||
|
|
98
|
+
!selectedSeriesIds.length ||
|
|
99
|
+
selectedSeriesIds.includes(d.id)),
|
|
99
100
|
datum: d,
|
|
100
101
|
});
|
|
101
102
|
});
|
|
@@ -103,13 +104,15 @@ export const AreaSeriesShapes = (args) => {
|
|
|
103
104
|
return setActiveState({
|
|
104
105
|
element: list[index],
|
|
105
106
|
state: inactiveOptions,
|
|
106
|
-
active: Boolean(!inactiveEnabled ||
|
|
107
|
+
active: Boolean(!inactiveEnabled ||
|
|
108
|
+
!selectedSeriesIds.length ||
|
|
109
|
+
selectedSeriesIds.includes(d.series.id)),
|
|
107
110
|
datum: d,
|
|
108
111
|
});
|
|
109
112
|
});
|
|
110
113
|
markerSelection.datum((d, index, list) => {
|
|
111
114
|
const elementSelection = select(list[index]);
|
|
112
|
-
const hovered = Boolean(hoverEnabled && d.point.data
|
|
115
|
+
const hovered = Boolean(hoverEnabled && selectedDataItems.includes(d.point.data));
|
|
113
116
|
if (d.hovered !== hovered) {
|
|
114
117
|
d.hovered = hovered;
|
|
115
118
|
elementSelection.attr('visibility', getMarkerVisibility(d));
|
|
@@ -118,8 +121,8 @@ export const AreaSeriesShapes = (args) => {
|
|
|
118
121
|
}
|
|
119
122
|
if (d.point.series.marker.states.normal.enabled) {
|
|
120
123
|
const isActive = Boolean(!inactiveEnabled ||
|
|
121
|
-
!
|
|
122
|
-
|
|
124
|
+
!selectedSeriesIds.length ||
|
|
125
|
+
selectedSeriesIds.includes(d.point.series.id));
|
|
123
126
|
setActiveState({
|
|
124
127
|
element: list[index],
|
|
125
128
|
state: inactiveOptions,
|
|
@@ -14,7 +14,7 @@ import { TreemapSeriesShape } from './treemap';
|
|
|
14
14
|
import { prepareTreemapData } from './treemap/prepare-data';
|
|
15
15
|
import './styles.css';
|
|
16
16
|
export const useShapes = (args) => {
|
|
17
|
-
const { boundsWidth, boundsHeight, dispatcher, series, seriesOptions, xAxis, xScale, yAxis, yScale,
|
|
17
|
+
const { boundsWidth, boundsHeight, dispatcher, series, seriesOptions, xAxis, xScale, yAxis, yScale, } = args;
|
|
18
18
|
const shapesComponents = React.useMemo(() => {
|
|
19
19
|
const visibleSeries = getOnlyVisibleSeries(series);
|
|
20
20
|
const groupedSeries = group(visibleSeries, (item) => item.type);
|
|
@@ -89,7 +89,8 @@ export const useShapes = (args) => {
|
|
|
89
89
|
yAxis: yAxis[0],
|
|
90
90
|
yScale,
|
|
91
91
|
});
|
|
92
|
-
acc.push(React.createElement(ScatterSeriesShape, { key: "scatter", dispatcher: dispatcher, preparedData: preparedData, seriesOptions: seriesOptions
|
|
92
|
+
acc.push(React.createElement(ScatterSeriesShape, { key: "scatter", dispatcher: dispatcher, preparedData: preparedData, seriesOptions: seriesOptions }));
|
|
93
|
+
shapesData.push(...preparedData);
|
|
93
94
|
}
|
|
94
95
|
break;
|
|
95
96
|
}
|
|
@@ -99,7 +100,8 @@ export const useShapes = (args) => {
|
|
|
99
100
|
boundsWidth,
|
|
100
101
|
boundsHeight,
|
|
101
102
|
});
|
|
102
|
-
acc.push(React.createElement(PieSeriesShapes, { key: "pie", dispatcher: dispatcher, preparedData: preparedData, seriesOptions: seriesOptions
|
|
103
|
+
acc.push(React.createElement(PieSeriesShapes, { key: "pie", dispatcher: dispatcher, preparedData: preparedData, seriesOptions: seriesOptions }));
|
|
104
|
+
shapesData.push(...preparedData);
|
|
103
105
|
break;
|
|
104
106
|
}
|
|
105
107
|
case 'treemap': {
|
|
@@ -110,7 +112,8 @@ export const useShapes = (args) => {
|
|
|
110
112
|
width: boundsWidth,
|
|
111
113
|
height: boundsHeight,
|
|
112
114
|
});
|
|
113
|
-
acc.push(React.createElement(TreemapSeriesShape, { key: "treemap", dispatcher: dispatcher, preparedData: preparedData, seriesOptions: seriesOptions
|
|
115
|
+
acc.push(React.createElement(TreemapSeriesShape, { key: "treemap", dispatcher: dispatcher, preparedData: preparedData, seriesOptions: seriesOptions }));
|
|
116
|
+
shapesData.push(preparedData);
|
|
114
117
|
}
|
|
115
118
|
}
|
|
116
119
|
return acc;
|
|
@@ -126,7 +129,6 @@ export const useShapes = (args) => {
|
|
|
126
129
|
xScale,
|
|
127
130
|
yAxis,
|
|
128
131
|
yScale,
|
|
129
|
-
svgContainer,
|
|
130
132
|
]);
|
|
131
133
|
return { shapes: shapesComponents.shapes, shapesData: shapesComponents.shapesData };
|
|
132
134
|
};
|
|
@@ -61,13 +61,12 @@ export const LineSeriesShapes = (args) => {
|
|
|
61
61
|
const hoverEnabled = hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.enabled;
|
|
62
62
|
const inactiveEnabled = inactiveOptions === null || inactiveOptions === void 0 ? void 0 : inactiveOptions.enabled;
|
|
63
63
|
dispatcher.on('hover-shape.line', (data) => {
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const selectedSeriesId = (_a = selected === null || selected === void 0 ? void 0 : selected.series) === null || _a === void 0 ? void 0 : _a.id;
|
|
64
|
+
const selected = (data === null || data === void 0 ? void 0 : data.filter((d) => d.series.type === 'line')) || [];
|
|
65
|
+
const selectedDataItems = selected.map((d) => d.data);
|
|
66
|
+
const selectedSeriesIds = selected.map((d) => { var _a; return (_a = d.series) === null || _a === void 0 ? void 0 : _a.id; });
|
|
68
67
|
lineSelection.datum((d, index, list) => {
|
|
69
68
|
const elementSelection = select(list[index]);
|
|
70
|
-
const hovered = Boolean(hoverEnabled && d.id
|
|
69
|
+
const hovered = Boolean(hoverEnabled && selectedSeriesIds.includes(d.id));
|
|
71
70
|
if (d.hovered !== hovered) {
|
|
72
71
|
d.hovered = hovered;
|
|
73
72
|
elementSelection.attr('stroke', (d) => {
|
|
@@ -82,7 +81,9 @@ export const LineSeriesShapes = (args) => {
|
|
|
82
81
|
return setActiveState({
|
|
83
82
|
element: list[index],
|
|
84
83
|
state: inactiveOptions,
|
|
85
|
-
active: Boolean(!inactiveEnabled ||
|
|
84
|
+
active: Boolean(!inactiveEnabled ||
|
|
85
|
+
!selectedSeriesIds.length ||
|
|
86
|
+
selectedSeriesIds.includes(d.id)),
|
|
86
87
|
datum: d,
|
|
87
88
|
});
|
|
88
89
|
});
|
|
@@ -90,13 +91,15 @@ export const LineSeriesShapes = (args) => {
|
|
|
90
91
|
return setActiveState({
|
|
91
92
|
element: list[index],
|
|
92
93
|
state: inactiveOptions,
|
|
93
|
-
active: Boolean(!inactiveEnabled ||
|
|
94
|
+
active: Boolean(!inactiveEnabled ||
|
|
95
|
+
!selectedSeriesIds.length ||
|
|
96
|
+
selectedSeriesIds.includes(d.series.id)),
|
|
94
97
|
datum: d,
|
|
95
98
|
});
|
|
96
99
|
});
|
|
97
100
|
markerSelection.datum((d, index, list) => {
|
|
98
101
|
const elementSelection = select(list[index]);
|
|
99
|
-
const hovered = Boolean(hoverEnabled && d.point.data
|
|
102
|
+
const hovered = Boolean(hoverEnabled && selectedDataItems.includes(d.point.data));
|
|
100
103
|
if (d.hovered !== hovered) {
|
|
101
104
|
d.hovered = hovered;
|
|
102
105
|
elementSelection.attr('visibility', getMarkerVisibility(d));
|
|
@@ -105,8 +108,8 @@ export const LineSeriesShapes = (args) => {
|
|
|
105
108
|
}
|
|
106
109
|
if (d.point.series.marker.states.normal.enabled) {
|
|
107
110
|
const isActive = Boolean(!inactiveEnabled ||
|
|
108
|
-
!
|
|
109
|
-
|
|
111
|
+
!selectedSeriesIds.length ||
|
|
112
|
+
selectedSeriesIds.includes(d.point.series.id));
|
|
110
113
|
setActiveState({
|
|
111
114
|
element: list[index],
|
|
112
115
|
state: inactiveOptions,
|
|
@@ -6,7 +6,6 @@ type PreparePieSeriesArgs = {
|
|
|
6
6
|
dispatcher: Dispatch<object>;
|
|
7
7
|
preparedData: PreparedPieData[];
|
|
8
8
|
seriesOptions: PreparedSeriesOptions;
|
|
9
|
-
svgContainer: SVGSVGElement | null;
|
|
10
9
|
};
|
|
11
10
|
export declare function getHaloVisibility(d: PieArcDatum<SegmentData>): "" | "hidden";
|
|
12
11
|
export declare function PieSeriesShapes(args: PreparePieSeriesArgs): React.JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { arc, color, line as lineGenerator,
|
|
2
|
+
import { arc, color, line as lineGenerator, select } from 'd3';
|
|
3
3
|
import get from 'lodash/get';
|
|
4
4
|
import { block } from '../../../../../../utils/cn';
|
|
5
5
|
import { setEllipsisForOverflowTexts } from '../../../utils';
|
|
@@ -11,7 +11,7 @@ export function getHaloVisibility(d) {
|
|
|
11
11
|
return enabled ? '' : 'hidden';
|
|
12
12
|
}
|
|
13
13
|
export function PieSeriesShapes(args) {
|
|
14
|
-
const { dispatcher, preparedData, seriesOptions
|
|
14
|
+
const { dispatcher, preparedData, seriesOptions } = args;
|
|
15
15
|
const ref = React.useRef(null);
|
|
16
16
|
React.useEffect(() => {
|
|
17
17
|
if (!ref.current) {
|
|
@@ -127,32 +127,15 @@ export function PieSeriesShapes(args) {
|
|
|
127
127
|
const eventName = `hover-shape.pie`;
|
|
128
128
|
const hoverOptions = get(seriesOptions, 'pie.states.hover');
|
|
129
129
|
const inactiveOptions = get(seriesOptions, 'pie.states.inactive');
|
|
130
|
-
svgElement
|
|
131
|
-
.on('mousemove', (e) => {
|
|
132
|
-
const currentSegment = getSelectedSegment(e.target);
|
|
133
|
-
if (currentSegment) {
|
|
134
|
-
const data = {
|
|
135
|
-
series: {
|
|
136
|
-
id: currentSegment.series.id,
|
|
137
|
-
type: 'pie',
|
|
138
|
-
name: currentSegment.series.name,
|
|
139
|
-
},
|
|
140
|
-
data: currentSegment.series.data,
|
|
141
|
-
};
|
|
142
|
-
dispatcher.call('hover-shape', {}, [data], pointer(e, svgContainer));
|
|
143
|
-
}
|
|
144
|
-
})
|
|
145
|
-
.on('mouseleave', () => {
|
|
146
|
-
dispatcher.call('hover-shape', {}, undefined);
|
|
147
|
-
})
|
|
148
|
-
.on('click', (e) => {
|
|
130
|
+
svgElement.on('click', (e) => {
|
|
149
131
|
const selectedSegment = getSelectedSegment(e.target);
|
|
150
132
|
if (selectedSegment) {
|
|
151
133
|
dispatcher.call('click-chart', undefined, { point: selectedSegment.series.data, series: selectedSegment.series }, e);
|
|
152
134
|
}
|
|
153
135
|
});
|
|
154
136
|
dispatcher.on(eventName, (data) => {
|
|
155
|
-
|
|
137
|
+
var _a, _b;
|
|
138
|
+
const selectedSeriesId = (_b = (_a = data === null || data === void 0 ? void 0 : data[0]) === null || _a === void 0 ? void 0 : _a.series) === null || _b === void 0 ? void 0 : _b.id;
|
|
156
139
|
const hoverEnabled = hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.enabled;
|
|
157
140
|
const inactiveEnabled = inactiveOptions === null || inactiveOptions === void 0 ? void 0 : inactiveOptions.enabled;
|
|
158
141
|
shapesSelection.datum((_d, index, list) => {
|
|
@@ -202,6 +185,6 @@ export function PieSeriesShapes(args) {
|
|
|
202
185
|
return () => {
|
|
203
186
|
dispatcher.on(eventName, null);
|
|
204
187
|
};
|
|
205
|
-
}, [dispatcher, preparedData, seriesOptions
|
|
188
|
+
}, [dispatcher, preparedData, seriesOptions]);
|
|
206
189
|
return React.createElement("g", { ref: ref, className: b(), style: { zIndex: 9 } });
|
|
207
190
|
}
|
|
@@ -7,6 +7,5 @@ type ScatterSeriesShapeProps = {
|
|
|
7
7
|
dispatcher: Dispatch<object>;
|
|
8
8
|
preparedData: PreparedScatterData[];
|
|
9
9
|
seriesOptions: PreparedSeriesOptions;
|
|
10
|
-
svgContainer: SVGSVGElement | null;
|
|
11
10
|
};
|
|
12
11
|
export declare function ScatterSeriesShape(props: ScatterSeriesShapeProps): React.JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { select } from 'd3';
|
|
3
3
|
import get from 'lodash/get';
|
|
4
4
|
import { block } from '../../../../../../utils/cn';
|
|
5
5
|
import { getMarkerHaloVisibility, renderMarker, selectMarkerHalo, selectMarkerSymbol, setMarker, } from '../marker';
|
|
@@ -7,7 +7,7 @@ import { setActiveState, shapeKey } from '../utils';
|
|
|
7
7
|
export { prepareScatterData } from './prepare-data';
|
|
8
8
|
const b = block('d3-scatter');
|
|
9
9
|
export function ScatterSeriesShape(props) {
|
|
10
|
-
const { dispatcher, preparedData, seriesOptions
|
|
10
|
+
const { dispatcher, preparedData, seriesOptions } = props;
|
|
11
11
|
const ref = React.useRef(null);
|
|
12
12
|
React.useEffect(() => {
|
|
13
13
|
if (!ref.current) {
|
|
@@ -28,27 +28,7 @@ export function ScatterSeriesShape(props) {
|
|
|
28
28
|
const getSelectedPoint = (element) => {
|
|
29
29
|
return select(element).datum();
|
|
30
30
|
};
|
|
31
|
-
svgElement
|
|
32
|
-
.on('mousemove', (e) => {
|
|
33
|
-
const datum = getSelectedPoint(e.target);
|
|
34
|
-
if (!datum) {
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
const [pointerX, pointerY] = pointer(e, svgContainer);
|
|
38
|
-
const data = {
|
|
39
|
-
series: {
|
|
40
|
-
id: datum.point.series.id,
|
|
41
|
-
type: 'scatter',
|
|
42
|
-
name: datum.point.series.name,
|
|
43
|
-
},
|
|
44
|
-
data: datum.point.data,
|
|
45
|
-
};
|
|
46
|
-
dispatcher.call('hover-shape', {}, [data], [pointerX, pointerY]);
|
|
47
|
-
})
|
|
48
|
-
.on('mouseleave', () => {
|
|
49
|
-
dispatcher.call('hover-shape', {}, undefined);
|
|
50
|
-
})
|
|
51
|
-
.on('click', (e) => {
|
|
31
|
+
svgElement.on('click', (e) => {
|
|
52
32
|
const datum = getSelectedPoint(e.target);
|
|
53
33
|
if (datum) {
|
|
54
34
|
dispatcher.call('click-chart', undefined, { point: datum.point.data, series: datum.point.series }, e);
|
|
@@ -90,6 +70,6 @@ export function ScatterSeriesShape(props) {
|
|
|
90
70
|
return () => {
|
|
91
71
|
dispatcher.on('hover-shape.scatter', null);
|
|
92
72
|
};
|
|
93
|
-
}, [dispatcher, preparedData, seriesOptions
|
|
73
|
+
}, [dispatcher, preparedData, seriesOptions]);
|
|
94
74
|
return React.createElement("g", { ref: ref, className: b() });
|
|
95
75
|
}
|
|
@@ -6,7 +6,6 @@ type ShapeProps = {
|
|
|
6
6
|
dispatcher: Dispatch<object>;
|
|
7
7
|
preparedData: PreparedTreemapData;
|
|
8
8
|
seriesOptions: PreparedSeriesOptions;
|
|
9
|
-
svgContainer: SVGSVGElement | null;
|
|
10
9
|
};
|
|
11
10
|
export declare const TreemapSeriesShape: (props: ShapeProps) => React.JSX.Element;
|
|
12
11
|
export {};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { color,
|
|
2
|
+
import { color, select } from 'd3';
|
|
3
3
|
import get from 'lodash/get';
|
|
4
4
|
import { block } from '../../../../../../utils/cn';
|
|
5
5
|
import { setEllipsisForOverflowTexts } from '../../../utils';
|
|
6
6
|
const b = block('d3-treemap');
|
|
7
7
|
export const TreemapSeriesShape = (props) => {
|
|
8
|
-
const { dispatcher, preparedData, seriesOptions
|
|
8
|
+
const { dispatcher, preparedData, seriesOptions } = props;
|
|
9
9
|
const ref = React.useRef(null);
|
|
10
10
|
React.useEffect(() => {
|
|
11
11
|
if (!ref.current) {
|
|
@@ -52,22 +52,15 @@ export const TreemapSeriesShape = (props) => {
|
|
|
52
52
|
const eventName = `hover-shape.treemap`;
|
|
53
53
|
const hoverOptions = get(seriesOptions, 'treemap.states.hover');
|
|
54
54
|
const inactiveOptions = get(seriesOptions, 'treemap.states.inactive');
|
|
55
|
-
svgElement
|
|
56
|
-
.on('mousemove', (e) => {
|
|
57
|
-
const datum = getSelectedPart(e.target);
|
|
58
|
-
dispatcher.call('hover-shape', {}, [{ data: datum.data, series }], pointer(e, svgContainer));
|
|
59
|
-
})
|
|
60
|
-
.on('mouseleave', () => {
|
|
61
|
-
dispatcher.call('hover-shape', {}, undefined);
|
|
62
|
-
})
|
|
63
|
-
.on('click', (e) => {
|
|
55
|
+
svgElement.on('click', (e) => {
|
|
64
56
|
const datum = getSelectedPart(e.target);
|
|
65
57
|
dispatcher.call('click-chart', undefined, { point: datum.data, series }, e);
|
|
66
58
|
});
|
|
67
59
|
dispatcher.on(eventName, (data) => {
|
|
60
|
+
var _a;
|
|
68
61
|
const hoverEnabled = hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.enabled;
|
|
69
62
|
const inactiveEnabled = inactiveOptions === null || inactiveOptions === void 0 ? void 0 : inactiveOptions.enabled;
|
|
70
|
-
const hoveredData = data === null || data === void 0 ? void 0 : data[0].data;
|
|
63
|
+
const hoveredData = (_a = data === null || data === void 0 ? void 0 : data[0]) === null || _a === void 0 ? void 0 : _a.data;
|
|
71
64
|
rectSelection.datum((d, index, list) => {
|
|
72
65
|
const currentRect = select(list[index]);
|
|
73
66
|
const hovered = Boolean(hoverEnabled && hoveredData === d.data);
|
|
@@ -106,6 +99,6 @@ export const TreemapSeriesShape = (props) => {
|
|
|
106
99
|
return () => {
|
|
107
100
|
dispatcher.on(eventName, null);
|
|
108
101
|
};
|
|
109
|
-
}, [dispatcher, preparedData, seriesOptions
|
|
102
|
+
}, [dispatcher, preparedData, seriesOptions]);
|
|
110
103
|
return React.createElement("g", { ref: ref, className: b() });
|
|
111
104
|
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ChartKitWidgetSeries, ChartKitWidgetSeriesData, TooltipDataChunk } from '../../../../types';
|
|
2
|
+
import type { ShapeData } from '../hooks';
|
|
3
|
+
type GetClosestPointsArgs = {
|
|
4
|
+
position: [number, number];
|
|
5
|
+
shapesData: ShapeData[];
|
|
6
|
+
};
|
|
7
|
+
export type ShapePoint = {
|
|
8
|
+
x: number;
|
|
9
|
+
y0: number;
|
|
10
|
+
y1: number;
|
|
11
|
+
data: ChartKitWidgetSeriesData;
|
|
12
|
+
series: ChartKitWidgetSeries;
|
|
13
|
+
};
|
|
14
|
+
export declare function getClosestPoints(args: GetClosestPointsArgs): TooltipDataChunk[];
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { Delaunay, bisector, sort } from 'd3';
|
|
2
|
+
import get from 'lodash/get';
|
|
3
|
+
import groupBy from 'lodash/groupBy';
|
|
4
|
+
function getClosestPointsByXValue(x, y, points) {
|
|
5
|
+
var _a, _b;
|
|
6
|
+
const sorted = sort(points, (p) => p.x);
|
|
7
|
+
const closestXIndex = bisector((p) => p.x).center(sorted, x);
|
|
8
|
+
if (closestXIndex === -1) {
|
|
9
|
+
return [];
|
|
10
|
+
}
|
|
11
|
+
const closestX = sorted[closestXIndex].x;
|
|
12
|
+
const closestPoints = sort(points.filter((p) => p.x === closestX), (p) => p.y0);
|
|
13
|
+
let closestYIndex = -1;
|
|
14
|
+
if (y < ((_a = closestPoints[0]) === null || _a === void 0 ? void 0 : _a.y0)) {
|
|
15
|
+
closestYIndex = 0;
|
|
16
|
+
}
|
|
17
|
+
else if (y > ((_b = closestPoints[closestPoints.length - 1]) === null || _b === void 0 ? void 0 : _b.y1)) {
|
|
18
|
+
closestYIndex = closestPoints.length - 1;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
closestYIndex = closestPoints.findIndex((p) => y > p.y0 && y < p.y1);
|
|
22
|
+
}
|
|
23
|
+
return closestPoints.map((p, i) => ({
|
|
24
|
+
data: p.data,
|
|
25
|
+
series: p.series,
|
|
26
|
+
closest: i === closestYIndex,
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
function getSeriesType(shapeData) {
|
|
30
|
+
return get(shapeData, 'series.type') || get(shapeData, 'point.series.type');
|
|
31
|
+
}
|
|
32
|
+
export function getClosestPoints(args) {
|
|
33
|
+
const { position, shapesData } = args;
|
|
34
|
+
const [pointerX, pointerY] = position;
|
|
35
|
+
const result = [];
|
|
36
|
+
const groups = groupBy(shapesData, getSeriesType);
|
|
37
|
+
Object.entries(groups).forEach(([seriesType, list]) => {
|
|
38
|
+
var _a, _b;
|
|
39
|
+
switch (seriesType) {
|
|
40
|
+
case 'bar-x': {
|
|
41
|
+
const points = list.map((d) => ({
|
|
42
|
+
data: d.data,
|
|
43
|
+
series: d.series,
|
|
44
|
+
x: d.x + d.width / 2,
|
|
45
|
+
y0: d.y,
|
|
46
|
+
y1: d.y + d.height,
|
|
47
|
+
}));
|
|
48
|
+
Array.prototype.push.apply(result, getClosestPointsByXValue(pointerX, pointerY, points));
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
case 'area': {
|
|
52
|
+
const points = list.reduce((acc, d) => {
|
|
53
|
+
Array.prototype.push.apply(acc, d.points.map((p) => ({
|
|
54
|
+
data: p.data,
|
|
55
|
+
series: p.series,
|
|
56
|
+
x: p.x,
|
|
57
|
+
y0: p.y0,
|
|
58
|
+
y1: p.y,
|
|
59
|
+
})));
|
|
60
|
+
return acc;
|
|
61
|
+
}, []);
|
|
62
|
+
Array.prototype.push.apply(result, getClosestPointsByXValue(pointerX, pointerY, points));
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
case 'line': {
|
|
66
|
+
const points = list.reduce((acc, d) => {
|
|
67
|
+
Array.prototype.push.apply(acc, d.points.map((p) => ({
|
|
68
|
+
data: p.data,
|
|
69
|
+
series: p.series,
|
|
70
|
+
x: p.x,
|
|
71
|
+
y0: p.y,
|
|
72
|
+
y1: p.y,
|
|
73
|
+
})));
|
|
74
|
+
return acc;
|
|
75
|
+
}, []);
|
|
76
|
+
Array.prototype.push.apply(result, getClosestPointsByXValue(pointerX, pointerY, points));
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
case 'bar-y': {
|
|
80
|
+
const points = list;
|
|
81
|
+
const sorted = sort(points, (p) => p.y);
|
|
82
|
+
const closestYIndex = bisector((p) => p.y).center(sorted, pointerY);
|
|
83
|
+
let closestPoints = [];
|
|
84
|
+
let closestXIndex = -1;
|
|
85
|
+
if (closestYIndex !== -1) {
|
|
86
|
+
const closestY = sorted[closestYIndex].y;
|
|
87
|
+
closestPoints = sort(points.filter((p) => p.y === closestY), (p) => p.x);
|
|
88
|
+
const lastPoint = closestPoints[closestPoints.length - 1];
|
|
89
|
+
if (pointerX < ((_a = closestPoints[0]) === null || _a === void 0 ? void 0 : _a.x)) {
|
|
90
|
+
closestXIndex = 0;
|
|
91
|
+
}
|
|
92
|
+
else if (lastPoint && pointerX > lastPoint.x + lastPoint.width) {
|
|
93
|
+
closestXIndex = closestPoints.length - 1;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
closestXIndex = closestPoints.findIndex((p) => pointerX > p.x && pointerX < p.x + p.width);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
Array.prototype.push.apply(result, closestPoints.map((p, i) => ({
|
|
100
|
+
data: p.data,
|
|
101
|
+
series: p.series,
|
|
102
|
+
closest: i === closestXIndex,
|
|
103
|
+
})));
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
case 'scatter': {
|
|
107
|
+
const points = list;
|
|
108
|
+
const delaunayX = Delaunay.from(points, (d) => d.point.x, (d) => d.point.y);
|
|
109
|
+
const closestPoint = points[delaunayX.find(pointerX, pointerY)];
|
|
110
|
+
if (closestPoint) {
|
|
111
|
+
result.push({
|
|
112
|
+
data: closestPoint.point.data,
|
|
113
|
+
series: closestPoint.point.series,
|
|
114
|
+
closest: true,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
case 'pie': {
|
|
120
|
+
const points = list.map((d) => d.segments).flat();
|
|
121
|
+
const closestPoint = points.find((p) => {
|
|
122
|
+
const { center, radius } = p.data.pie;
|
|
123
|
+
const x = pointerX - center[0];
|
|
124
|
+
const y = pointerY - center[1];
|
|
125
|
+
let angle = Math.atan2(y, x) + 0.5 * Math.PI;
|
|
126
|
+
angle = angle < 0 ? Math.PI * 2 + angle : angle;
|
|
127
|
+
const polarRadius = Math.sqrt(x * x + y * y);
|
|
128
|
+
return angle >= p.startAngle && angle <= p.endAngle && polarRadius < radius;
|
|
129
|
+
});
|
|
130
|
+
if (closestPoint) {
|
|
131
|
+
result.push({
|
|
132
|
+
data: closestPoint.data.series.data,
|
|
133
|
+
series: closestPoint.data.series,
|
|
134
|
+
closest: true,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
case 'treemap': {
|
|
140
|
+
const data = list;
|
|
141
|
+
const closestPoint = (_b = data[0]) === null || _b === void 0 ? void 0 : _b.leaves.find((l) => {
|
|
142
|
+
return (pointerX >= l.x0 && pointerX <= l.x1 && pointerY >= l.y0 && pointerY <= l.y1);
|
|
143
|
+
});
|
|
144
|
+
if (closestPoint) {
|
|
145
|
+
result.push({
|
|
146
|
+
data: closestPoint.data,
|
|
147
|
+
series: data[0].series,
|
|
148
|
+
closest: true,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
@@ -8,9 +8,6 @@ export * from './axis';
|
|
|
8
8
|
export * from './labels';
|
|
9
9
|
export * from './symbol';
|
|
10
10
|
export type AxisDirection = 'x' | 'y';
|
|
11
|
-
export type NodeWithD3Data<T = unknown> = Element & {
|
|
12
|
-
__data__: T;
|
|
13
|
-
};
|
|
14
11
|
type UnknownSeries = {
|
|
15
12
|
type: ChartKitWidgetSeries['type'];
|
|
16
13
|
data: unknown;
|
|
@@ -73,5 +70,3 @@ export declare const getDataCategoryValue: (args: {
|
|
|
73
70
|
data: ChartKitWidgetSeriesData;
|
|
74
71
|
}) => string;
|
|
75
72
|
export declare function getClosestPointsRange(axis: PreparedAxis, points: AxisDomain[]): number | undefined;
|
|
76
|
-
export declare const isNodeContainsD3Data: (node?: Element | null) => node is NodeWithD3Data<unknown>;
|
|
77
|
-
export declare const extractD3DataFromNode: <T extends unknown>(node: NodeWithD3Data<T>) => T;
|
|
@@ -170,10 +170,3 @@ export function getClosestPointsRange(axis, points) {
|
|
|
170
170
|
}
|
|
171
171
|
return points[1] - points[0];
|
|
172
172
|
}
|
|
173
|
-
// https://d3js.org/d3-selection/joining#selection_data
|
|
174
|
-
export const isNodeContainsD3Data = (node) => {
|
|
175
|
-
return Boolean(node && '__data__' in node);
|
|
176
|
-
};
|
|
177
|
-
export const extractD3DataFromNode = (node) => {
|
|
178
|
-
return node.__data__;
|
|
179
|
-
};
|
|
@@ -363,11 +363,6 @@ export declare class HighchartsComponent extends React.PureComponent<Props, Stat
|
|
|
363
363
|
mouseOut: () => void;
|
|
364
364
|
click: (event: any) => void;
|
|
365
365
|
};
|
|
366
|
-
point: {
|
|
367
|
-
events: {
|
|
368
|
-
click: (event: any) => boolean;
|
|
369
|
-
};
|
|
370
|
-
};
|
|
371
366
|
marker: {
|
|
372
367
|
states: {
|
|
373
368
|
hover: {
|
|
@@ -526,11 +526,6 @@ export function prepareConfig(data: any, options: any, isMobile: any, holidays:
|
|
|
526
526
|
mouseOut: () => void;
|
|
527
527
|
click: (event: any) => void;
|
|
528
528
|
};
|
|
529
|
-
point: {
|
|
530
|
-
events: {
|
|
531
|
-
click: (event: any) => boolean;
|
|
532
|
-
};
|
|
533
|
-
};
|
|
534
529
|
marker: {
|
|
535
530
|
states: {
|
|
536
531
|
hover: {
|
|
@@ -1264,18 +1264,6 @@ export function prepareConfig(data, options, isMobile, holidays) {
|
|
|
1264
1264
|
}
|
|
1265
1265
|
},
|
|
1266
1266
|
},
|
|
1267
|
-
point: {
|
|
1268
|
-
events: {
|
|
1269
|
-
click: function (event) {
|
|
1270
|
-
if (event.shiftKey) {
|
|
1271
|
-
this.series.chart.tooltip.hide();
|
|
1272
|
-
this.series.data[this.index].remove();
|
|
1273
|
-
return true;
|
|
1274
|
-
}
|
|
1275
|
-
return false;
|
|
1276
|
-
},
|
|
1277
|
-
},
|
|
1278
|
-
},
|
|
1279
1267
|
marker: options.splitTooltip
|
|
1280
1268
|
? {
|
|
1281
1269
|
states: {
|
|
@@ -357,11 +357,6 @@ declare function getGraph({ options, data, comments, isMobile, holidays }: GetGr
|
|
|
357
357
|
mouseOut: () => void;
|
|
358
358
|
click: (event: any) => void;
|
|
359
359
|
};
|
|
360
|
-
point: {
|
|
361
|
-
events: {
|
|
362
|
-
click: (event: any) => boolean;
|
|
363
|
-
};
|
|
364
|
-
};
|
|
365
360
|
marker: {
|
|
366
361
|
states: {
|
|
367
362
|
hover: {
|
|
@@ -50,7 +50,9 @@ export type TooltipDataChunkTreemap<T = any> = {
|
|
|
50
50
|
data: TreemapSeriesData<T>;
|
|
51
51
|
series: TreemapSeries<T>;
|
|
52
52
|
};
|
|
53
|
-
export type TooltipDataChunk<T = any> = TooltipDataChunkBarX<T> | TooltipDataChunkBarY<T> | TooltipDataChunkPie<T> | TooltipDataChunkScatter<T> | TooltipDataChunkLine<T> | TooltipDataChunkArea<T> | TooltipDataChunkTreemap<T
|
|
53
|
+
export type TooltipDataChunk<T = any> = (TooltipDataChunkBarX<T> | TooltipDataChunkBarY<T> | TooltipDataChunkPie<T> | TooltipDataChunkScatter<T> | TooltipDataChunkLine<T> | TooltipDataChunkArea<T> | TooltipDataChunkTreemap<T>) & {
|
|
54
|
+
closest?: boolean;
|
|
55
|
+
};
|
|
54
56
|
export type ChartKitWidgetTooltip<T = any> = {
|
|
55
57
|
enabled?: boolean;
|
|
56
58
|
/** Specifies the renderer for the tooltip. If returned null default tooltip renderer will be used. */
|
package/package.json
CHANGED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import type { Dispatch } from 'd3';
|
|
3
|
-
import type { ShapeData } from '../../hooks';
|
|
4
|
-
type Args = {
|
|
5
|
-
boundsWidth: number;
|
|
6
|
-
boundsHeight: number;
|
|
7
|
-
dispatcher: Dispatch<object>;
|
|
8
|
-
shapesData: ShapeData[];
|
|
9
|
-
svgContainer: SVGSVGElement | null;
|
|
10
|
-
};
|
|
11
|
-
export declare const TooltipTriggerArea: (args: Args) => React.JSX.Element;
|
|
12
|
-
export {};
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { bisector, group, pointer, sort } from 'd3';
|
|
3
|
-
import get from 'lodash/get';
|
|
4
|
-
import throttle from 'lodash/throttle';
|
|
5
|
-
import { extractD3DataFromNode, isNodeContainsD3Data } from '../../utils';
|
|
6
|
-
const THROTTLE_DELAY = 50;
|
|
7
|
-
const isNodeContainsData = (node) => {
|
|
8
|
-
return isNodeContainsD3Data(node);
|
|
9
|
-
};
|
|
10
|
-
function getBarXShapeData(args) {
|
|
11
|
-
var _a;
|
|
12
|
-
const { shapesData, point: [pointerX, pointerY], top, left, xData, container, } = args;
|
|
13
|
-
const barWidthOffset = shapesData[0].width / 2;
|
|
14
|
-
const xPosition = pointerX - left - barWidthOffset;
|
|
15
|
-
const xDataIndex = bisector((d) => d.x).center(xData, xPosition);
|
|
16
|
-
const xNodes = Array.from((container === null || container === void 0 ? void 0 : container.querySelectorAll(`[x="${(_a = xData[xDataIndex]) === null || _a === void 0 ? void 0 : _a.x}"]`)) || []);
|
|
17
|
-
if (xNodes.length === 1 && isNodeContainsData(xNodes[0])) {
|
|
18
|
-
return [extractD3DataFromNode(xNodes[0])];
|
|
19
|
-
}
|
|
20
|
-
if (xNodes.length > 1 && xNodes.every(isNodeContainsData)) {
|
|
21
|
-
const yPosition = pointerY - top;
|
|
22
|
-
const xyNode = xNodes.find((node, i) => {
|
|
23
|
-
const { y, height } = extractD3DataFromNode(node);
|
|
24
|
-
if (i === xNodes.length - 1) {
|
|
25
|
-
return yPosition <= y + height;
|
|
26
|
-
}
|
|
27
|
-
return yPosition >= y && yPosition <= y + height;
|
|
28
|
-
});
|
|
29
|
-
if (xyNode) {
|
|
30
|
-
return [extractD3DataFromNode(xyNode)];
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return [];
|
|
34
|
-
}
|
|
35
|
-
function getLineShapesData(args) {
|
|
36
|
-
const { xData, point: [pointerX], } = args;
|
|
37
|
-
const xDataIndex = bisector((d) => d.x).center(xData, pointerX);
|
|
38
|
-
const selectedLineShape = xData[xDataIndex];
|
|
39
|
-
if (selectedLineShape) {
|
|
40
|
-
return [
|
|
41
|
-
{
|
|
42
|
-
series: selectedLineShape.series,
|
|
43
|
-
data: selectedLineShape.data,
|
|
44
|
-
},
|
|
45
|
-
];
|
|
46
|
-
}
|
|
47
|
-
return [];
|
|
48
|
-
}
|
|
49
|
-
function getBarYData(args) {
|
|
50
|
-
var _a;
|
|
51
|
-
const { data, point: [pointerX, pointerY], } = args;
|
|
52
|
-
const yDataIndex = bisector((d) => d.y).center(data, pointerY);
|
|
53
|
-
const shapesByY = ((_a = data[yDataIndex]) === null || _a === void 0 ? void 0 : _a.items) || [];
|
|
54
|
-
const xDataIndex = bisector((d) => d.x).left(shapesByY, pointerX);
|
|
55
|
-
const result = shapesByY[Math.min(xDataIndex, shapesByY.length - 1)];
|
|
56
|
-
return result
|
|
57
|
-
? [
|
|
58
|
-
{
|
|
59
|
-
series: result.series,
|
|
60
|
-
data: result.data,
|
|
61
|
-
},
|
|
62
|
-
]
|
|
63
|
-
: [];
|
|
64
|
-
}
|
|
65
|
-
export const TooltipTriggerArea = (args) => {
|
|
66
|
-
const { boundsWidth, boundsHeight, dispatcher, shapesData, svgContainer } = args;
|
|
67
|
-
const rectRef = React.useRef(null);
|
|
68
|
-
const xBarData = React.useMemo(() => {
|
|
69
|
-
const result = shapesData
|
|
70
|
-
.filter((sd) => get(sd, 'series.type') === 'bar-x')
|
|
71
|
-
.map((sd) => ({ x: sd.x, data: sd }));
|
|
72
|
-
return sort(result, (item) => item.x);
|
|
73
|
-
}, [shapesData]);
|
|
74
|
-
const xLineData = React.useMemo(() => {
|
|
75
|
-
const result = shapesData
|
|
76
|
-
.filter((sd) => ['line', 'area'].includes(sd.series.type))
|
|
77
|
-
.reduce((acc, sd) => {
|
|
78
|
-
return acc.concat(sd.points.map((d) => ({
|
|
79
|
-
x: d.x,
|
|
80
|
-
data: d.data,
|
|
81
|
-
series: d.series,
|
|
82
|
-
})));
|
|
83
|
-
}, []);
|
|
84
|
-
return sort(result, (item) => item.x);
|
|
85
|
-
}, [shapesData]);
|
|
86
|
-
const barYData = React.useMemo(() => {
|
|
87
|
-
const barYShapeData = shapesData.filter((sd) => get(sd, 'series.type') === 'bar-y');
|
|
88
|
-
const result = Array.from(group(barYShapeData, (sd) => sd.y)).map(([y, shapes]) => {
|
|
89
|
-
const yValue = y + shapes[0].height / 2;
|
|
90
|
-
return {
|
|
91
|
-
y: yValue,
|
|
92
|
-
items: sort(shapes.map((shape) => {
|
|
93
|
-
const preparedData = shape;
|
|
94
|
-
return {
|
|
95
|
-
x: preparedData.x + preparedData.width,
|
|
96
|
-
data: preparedData.data,
|
|
97
|
-
series: preparedData.series,
|
|
98
|
-
};
|
|
99
|
-
}), (item) => item.x),
|
|
100
|
-
};
|
|
101
|
-
});
|
|
102
|
-
return sort(result, (item) => item.y);
|
|
103
|
-
}, [shapesData]);
|
|
104
|
-
const getShapeData = (point) => {
|
|
105
|
-
var _a, _b;
|
|
106
|
-
const { left: ownLeft, top: ownTop } = ((_a = rectRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect()) || {
|
|
107
|
-
left: 0,
|
|
108
|
-
top: 0,
|
|
109
|
-
};
|
|
110
|
-
const { left: containerLeft, top: containerTop } = (svgContainer === null || svgContainer === void 0 ? void 0 : svgContainer.getBoundingClientRect()) || {
|
|
111
|
-
left: 0,
|
|
112
|
-
top: 0,
|
|
113
|
-
};
|
|
114
|
-
const [pointerX, pointerY] = point; //pointer(e, svgContainer);
|
|
115
|
-
const result = [];
|
|
116
|
-
result === null || result === void 0 ? void 0 : result.push(...getBarXShapeData({
|
|
117
|
-
shapesData,
|
|
118
|
-
point: [pointerX, pointerY],
|
|
119
|
-
left: ownLeft - containerLeft,
|
|
120
|
-
top: ownTop - containerTop,
|
|
121
|
-
xData: xBarData,
|
|
122
|
-
container: (_b = rectRef.current) === null || _b === void 0 ? void 0 : _b.parentElement,
|
|
123
|
-
}), ...getLineShapesData({
|
|
124
|
-
xData: xLineData,
|
|
125
|
-
point: [pointerX - (ownLeft - containerLeft), pointerY - (ownTop - containerTop)],
|
|
126
|
-
}), ...getBarYData({
|
|
127
|
-
data: barYData,
|
|
128
|
-
point: [pointerX - (ownLeft - containerLeft), pointerY - (ownTop - containerTop)],
|
|
129
|
-
}));
|
|
130
|
-
return result;
|
|
131
|
-
};
|
|
132
|
-
const handleMouseMove = (e) => {
|
|
133
|
-
const [pointerX, pointerY] = pointer(e, svgContainer);
|
|
134
|
-
const hoverShapeData = getShapeData([pointerX, pointerY]);
|
|
135
|
-
if (hoverShapeData.length) {
|
|
136
|
-
const position = [pointerX, pointerY];
|
|
137
|
-
dispatcher.call('hover-shape', e.target, hoverShapeData, position);
|
|
138
|
-
}
|
|
139
|
-
};
|
|
140
|
-
const throttledHandleMouseMove = throttle(handleMouseMove, THROTTLE_DELAY);
|
|
141
|
-
const handleMouseLeave = () => {
|
|
142
|
-
throttledHandleMouseMove.cancel();
|
|
143
|
-
dispatcher.call('hover-shape', {}, undefined);
|
|
144
|
-
};
|
|
145
|
-
const handleClick = (e) => {
|
|
146
|
-
const [pointerX, pointerY] = pointer(e, svgContainer);
|
|
147
|
-
const shapeData = getShapeData([pointerX, pointerY]);
|
|
148
|
-
if (shapeData.length) {
|
|
149
|
-
dispatcher.call('click-chart', undefined, { point: get(shapeData, '[0].data'), series: get(shapeData, '[0].series') }, e);
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
return (React.createElement("rect", { ref: rectRef, width: boundsWidth, height: boundsHeight, fill: "transparent", onMouseMove: throttledHandleMouseMove, onMouseLeave: handleMouseLeave, onClick: handleClick }));
|
|
153
|
-
};
|