@mui/x-charts
Version:
The community edition of the charts components (MUI X).
162 lines (161 loc) • 6.58 kB
JavaScript
import * as React from 'react';
import PropTypes from 'prop-types';
import { Delaunay } from 'd3-delaunay';
import useEnhancedEffect from '@mui/utils/useEnhancedEffect';
import { InteractionContext } from '../context/InteractionProvider';
import { CartesianContext } from '../context/CartesianContextProvider';
import { SVGContext, DrawingContext } from '../context/DrawingProvider';
import { SeriesContext } from '../context/SeriesContextProvider';
import { getValueToPositionMapper } from '../hooks/useScale';
import { getSVGPoint } from '../internals/utils';
import { jsx as _jsx } from "react/jsx-runtime";
function ChartsVoronoiHandler(props) {
var _React$useContext$sca;
var voronoiMaxRadius = props.voronoiMaxRadius;
var svgRef = React.useContext(SVGContext);
var _React$useContext = React.useContext(DrawingContext),
width = _React$useContext.width,
height = _React$useContext.height,
top = _React$useContext.top,
left = _React$useContext.left;
var _React$useContext2 = React.useContext(CartesianContext),
xAxis = _React$useContext2.xAxis,
yAxis = _React$useContext2.yAxis,
xAxisIds = _React$useContext2.xAxisIds,
yAxisIds = _React$useContext2.yAxisIds;
var _React$useContext3 = React.useContext(InteractionContext),
dispatch = _React$useContext3.dispatch;
var _ref = (_React$useContext$sca = React.useContext(SeriesContext).scatter) != null ? _React$useContext$sca : {},
series = _ref.series,
seriesOrder = _ref.seriesOrder;
var voronoiRef = React.useRef({});
var defaultXAxisId = xAxisIds[0];
var defaultYAxisId = yAxisIds[0];
useEnhancedEffect(function () {
dispatch({
type: 'updateVoronoiUsage',
useVoronoiInteraction: true
});
return function () {
dispatch({
type: 'updateVoronoiUsage',
useVoronoiInteraction: false
});
};
}, [dispatch]);
useEnhancedEffect(function () {
if (seriesOrder === undefined || series === undefined) {
// If there is no scatter chart series
return;
}
voronoiRef.current = {};
var points = [];
seriesOrder.forEach(function (seriesId) {
var _series$seriesId = series[seriesId],
data = _series$seriesId.data,
xAxisKey = _series$seriesId.xAxisKey,
yAxisKey = _series$seriesId.yAxisKey;
var xScale = xAxis[xAxisKey != null ? xAxisKey : defaultXAxisId].scale;
var yScale = yAxis[yAxisKey != null ? yAxisKey : defaultYAxisId].scale;
var getXPosition = getValueToPositionMapper(xScale);
var getYPosition = getValueToPositionMapper(yScale);
var seriesPoints = data.flatMap(function (_ref2) {
var x = _ref2.x,
y = _ref2.y;
return [getXPosition(x), getYPosition(y)];
});
voronoiRef.current[seriesId] = {
startIndex: points.length,
endIndex: points.length + seriesPoints.length
};
points = points.concat(seriesPoints);
});
voronoiRef.current.delauney = new Delaunay(points);
}, [defaultXAxisId, defaultYAxisId, series, seriesOrder, xAxis, yAxis]);
React.useEffect(function () {
var element = svgRef.current;
if (element === null) {
return undefined;
}
var handleMouseOut = function handleMouseOut() {
dispatch({
type: 'exitChart'
});
};
// TODO: A perf optimisation of voronoi could be to use the last point as the intial point for the next search.
var handleMouseMove = function handleMouseMove(event) {
var _voronoiRef$current$d;
// Get mouse coordinate in global SVG space
var svgPoint = getSVGPoint(svgRef.current, event);
var outsideX = svgPoint.x < left || svgPoint.x > left + width;
var outsideY = svgPoint.y < top || svgPoint.y > top + height;
if (outsideX || outsideY) {
dispatch({
type: 'exitChart'
});
return;
}
if (!voronoiRef.current.delauney) {
return;
}
var closestPointIndex = (_voronoiRef$current$d = voronoiRef.current.delauney) == null ? void 0 : _voronoiRef$current$d.find(svgPoint.x, svgPoint.y);
if (closestPointIndex !== undefined) {
var seriesId = Object.keys(voronoiRef.current).find(function (id) {
if (id === 'delauney') {
return false;
}
return 2 * closestPointIndex >= voronoiRef.current[id].startIndex && 2 * closestPointIndex < voronoiRef.current[id].endIndex;
});
if (seriesId === undefined) {
return;
}
var dataIndex = (2 * closestPointIndex - voronoiRef.current[seriesId].startIndex) / 2;
if (voronoiMaxRadius !== undefined) {
var pointX = voronoiRef.current.delauney.points[2 * closestPointIndex];
var pointY = voronoiRef.current.delauney.points[2 * closestPointIndex + 1];
var dist2 = Math.pow(pointX - svgPoint.x, 2) + Math.pow(pointY - svgPoint.y, 2);
if (dist2 > Math.pow(voronoiMaxRadius, 2)) {
// The closest point is too far to be considered.
dispatch({
type: 'leaveItem',
data: {
type: 'scatter',
seriesId: seriesId,
dataIndex: dataIndex
}
});
return;
}
}
dispatch({
type: 'enterItem',
data: {
type: 'scatter',
seriesId: seriesId,
dataIndex: dataIndex
}
});
}
};
element.addEventListener('mouseout', handleMouseOut);
element.addEventListener('mousemove', handleMouseMove);
return function () {
element.removeEventListener('mouseout', handleMouseOut);
element.removeEventListener('mousemove', handleMouseMove);
};
}, [svgRef, dispatch, left, width, top, height, yAxis, xAxis, voronoiMaxRadius]);
return /*#__PURE__*/_jsx("g", {}); // Workaround to fix docs scripts
}
process.env.NODE_ENV !== "production" ? ChartsVoronoiHandler.propTypes = {
// ----------------------------- Warning --------------------------------
// | These PropTypes are generated from the TypeScript type definitions |
// | To update them edit the TypeScript types and run "yarn proptypes" |
// ----------------------------------------------------------------------
/**
* Defines the maximal distance between a scatter point and the pointer that triggers the interaction.
* If `undefined`, the radius is assumed to be infinite.
* @default undefined
*/
voronoiMaxRadius: PropTypes.number
} : void 0;
export { ChartsVoronoiHandler };