@mui/x-charts 7.0.0-alpha.3 → 7.0.0-alpha.4

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 (52) hide show
  1. package/CHANGELOG.md +123 -0
  2. package/ChartsVoronoiHandler/ChartsVoronoiHandler.d.ts +14 -0
  3. package/ChartsVoronoiHandler/ChartsVoronoiHandler.js +174 -0
  4. package/ChartsVoronoiHandler/index.d.ts +1 -0
  5. package/ChartsVoronoiHandler/index.js +16 -0
  6. package/ChartsVoronoiHandler/package.json +6 -0
  7. package/ScatterChart/Scatter.js +14 -5
  8. package/ScatterChart/ScatterChart.d.ts +7 -1
  9. package/ScatterChart/ScatterChart.js +18 -1
  10. package/SparkLineChart/SparkLineChart.js +1 -0
  11. package/context/InteractionProvider.d.ts +10 -0
  12. package/context/InteractionProvider.js +21 -1
  13. package/esm/ChartsVoronoiHandler/ChartsVoronoiHandler.js +168 -0
  14. package/esm/ChartsVoronoiHandler/index.js +1 -0
  15. package/esm/ScatterChart/Scatter.js +14 -5
  16. package/esm/ScatterChart/ScatterChart.js +18 -1
  17. package/esm/SparkLineChart/SparkLineChart.js +1 -0
  18. package/esm/context/InteractionProvider.js +21 -1
  19. package/esm/hooks/useAxisEvents.js +20 -27
  20. package/esm/hooks/useInteractionItemProps.js +4 -1
  21. package/esm/index.js +1 -0
  22. package/esm/internals/utils.js +11 -0
  23. package/hooks/useAxisEvents.js +20 -27
  24. package/hooks/useInteractionItemProps.d.ts +2 -2
  25. package/hooks/useInteractionItemProps.js +4 -1
  26. package/index.d.ts +1 -0
  27. package/index.js +12 -1
  28. package/internals/defaultizeColor.d.ts +1 -0
  29. package/internals/utils.d.ts +6 -0
  30. package/internals/utils.js +12 -0
  31. package/legacy/ChartsVoronoiHandler/ChartsVoronoiHandler.js +162 -0
  32. package/legacy/ChartsVoronoiHandler/index.js +1 -0
  33. package/legacy/ScatterChart/Scatter.js +17 -6
  34. package/legacy/ScatterChart/ScatterChart.js +18 -1
  35. package/legacy/SparkLineChart/SparkLineChart.js +1 -0
  36. package/legacy/context/InteractionProvider.js +21 -1
  37. package/legacy/hooks/useAxisEvents.js +20 -27
  38. package/legacy/hooks/useInteractionItemProps.js +6 -1
  39. package/legacy/index.js +2 -1
  40. package/legacy/internals/utils.js +11 -0
  41. package/models/seriesType/scatter.d.ts +5 -0
  42. package/modern/ChartsVoronoiHandler/ChartsVoronoiHandler.js +166 -0
  43. package/modern/ChartsVoronoiHandler/index.js +1 -0
  44. package/modern/ScatterChart/Scatter.js +14 -5
  45. package/modern/ScatterChart/ScatterChart.js +18 -1
  46. package/modern/SparkLineChart/SparkLineChart.js +1 -0
  47. package/modern/context/InteractionProvider.js +21 -1
  48. package/modern/hooks/useAxisEvents.js +20 -27
  49. package/modern/hooks/useInteractionItemProps.js +4 -1
  50. package/modern/index.js +2 -1
  51. package/modern/internals/utils.js +11 -0
  52. package/package.json +6 -4
