@@ -31,13 +31,14 @@ type VoronoiSeries = { seriesId: SeriesId; startIndex: number; endIndex: number
3131function ChartsVoronoiHandler ( props : ChartsVoronoiHandlerProps ) {
3232 const { voronoiMaxRadius, onItemClick } = props ;
3333 const svgRef = useSvgRef ( ) ;
34- const { left , top , width , height } = useDrawingArea ( ) ;
34+ const drawingArea = useDrawingArea ( ) ;
3535 const { xAxis, yAxis, xAxisIds, yAxisIds } = useCartesianContext ( ) ;
3636 const { dispatch } = React . useContext ( InteractionContext ) ;
3737
3838 const { series, seriesOrder } = useScatterSeries ( ) ?? { } ;
3939 const voronoiRef = React . useRef < Record < string , VoronoiSeries > > ( { } ) ;
4040 const delauneyRef = React . useRef < Delaunay < any > | undefined > ( undefined ) ;
41+ const lastFind = React . useRef < number | undefined > ( undefined ) ;
4142
4243 const { setHighlighted, clearHighlighted } = useHighlighted ( ) ;
4344
@@ -70,7 +71,20 @@ function ChartsVoronoiHandler(props: ChartsVoronoiHandlerProps) {
7071 const getXPosition = getValueToPositionMapper ( xScale ) ;
7172 const getYPosition = getValueToPositionMapper ( yScale ) ;
7273
73- const seriesPoints = data . flatMap ( ( { x, y } ) => [ getXPosition ( x ) , getYPosition ( y ) ] ) ;
74+ const seriesPoints = data . flatMap ( ( { x, y } ) => {
75+ const pointX = getXPosition ( x ) ;
76+ const pointY = getYPosition ( y ) ;
77+
78+ if ( ! drawingArea . isPointInside ( { x : pointX , y : pointY } ) ) {
79+ // If the point is not displayed we move them to a trash coordinate.
80+ // This avoids managing index mapping before/after filtering.
81+ // The trash point is far enough such that any point in the drawing area will be closer to the mouse than the trash coordinate.
82+ return [ - drawingArea . width , - drawingArea . height ] ;
83+ }
84+
85+ return [ pointX , pointY ] ;
86+ } ) ;
87+
7488 voronoiRef . current [ seriesId ] = {
7589 seriesId,
7690 startIndex : points . length ,
@@ -80,15 +94,15 @@ function ChartsVoronoiHandler(props: ChartsVoronoiHandlerProps) {
8094 } ) ;
8195
8296 delauneyRef . current = new Delaunay ( points ) ;
83- } , [ defaultXAxisId , defaultYAxisId , series , seriesOrder , xAxis , yAxis ] ) ;
97+ lastFind . current = undefined ;
98+ } , [ defaultXAxisId , defaultYAxisId , series , seriesOrder , xAxis , yAxis , drawingArea ] ) ;
8499
85100 React . useEffect ( ( ) => {
86101 const element = svgRef . current ;
87102 if ( element === null ) {
88103 return undefined ;
89104 }
90105
91- // TODO: A perf optimisation of voronoi could be to use the last point as the initial point for the next search.
92106 function getClosestPoint (
93107 event : MouseEvent ,
94108 ) :
@@ -99,21 +113,21 @@ function ChartsVoronoiHandler(props: ChartsVoronoiHandlerProps) {
99113 // Get mouse coordinate in global SVG space
100114 const svgPoint = getSVGPoint ( element , event ) ;
101115
102- const outsideX = svgPoint . x < left || svgPoint . x > left + width ;
103- const outsideY = svgPoint . y < top || svgPoint . y > top + height ;
104- if ( outsideX || outsideY ) {
116+ if ( ! drawingArea . isPointInside ( svgPoint ) ) {
117+ lastFind . current = undefined ;
105118 return 'outside-chart' ;
106119 }
107120
108121 if ( ! delauneyRef . current ) {
109122 return 'no-point-found' ;
110123 }
111124
112- const closestPointIndex = delauneyRef . current . find ( svgPoint . x , svgPoint . y ) ;
125+ const closestPointIndex = delauneyRef . current . find ( svgPoint . x , svgPoint . y , lastFind . current ) ;
113126 if ( closestPointIndex === undefined ) {
114127 return 'no-point-found' ;
115128 }
116129
130+ lastFind . current = closestPointIndex ;
117131 const closestSeries = Object . values ( voronoiRef . current ) . find ( ( value ) => {
118132 return 2 * closestPointIndex >= value . startIndex && 2 * closestPointIndex < value . endIndex ;
119133 } ) ;
@@ -137,7 +151,7 @@ function ChartsVoronoiHandler(props: ChartsVoronoiHandlerProps) {
137151 return { seriesId : closestSeries . seriesId , dataIndex } ;
138152 }
139153
140- const handleMouseOut = ( ) => {
154+ const handleMouseLeave = ( ) => {
141155 dispatch ( { type : 'exitChart' } ) ;
142156 clearHighlighted ( ) ;
143157 } ;
@@ -180,27 +194,24 @@ function ChartsVoronoiHandler(props: ChartsVoronoiHandlerProps) {
180194 onItemClick ( event , { type : 'scatter' , seriesId, dataIndex } ) ;
181195 } ;
182196
183- element . addEventListener ( 'pointerout ' , handleMouseOut ) ;
197+ element . addEventListener ( 'pointerleave ' , handleMouseLeave ) ;
184198 element . addEventListener ( 'pointermove' , handleMouseMove ) ;
185199 element . addEventListener ( 'click' , handleMouseClick ) ;
186200 return ( ) => {
187- element . removeEventListener ( 'pointerout ' , handleMouseOut ) ;
201+ element . removeEventListener ( 'pointerleave ' , handleMouseLeave ) ;
188202 element . removeEventListener ( 'pointermove' , handleMouseMove ) ;
189203 element . removeEventListener ( 'click' , handleMouseClick ) ;
190204 } ;
191205 } , [
192206 svgRef ,
193207 dispatch ,
194- left ,
195- width ,
196- top ,
197- height ,
198208 yAxis ,
199209 xAxis ,
200210 voronoiMaxRadius ,
201211 onItemClick ,
202212 setHighlighted ,
203213 clearHighlighted ,
214+ drawingArea ,
204215 ] ) ;
205216
206217 // eslint-disable-next-line react/jsx-no-useless-fragment
0 commit comments