@gravity-ui/chartkit 5.3.2 → 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.
Files changed (28) hide show
  1. package/build/plugins/d3/renderer/components/Chart.js +28 -8
  2. package/build/plugins/d3/renderer/components/Tooltip/DefaultContent.js +53 -40
  3. package/build/plugins/d3/renderer/components/Tooltip/index.d.ts +2 -5
  4. package/build/plugins/d3/renderer/components/Tooltip/index.js +4 -3
  5. package/build/plugins/d3/renderer/components/styles.css +14 -0
  6. package/build/plugins/d3/renderer/hooks/useShapes/area/index.js +13 -10
  7. package/build/plugins/d3/renderer/hooks/useShapes/index.d.ts +0 -1
  8. package/build/plugins/d3/renderer/hooks/useShapes/index.js +7 -5
  9. package/build/plugins/d3/renderer/hooks/useShapes/line/index.js +13 -10
  10. package/build/plugins/d3/renderer/hooks/useShapes/pie/index.d.ts +0 -1
  11. package/build/plugins/d3/renderer/hooks/useShapes/pie/index.js +6 -23
  12. package/build/plugins/d3/renderer/hooks/useShapes/scatter/index.d.ts +0 -1
  13. package/build/plugins/d3/renderer/hooks/useShapes/scatter/index.js +4 -24
  14. package/build/plugins/d3/renderer/hooks/useShapes/treemap/index.d.ts +0 -1
  15. package/build/plugins/d3/renderer/hooks/useShapes/treemap/index.js +6 -13
  16. package/build/plugins/d3/renderer/utils/get-closest-data.d.ts +15 -0
  17. package/build/plugins/d3/renderer/utils/get-closest-data.js +156 -0
  18. package/build/plugins/d3/renderer/utils/index.d.ts +0 -5
  19. package/build/plugins/d3/renderer/utils/index.js +0 -7
  20. package/build/plugins/highcharts/renderer/components/HighchartsComponent.d.ts +0 -5
  21. package/build/plugins/highcharts/renderer/helpers/config/config.d.ts +0 -5
  22. package/build/plugins/highcharts/renderer/helpers/config/config.js +1 -15
  23. package/build/plugins/highcharts/renderer/helpers/config/options.js +1 -1
  24. package/build/plugins/highcharts/renderer/helpers/graph.d.ts +0 -5
  25. package/build/types/widget-data/tooltip.d.ts +3 -1
  26. package/package.json +1 -1
  27. package/build/plugins/d3/renderer/components/Tooltip/TooltipTriggerArea.d.ts +0 -12
  28. 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, useTooltip, } from '../hooks';
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, TooltipTriggerArea } from './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], hovered: hovered, pointerPosition: pointerPosition })));
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
- return (React.createElement(React.Fragment, null, hovered.map(({ data, series }, i) => {
31
- const id = get(series, 'id', i);
32
- switch (series.type) {
33
- case 'scatter':
34
- case 'line':
35
- case 'area':
36
- case 'bar-x': {
37
- const xRow = getXRowData(xAxis, data);
38
- const yRow = getYRowData(yAxis, data);
39
- return (React.createElement("div", { key: id },
40
- React.createElement("div", null, xRow),
41
- React.createElement("div", null,
42
- React.createElement("span", null,
43
- React.createElement("b", null, series.name),
44
- ": ",
45
- yRow))));
46
- }
47
- case 'bar-y': {
48
- const xRow = getXRowData(xAxis, data);
49
- const yRow = getYRowData(yAxis, data);
50
- return (React.createElement("div", { key: id },
51
- React.createElement("div", null, yRow),
52
- React.createElement("div", null,
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
- React.createElement("b", null, series.name),
55
- ": ",
56
- xRow))));
57
- }
58
- case 'pie':
59
- case 'treemap': {
60
- const pieSeriesData = data;
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
- default: {
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 { TooltipDataChunk } from '../../../../../types/widget-data';
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, hovered, svgContainer, pointerPosition } = props;
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
- var _a;
77
- const selected = data === null || data === void 0 ? void 0 : data.find((d) => d.series.type === 'area');
78
- const selectedDataItem = selected === null || selected === void 0 ? void 0 : selected.data;
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 === selectedSeriesId);
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 || !selectedSeriesId || selectedSeriesId === d.id),
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 || !selectedSeriesId || selectedSeriesId === d.series.id),
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 === selectedDataItem);
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
- !selectedSeriesId ||
122
- selectedSeriesId === d.point.series.id);
124
+ !selectedSeriesIds.length ||
125
+ selectedSeriesIds.includes(d.point.series.id));
123
126
  setActiveState({
124
127
  element: list[index],
125
128
  state: inactiveOptions,
@@ -21,7 +21,6 @@ type Args = {
21
21
  seriesOptions: PreparedSeriesOptions;
22
22
  xAxis: PreparedAxis;
23
23
  yAxis: PreparedAxis[];
24
- svgContainer: SVGSVGElement | null;
25
24
  xScale?: ChartScale;
26
25
  yScale?: ChartScale;
27
26
  };
@@ -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, svgContainer, } = args;
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, svgContainer: svgContainer }));
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, svgContainer: svgContainer }));
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, svgContainer: svgContainer }));
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
- var _a;
65
- const selected = data === null || data === void 0 ? void 0 : data.find((d) => d.series.type === 'line');
66
- const selectedDataItem = selected === null || selected === void 0 ? void 0 : selected.data;
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 === selectedSeriesId);
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 || !selectedSeriesId || selectedSeriesId === d.id),
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 || !selectedSeriesId || selectedSeriesId === d.series.id),
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 === selectedDataItem);
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
- !selectedSeriesId ||
109
- selectedSeriesId === d.point.series.id);
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, pointer, select } from 'd3';
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, svgContainer } = args;
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
- const selectedSeriesId = data === null || data === void 0 ? void 0 : data[0].series.id;
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, svgContainer]);
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 { pointer, select } from 'd3';
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, svgContainer } = props;
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, svgContainer]);
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, pointer, select } from 'd3';
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, svgContainer } = props;
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, svgContainer]);
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: {
@@ -2,7 +2,6 @@
2
2
  import { dateTime } from '@gravity-ui/date-utils';
3
3
  import Highcharts from 'highcharts';
4
4
  import clamp from 'lodash/clamp';
5
- import debounce from 'lodash/debounce';
6
5
  import get from 'lodash/get';
7
6
  import isEmpty from 'lodash/isEmpty';
8
7
  import isNumber from 'lodash/isNumber';
@@ -1152,7 +1151,6 @@ export function prepareConfig(data, options, isMobile, holidays) {
1152
1151
  const chartType = get(options, 'highcharts.chart.type') || 'line';
1153
1152
  const { entryId } = options;
1154
1153
  const testClassName = `data-qa-chartkit-tooltip-entry-${entryId}`;
1155
- const debouncedAdjustDonutFontSize = debounce(adjustDonutFontSize, 100);
1156
1154
  const params = merge(getParamsByCustomType(options.type, options), defaultOptions, Object.assign({ _config: options, chart: {
1157
1155
  events: Object.assign({ load: function () {
1158
1156
  var _a;
@@ -1231,7 +1229,7 @@ export function prepareConfig(data, options, isMobile, holidays) {
1231
1229
  const chartSeries = chart.series[0];
1232
1230
  const innerWidth = chartSeries === null || chartSeries === void 0 ? void 0 : chartSeries.center[3];
1233
1231
  if (innerWidth) {
1234
- debouncedAdjustDonutFontSize(chart, chartSeries, innerWidth, totals);
1232
+ adjustDonutFontSize(chart, chartSeries, innerWidth, totals);
1235
1233
  }
1236
1234
  },
1237
1235
  })),
@@ -1266,18 +1264,6 @@ export function prepareConfig(data, options, isMobile, holidays) {
1266
1264
  }
1267
1265
  },
1268
1266
  },
1269
- point: {
1270
- events: {
1271
- click: function (event) {
1272
- if (event.shiftKey) {
1273
- this.series.chart.tooltip.hide();
1274
- this.series.data[this.index].remove();
1275
- return true;
1276
- }
1277
- return false;
1278
- },
1279
- },
1280
- },
1281
1267
  marker: options.splitTooltip
1282
1268
  ? {
1283
1269
  states: {
@@ -57,7 +57,7 @@ const first = {
57
57
  };
58
58
  const second = {
59
59
  allowPointSelect: true,
60
- slicedOffset: 20,
60
+ slicedOffset: 0,
61
61
  cursor: 'pointer',
62
62
  showInLegend: true,
63
63
  };
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/chartkit",
3
- "version": "5.3.2",
3
+ "version": "5.4.0",
4
4
  "description": "React component used to render charts based on any sources you need",
5
5
  "license": "MIT",
6
6
  "repository": "git@github.com:gravity-ui/ChartKit.git",
@@ -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
- };