@@ -0,0 +1,162 @@
1
+ import * as React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Delaunay } from 'd3-delaunay';
4
+ import useEnhancedEffect from '@mui/utils/useEnhancedEffect';
5
+ import { InteractionContext } from '../context/InteractionProvider';
6
+ import { CartesianContext } from '../context/CartesianContextProvider';
7
+ import { SVGContext, DrawingContext } from '../context/DrawingProvider';
8
+ import { SeriesContext } from '../context/SeriesContextProvider';
9
+ import { getValueToPositionMapper } from '../hooks/useScale';
10
+ import { getSVGPoint } from '../internals/utils';
11
+ import { jsx as _jsx } from "react/jsx-runtime";
12
+ function ChartsVoronoiHandler(props) {
13
+ var _React$useContext$sca;
14
+ var voronoiMaxRadius = props.voronoiMaxRadius;
15
+ var svgRef = React.useContext(SVGContext);
16
+ var _React$useContext = React.useContext(DrawingContext),
17
+ width = _React$useContext.width,
18
+ height = _React$useContext.height,
19
+ top = _React$useContext.top,
20
+ left = _React$useContext.left;
21
+ var _React$useContext2 = React.useContext(CartesianContext),
22
+ xAxis = _React$useContext2.xAxis,
23
+ yAxis = _React$useContext2.yAxis,
24
+ xAxisIds = _React$useContext2.xAxisIds,
25
+ yAxisIds = _React$useContext2.yAxisIds;
26
+ var _React$useContext3 = React.useContext(InteractionContext),
27
+ dispatch = _React$useContext3.dispatch;
28
+ var _ref = (_React$useContext$sca = React.useContext(SeriesContext).scatter) != null ? _React$useContext$sca : {},
29
+ series = _ref.series,
30
+ seriesOrder = _ref.seriesOrder;
31
+ var voronoiRef = React.useRef({});
32
+ var defaultXAxisId = xAxisIds[0];
33
+ var defaultYAxisId = yAxisIds[0];
34
+ useEnhancedEffect(function () {
35
+ dispatch({
36
+ type: 'updateVoronoiUsage',
37
+ useVoronoiInteraction: true
38
+ });
39
+ return function () {
40
+ dispatch({
41
+ type: 'updateVoronoiUsage',
42
+ useVoronoiInteraction: false
43
+ });
44
+ };
45
+ }, [dispatch]);
46
+ useEnhancedEffect(function () {
47
+ if (seriesOrder === undefined || series === undefined) {
48
+ // If there is no scatter chart series
49
+ return;
50
+ }
51
+ voronoiRef.current = {};
52
+ var points = [];
53
+ seriesOrder.forEach(function (seriesId) {
54
+ var _series$seriesId = series[seriesId],
55
+ data = _series$seriesId.data,
56
+ xAxisKey = _series$seriesId.xAxisKey,
57
+ yAxisKey = _series$seriesId.yAxisKey;
58
+ var xScale = xAxis[xAxisKey != null ? xAxisKey : defaultXAxisId].scale;
59
+ var yScale = yAxis[yAxisKey != null ? yAxisKey : defaultYAxisId].scale;
60
+ var getXPosition = getValueToPositionMapper(xScale);
61
+ var getYPosition = getValueToPositionMapper(yScale);
62
+ var seriesPoints = data.flatMap(function (_ref2) {
63
+ var x = _ref2.x,
64
+ y = _ref2.y;
65
+ return [getXPosition(x), getYPosition(y)];
66
+ });
67
+ voronoiRef.current[seriesId] = {
68
+ startIndex: points.length,
69
+ endIndex: points.length + seriesPoints.length
70
+ };
71
+ points = points.concat(seriesPoints);
72
+ });
73
+ voronoiRef.current.delauney = new Delaunay(points);
74
+ }, [defaultXAxisId, defaultYAxisId, series, seriesOrder, xAxis, yAxis]);
75
+ React.useEffect(function () {
76
+ var element = svgRef.current;
77
+ if (element === null) {
78
+ return undefined;
79
+ }
80
+ var handleMouseOut = function handleMouseOut() {
81
+ dispatch({
82
+ type: 'exitChart'
83
+ });
84
+ };
85
+
86
+ // TODO: A perf optimisation of voronoi could be to use the last point as the intial point for the next search.
87
+ var handleMouseMove = function handleMouseMove(event) {
88
+ var _voronoiRef$current$d;
89
+ // Get mouse coordinate in global SVG space
90
+ var svgPoint = getSVGPoint(svgRef.current, event);
91
+ var outsideX = svgPoint.x < left || svgPoint.x > left + width;
92
+ var outsideY = svgPoint.y < top || svgPoint.y > top + height;
93
+ if (outsideX || outsideY) {
94
+ dispatch({
95
+ type: 'exitChart'
96
+ });
97
+ return;
98
+ }
99
+ if (!voronoiRef.current.delauney) {
100
+ return;
101
+ }
102
+ var closestPointIndex = (_voronoiRef$current$d = voronoiRef.current.delauney) == null ? void 0 : _voronoiRef$current$d.find(svgPoint.x, svgPoint.y);
103
+ if (closestPointIndex !== undefined) {
104
+ var seriesId = Object.keys(voronoiRef.current).find(function (id) {
105
+ if (id === 'delauney') {
106
+ return false;
107
+ }
108
+ return 2 * closestPointIndex >= voronoiRef.current[id].startIndex && 2 * closestPointIndex < voronoiRef.current[id].endIndex;
109
+ });
110
+ if (seriesId === undefined) {
111
+ return;
112
+ }
113
+ var dataIndex = (2 * closestPointIndex - voronoiRef.current[seriesId].startIndex) / 2;
114
+ if (voronoiMaxRadius !== undefined) {
115
+ var pointX = voronoiRef.current.delauney.points[2 * closestPointIndex];
116
+ var pointY = voronoiRef.current.delauney.points[2 * closestPointIndex + 1];
117
+ var dist2 = Math.pow(pointX - svgPoint.x, 2) + Math.pow(pointY - svgPoint.y, 2);
118
+ if (dist2 > Math.pow(voronoiMaxRadius, 2)) {
119
+ // The closest point is too far to be considered.
120
+ dispatch({
121
+ type: 'leaveItem',
122
+ data: {
123
+ type: 'scatter',
124
+ seriesId: seriesId,
125
+ dataIndex: dataIndex
126
+ }
127
+ });
128
+ return;
129
+ }
130
+ }
131
+ dispatch({
132
+ type: 'enterItem',
133
+ data: {
134
+ type: 'scatter',
135
+ seriesId: seriesId,
136
+ dataIndex: dataIndex
137
+ }
138
+ });
139
+ }
140
+ };
141
+ element.addEventListener('mouseout', handleMouseOut);
142
+ element.addEventListener('mousemove', handleMouseMove);
143
+ return function () {
144
+ element.removeEventListener('mouseout', handleMouseOut);
145
+ element.removeEventListener('mousemove', handleMouseMove);
146
+ };
147
+ }, [svgRef, dispatch, left, width, top, height, yAxis, xAxis, voronoiMaxRadius]);
148
+ return /*#__PURE__*/_jsx("g", {}); // Workaround to fix docs scripts
149
+ }
150
+ process.env.NODE_ENV !== "production" ? ChartsVoronoiHandler.propTypes = {
151
+ // ----------------------------- Warning --------------------------------
152
+ // | These PropTypes are generated from the TypeScript type definitions |
153
+ // | To update them edit the TypeScript types and run "yarn proptypes" |
154
+ // ----------------------------------------------------------------------
155
+ /**
156
+ * Defines the maximal distance between a scatter point and the pointer that triggers the interaction.
157
+ * If `undefined`, the radius is assumed to be infinite.
158
+ * @default undefined
159
+ */
160
+ voronoiMaxRadius: PropTypes.number
161
+ } : void 0;
162
+ export { ChartsVoronoiHandler };
@@ -0,0 +1 @@
1
+ export * from './ChartsVoronoiHandler';
@@ -1,5 +1,5 @@
1
- import _extends from "@babel/runtime/helpers/esm/extends";
2
1
  import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
2
+ import _extends from "@babel/runtime/helpers/esm/extends";
3
3
  import * as React from 'react';
4
4
  import PropTypes from 'prop-types';
5
5
  import { getValueToPositionMapper } from '../hooks/useScale';
@@ -22,9 +22,17 @@ function Scatter(props) {
22
22
  yScale = props.yScale,
23
23
  color = props.color,
24
24
  markerSize = props.markerSize;
25
+ var highlightScope = React.useMemo(function () {
26
+ return _extends({
27
+ highlighted: 'item',
28
+ faded: 'global'
29
+ }, series.highlightScope);
30
+ }, [series.highlightScope]);
25
31
  var _React$useContext = React.useContext(InteractionContext),
26
- item = _React$useContext.item;
27
- var getInteractionItemProps = useInteractionItemProps(series.highlightScope);
32
+ item = _React$useContext.item,
33
+ useVoronoiInteraction = _React$useContext.useVoronoiInteraction;
34
+ var skipInteractionHandlers = useVoronoiInteraction || series.disableHover;
35
+ var getInteractionItemProps = useInteractionItemProps(highlightScope, skipInteractionHandlers);
28
36
  var cleanData = React.useMemo(function () {
29
37
  var getXPosition = getValueToPositionMapper(xScale);
30
38
  var getYPosition = getValueToPositionMapper(yScale);
@@ -46,23 +54,25 @@ function Scatter(props) {
46
54
  dataIndex: i
47
55
  };
48
56
  if (isInRange) {
57
+ var isHighlighted = getIsHighlighted(item, pointCtx, highlightScope);
49
58
  temp.push({
50
59
  x: x,
51
60
  y: y,
52
- isFaded: !getIsHighlighted(item, pointCtx, series.highlightScope) && getIsFaded(item, pointCtx, series.highlightScope),
61
+ isHighlighted: isHighlighted,
62
+ isFaded: !isHighlighted && getIsFaded(item, pointCtx, highlightScope),
53
63
  interactionProps: getInteractionItemProps(pointCtx),
54
64
  id: scatterPoint.id
55
65
  });
56
66
  }
57
67
  }
58
68
  return temp;
59
- }, [yScale, xScale, getInteractionItemProps, item, series.data, series.highlightScope, series.id]);
69
+ }, [xScale, yScale, series.data, series.id, item, highlightScope, getInteractionItemProps]);
60
70
  return /*#__PURE__*/_jsx("g", {
61
71
  children: cleanData.map(function (dataPoint) {
62
72
  return /*#__PURE__*/_jsx("circle", _extends({
63
73
  cx: 0,
64
74
  cy: 0,
65
- r: markerSize,
75
+ r: (dataPoint.isHighlighted ? 1.2 : 1) * markerSize,
66
76
  transform: "translate(".concat(dataPoint.x, ", ").concat(dataPoint.y, ")"),
67
77
  fill: color,
68
78
  opacity: dataPoint.isFaded && 0.3 || 1
@@ -84,6 +94,7 @@ process.env.NODE_ENV !== "production" ? Scatter.propTypes = {
84
94
  x: PropTypes.number.isRequired,
85
95
  y: PropTypes.number.isRequired
86
96
  })).isRequired,
97
+ disableHover: PropTypes.bool,
87
98
  highlightScope: PropTypes.shape({
88
99
  faded: PropTypes.oneOf(['global', 'none', 'series']),
89
100
  highlighted: PropTypes.oneOf(['item', 'none', 'series'])
@@ -7,6 +7,7 @@ import { ChartsAxis } from '../ChartsAxis';
7
7
  import { ChartsTooltip } from '../ChartsTooltip';
8
8
  import { ChartsLegend } from '../ChartsLegend';
9
9
  import { ChartsAxisHighlight } from '../ChartsAxisHighlight';
10
+ import { ChartsVoronoiHandler } from '../ChartsVoronoiHandler/ChartsVoronoiHandler';
10
11
  import { jsx as _jsx } from "react/jsx-runtime";
11
12
  import { jsxs as _jsxs } from "react/jsx-runtime";
12
13
  /**
@@ -25,6 +26,8 @@ var ScatterChart = /*#__PURE__*/React.forwardRef(function ScatterChart(props, re
25
26
  series = props.series,
26
27
  tooltip = props.tooltip,
27
28
  axisHighlight = props.axisHighlight,
29
+ voronoiMaxRadius = props.voronoiMaxRadius,
30
+ disableVoronoi = props.disableVoronoi,
28
31
  legend = props.legend,
29
32
  width = props.width,
30
33
  height = props.height,
@@ -52,7 +55,9 @@ var ScatterChart = /*#__PURE__*/React.forwardRef(function ScatterChart(props, re
52
55
  xAxis: xAxis,
53
56
  yAxis: yAxis,
54
57
  sx: sx,
55
- children: [/*#__PURE__*/_jsx(ChartsAxis, {
58
+ children: [!disableVoronoi && /*#__PURE__*/_jsx(ChartsVoronoiHandler, {
59
+ voronoiMaxRadius: voronoiMaxRadius
60
+ }), /*#__PURE__*/_jsx(ChartsAxis, {
56
61
  topAxis: topAxis,
57
62
  leftAxis: leftAxis,
58
63
  rightAxis: rightAxis,
@@ -126,6 +131,11 @@ process.env.NODE_ENV !== "production" ? ScatterChart.propTypes = {
126
131
  * @default false
127
132
  */
128
133
  disableAxisListener: PropTypes.bool,
134
+ /**
135
+ * If true, the interaction will not use the Voronoi cell and fall back to hover events.
136
+ * @default false
137
+ */
138
+ disableVoronoi: PropTypes.bool,
129
139
  /**
130
140
  * The height of the chart in px. If not defined, it takes the height of the parent element.
131
141
  * @default undefined
@@ -218,6 +228,7 @@ process.env.NODE_ENV !== "production" ? ScatterChart.propTypes = {
218
228
  x: PropTypes.number.isRequired,
219
229
  y: PropTypes.number.isRequired
220
230
  })).isRequired,
231
+ disableHover: PropTypes.bool,
221
232
  highlightScope: PropTypes.shape({
222
233
  faded: PropTypes.oneOf(['global', 'none', 'series']),
223
234
  highlighted: PropTypes.oneOf(['item', 'none', 'series'])
@@ -283,6 +294,12 @@ process.env.NODE_ENV !== "production" ? ScatterChart.propTypes = {
283
294
  x: PropTypes.number,
284
295
  y: PropTypes.number
285
296
  }),
297
+ /**
298
+ * Defines the maximal distance between a scatter point and the pointer that triggers the interaction.
299
+ * If `undefined`, the radius is assumed to be infinite.
300
+ * @default undefined
301
+ */
302
+ voronoiMaxRadius: PropTypes.number,
286
303
  /**
287
304
  * The width of the chart in px. If not defined, it takes the width of the parent element.
288
305
  * @default undefined
@@ -84,6 +84,7 @@ var SparkLineChart = /*#__PURE__*/React.forwardRef(function SparkLineChart(props
84
84
  sx: sx,
85
85
  disableAxisListener: (!showTooltip || (tooltip == null ? void 0 : tooltip.trigger) !== 'axis') && (axisHighlight == null ? void 0 : axisHighlight.x) === 'none' && (axisHighlight == null ? void 0 : axisHighlight.y) === 'none',
86
86
  children: [plotType === 'bar' && /*#__PURE__*/_jsx(BarPlot, {
87
+ skipAnimation: true,
87
88
  slots: slots,
88
89
  slotProps: slotProps,
89
90
  sx: {
@@ -8,6 +8,7 @@ export var InteractionContext = /*#__PURE__*/React.createContext({
8
8
  x: null,
9
9
  y: null
10
10
  },
11
+ useVoronoiInteraction: false,
11
12
  dispatch: function dispatch() {
12
13
  return null;
13
14
  }
@@ -18,6 +19,21 @@ var dataReducer = function dataReducer(prevState, action) {
18
19
  return _extends({}, prevState, {
19
20
  item: action.data
20
21
  });
22
+ case 'exitChart':
23
+ if (prevState.item === null && prevState.axis.x === null && prevState.axis.y === null) {
24
+ return prevState;
25
+ }
26
+ return _extends({}, prevState, {
27
+ axis: {
28
+ x: null,
29
+ y: null
30
+ },
31
+ item: null
32
+ });
33
+ case 'updateVoronoiUsage':
34
+ return _extends({}, prevState, {
35
+ useVoronoiInteraction: action.useVoronoiInteraction
36
+ });
21
37
  case 'leaveItem':
22
38
  if (prevState.item === null || Object.keys(action.data).some(function (key) {
23
39
  return action.data[key] !== prevState.item[key];
@@ -29,6 +45,9 @@ var dataReducer = function dataReducer(prevState, action) {
29
45
  item: null
30
46
  });
31
47
  case 'updateAxis':
48
+ if (action.data.x === prevState.axis.x && action.data.y === prevState.axis.y) {
49
+ return prevState;
50
+ }
32
51
  return _extends({}, prevState, {
33
52
  axis: action.data
34
53
  });
@@ -43,7 +62,8 @@ export function InteractionProvider(_ref) {
43
62
  axis: {
44
63
  x: null,
45
64
  y: null
46
- }
65
+ },
66
+ useVoronoiInteraction: false
47
67
  }),
48
68
  _React$useReducer2 = _slicedToArray(_React$useReducer, 2),
49
69
  data = _React$useReducer2[0],
@@ -4,6 +4,10 @@ import { InteractionContext } from '../context/InteractionProvider';
4
4
  import { CartesianContext } from '../context/CartesianContextProvider';
5
5
  import { SVGContext, DrawingContext } from '../context/DrawingProvider';
6
6
  import { isBandScale } from '../internals/isBandScale';
7
+ import { getSVGPoint } from '../internals/utils';
8
+ function getAsANumber(value) {
9
+ return value instanceof Date ? value.getTime() : value;
10
+ }
7
11
  export var useAxisEvents = function useAxisEvents(disableAxisListener) {
8
12
  var svgRef = React.useContext(SVGContext);
9
13
  var _React$useContext = React.useContext(DrawingContext),
@@ -44,17 +48,18 @@ export var useAxisEvents = function useAxisEvents(disableAxisListener) {
44
48
  value: value
45
49
  };
46
50
  }
47
- var closestIndex = axisData == null ? void 0 : axisData.findIndex(function (v, index) {
48
- if (v > value) {
49
- // @ts-ignore
50
- if (index === 0 || Math.abs(value - v) <= Math.abs(value - axisData[index - 1])) {
51
+ var valueAsNumber = getAsANumber(value);
52
+ var closestIndex = axisData == null ? void 0 : axisData.findIndex(function (pointValue, index) {
53
+ var v = getAsANumber(pointValue);
54
+ if (v > valueAsNumber) {
55
+ if (index === 0 || Math.abs(valueAsNumber - v) <= Math.abs(valueAsNumber - getAsANumber(axisData[index - 1]))) {
51
56
  return true;
52
57
  }
53
58
  }
54
- if (v <= value) {
59
+ if (v <= valueAsNumber) {
55
60
  if (index === axisData.length - 1 ||
56
61
  // @ts-ignore
57
- Math.abs(value - v) < Math.abs(value - axisData[index + 1])) {
62
+ Math.abs(value - v) < Math.abs(value - getAsANumber(axisData[index + 1]))) {
58
63
  return true;
59
64
  }
60
65
  }
@@ -80,37 +85,25 @@ export var useAxisEvents = function useAxisEvents(disableAxisListener) {
80
85
  y: -1
81
86
  };
82
87
  dispatch({
83
- type: 'updateAxis',
84
- data: {
85
- x: null,
86
- y: null
87
- }
88
+ type: 'exitChart'
88
89
  });
89
90
  };
90
91
  var handleMouseMove = function handleMouseMove(event) {
91
- // Get mouse coordinate in global SVG space
92
- var pt = svgRef.current.createSVGPoint();
93
- pt.x = event.clientX;
94
- pt.y = event.clientY;
95
- var svgPt = pt.matrixTransform(svgRef.current.getScreenCTM().inverse());
92
+ var svgPoint = getSVGPoint(svgRef.current, event);
96
93
  mousePosition.current = {
97
- x: svgPt.x,
98
- y: svgPt.y
94
+ x: svgPoint.x,
95
+ y: svgPoint.y
99
96
  };
100
- var outsideX = svgPt.x < left || svgPt.x > left + width;
101
- var outsideY = svgPt.y < top || svgPt.y > top + height;
97
+ var outsideX = svgPoint.x < left || svgPoint.x > left + width;
98
+ var outsideY = svgPoint.y < top || svgPoint.y > top + height;
102
99
  if (outsideX || outsideY) {
103
100
  dispatch({
104
- type: 'updateAxis',
105
- data: {
106
- x: null,
107
- y: null
108
- }
101
+ type: 'exitChart'
109
102
  });
110
103
  return;
111
104
  }
112
- var newStateX = getUpdate(xAxis[usedXAxis], svgPt.x);
113
- var newStateY = getUpdate(yAxis[usedYAxis], svgPt.y);
105
+ var newStateX = getUpdate(xAxis[usedXAxis], svgPoint.x);
106
+ var newStateY = getUpdate(yAxis[usedYAxis], svgPoint.y);
114
107
  dispatch({
115
108
  type: 'updateAxis',
116
109
  data: {
@@ -1,11 +1,16 @@
1
1
  import * as React from 'react';
2
2
  import { InteractionContext } from '../context/InteractionProvider';
3
3
  import { HighlighContext } from '../context/HighlightProvider';
4
- export var useInteractionItemProps = function useInteractionItemProps(scope) {
4
+ export var useInteractionItemProps = function useInteractionItemProps(scope, skip) {
5
5
  var _React$useContext = React.useContext(InteractionContext),
6
6
  dispatchInteraction = _React$useContext.dispatch;
7
7
  var _React$useContext2 = React.useContext(HighlighContext),
8
8
  dispatchHighlight = _React$useContext2.dispatch;
9
+ if (skip) {
10
+ return function () {
11
+ return {};
12
+ };
13
+ }
9
14
  var getInteractionItemProps = function getInteractionItemProps(data) {
10
15
  var onMouseEnter = function onMouseEnter() {
11
16
  dispatchInteraction({
package/legacy/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-charts v7.0.0-alpha.3
2
+ * @mui/x-charts v7.0.0-alpha.4
3
3
  *
4
4
  * @license MIT
5
5
  * This source code is licensed under the MIT license found in the
@@ -18,6 +18,7 @@ export * from './ChartsYAxis';
18
18
  export * from './ChartsTooltip';
19
19
  export * from './ChartsLegend';
20
20
  export * from './ChartsAxisHighlight';
21
+ export * from './ChartsVoronoiHandler';
21
22
  export * from './BarChart';
22
23
  export * from './LineChart';
23
24
  export * from './PieChart';
@@ -2,4 +2,15 @@
2
2
  export function getSymbol(shape) {
3
3
  var symbolNames = 'circle cross diamond square star triangle wye'.split(/ /);
4
4
  return symbolNames.indexOf(shape) || 0;
5
+ }
6
+ /**
7
+ * Transform mouse event position to corrdinates inside the SVG.
8
+ * @param svg The SVG element
9
+ * @param event The mouseEvent to transform
10
+ */
11
+ export function getSVGPoint(svg, event) {
12
+ var pt = svg.createSVGPoint();
13
+ pt.x = event.clientX;
14
+ pt.y = event.clientY;
15
+ return pt.matrixTransform(svg.getScreenCTM().inverse());
5
16
  }
@@ -10,6 +10,11 @@ export interface ScatterSeriesType extends CommonSeriesType<ScatterValueType>, C
10
10
  data: ScatterValueType[];
11
11
  markerSize?: number;
12
12
  label?: string;
13
+ /**
14
+ * If true, the interaction will not use element hover for this series.
15
+ * @default false
16
+ */
17
+ disableHover?: boolean;
13
18
  }
14
19
  /**
15
20
  * An object that allows to identify a single scatter item.
@@ -0,0 +1,166 @@
1
+ import * as React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Delaunay } from 'd3-delaunay';
4
+ import useEnhancedEffect from '@mui/utils/useEnhancedEffect';
5
+ import { InteractionContext } from '../context/InteractionProvider';
6
+ import { CartesianContext } from '../context/CartesianContextProvider';
7
+ import { SVGContext, DrawingContext } from '../context/DrawingProvider';
8
+ import { SeriesContext } from '../context/SeriesContextProvider';
9
+ import { getValueToPositionMapper } from '../hooks/useScale';
10
+ import { getSVGPoint } from '../internals/utils';
11
+ import { jsx as _jsx } from "react/jsx-runtime";
12
+ function ChartsVoronoiHandler(props) {
13
+ const {
14
+ voronoiMaxRadius
15
+ } = props;
16
+ const svgRef = React.useContext(SVGContext);
17
+ const {
18
+ width,
19
+ height,
20
+ top,
21
+ left
22
+ } = React.useContext(DrawingContext);
23
+ const {
24
+ xAxis,
25
+ yAxis,
26
+ xAxisIds,
27
+ yAxisIds
28
+ } = React.useContext(CartesianContext);
29
+ const {
30
+ dispatch
31
+ } = React.useContext(InteractionContext);
32
+ const {
33
+ series,
34
+ seriesOrder
35
+ } = React.useContext(SeriesContext).scatter ?? {};
36
+ const voronoiRef = React.useRef({});
37
+ const defaultXAxisId = xAxisIds[0];
38
+ const defaultYAxisId = yAxisIds[0];
39
+ useEnhancedEffect(() => {
40
+ dispatch({
41
+ type: 'updateVoronoiUsage',
42
+ useVoronoiInteraction: true
43
+ });
44
+ return () => {
45
+ dispatch({
46
+ type: 'updateVoronoiUsage',
47
+ useVoronoiInteraction: false
48
+ });
49
+ };
50
+ }, [dispatch]);
51
+ useEnhancedEffect(() => {
52
+ if (seriesOrder === undefined || series === undefined) {
53
+ // If there is no scatter chart series
54
+ return;
55
+ }
56
+ voronoiRef.current = {};
57
+ let points = [];
58
+ seriesOrder.forEach(seriesId => {
59
+ const {
60
+ data,
61
+ xAxisKey,
62
+ yAxisKey
63
+ } = series[seriesId];
64
+ const xScale = xAxis[xAxisKey ?? defaultXAxisId].scale;
65
+ const yScale = yAxis[yAxisKey ?? defaultYAxisId].scale;
66
+ const getXPosition = getValueToPositionMapper(xScale);
67
+ const getYPosition = getValueToPositionMapper(yScale);
68
+ const seriesPoints = data.flatMap(({
69
+ x,
70
+ y
71
+ }) => [getXPosition(x), getYPosition(y)]);
72
+ voronoiRef.current[seriesId] = {
73
+ startIndex: points.length,
74
+ endIndex: points.length + seriesPoints.length
75
+ };
76
+ points = points.concat(seriesPoints);
77
+ });
78
+ voronoiRef.current.delauney = new Delaunay(points);
79
+ }, [defaultXAxisId, defaultYAxisId, series, seriesOrder, xAxis, yAxis]);
80
+ React.useEffect(() => {
81
+ const element = svgRef.current;
82
+ if (element === null) {
83
+ return undefined;
84
+ }
85
+ const handleMouseOut = () => {
86
+ dispatch({
87
+ type: 'exitChart'
88
+ });
89
+ };
90
+
91
+ // TODO: A perf optimisation of voronoi could be to use the last point as the intial point for the next search.
92
+ const handleMouseMove = event => {
93
+ // Get mouse coordinate in global SVG space
94
+ const svgPoint = getSVGPoint(svgRef.current, event);
95
+ const outsideX = svgPoint.x < left || svgPoint.x > left + width;
96
+ const outsideY = svgPoint.y < top || svgPoint.y > top + height;
97
+ if (outsideX || outsideY) {
98
+ dispatch({
99
+ type: 'exitChart'
100
+ });
101
+ return;
102
+ }
103
+ if (!voronoiRef.current.delauney) {
104
+ return;
105
+ }
106
+ const closestPointIndex = voronoiRef.current.delauney?.find(svgPoint.x, svgPoint.y);
107
+ if (closestPointIndex !== undefined) {
108
+ const seriesId = Object.keys(voronoiRef.current).find(id => {
109
+ if (id === 'delauney') {
110
+ return false;
111
+ }
112
+ return 2 * closestPointIndex >= voronoiRef.current[id].startIndex && 2 * closestPointIndex < voronoiRef.current[id].endIndex;
113
+ });
114
+ if (seriesId === undefined) {
115
+ return;
116
+ }
117
+ const dataIndex = (2 * closestPointIndex - voronoiRef.current[seriesId].startIndex) / 2;
118
+ if (voronoiMaxRadius !== undefined) {
119
+ const pointX = voronoiRef.current.delauney.points[2 * closestPointIndex];
120
+ const pointY = voronoiRef.current.delauney.points[2 * closestPointIndex + 1];
121
+ const dist2 = (pointX - svgPoint.x) ** 2 + (pointY - svgPoint.y) ** 2;
122
+ if (dist2 > voronoiMaxRadius ** 2) {
123
+ // The closest point is too far to be considered.
124
+ dispatch({
125
+ type: 'leaveItem',
126
+ data: {
127
+ type: 'scatter',
128
+ seriesId,
129
+ dataIndex
130
+ }
131
+ });
132
+ return;
133
+ }
134
+ }
135
+ dispatch({
136
+ type: 'enterItem',
137
+ data: {
138
+ type: 'scatter',
139
+ seriesId,
140
+ dataIndex
141
+ }
142
+ });
143
+ }
144
+ };
145
+ element.addEventListener('mouseout', handleMouseOut);
146
+ element.addEventListener('mousemove', handleMouseMove);
147
+ return () => {
148
+ element.removeEventListener('mouseout', handleMouseOut);
149
+ element.removeEventListener('mousemove', handleMouseMove);
150
+ };
151
+ }, [svgRef, dispatch, left, width, top, height, yAxis, xAxis, voronoiMaxRadius]);
152
+ return /*#__PURE__*/_jsx("g", {}); // Workaround to fix docs scripts
153
+ }
154
+ process.env.NODE_ENV !== "production" ? ChartsVoronoiHandler.propTypes = {
155
+ // ----------------------------- Warning --------------------------------
156
+ // | These PropTypes are generated from the TypeScript type definitions |
157
+ // | To update them edit the TypeScript types and run "yarn proptypes" |
158
+ // ----------------------------------------------------------------------
159
+ /**
160
+ * Defines the maximal distance between a scatter point and the pointer that triggers the interaction.
161
+ * If `undefined`, the radius is assumed to be infinite.
162
+ * @default undefined
163
+ */
164
+ voronoiMaxRadius: PropTypes.number
165
+ } : void 0;
166
+ export { ChartsVoronoiHandler };
@@ -0,0 +1 @@
1
+ export * from './ChartsVoronoiHandler';