@@ -143,17 +143,28 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
143143 var ownFillDir = trace . fill . charAt ( trace . fill . length - 1 ) ;
144144 if ( ownFillDir !== 'x' && ownFillDir !== 'y' ) ownFillDir = '' ;
145145
146+ var fillAxisIndex , fillAxisZero ;
147+ if ( ownFillDir === 'y' ) {
148+ fillAxisIndex = 1 ;
149+ fillAxisZero = ya . c2p ( 0 , true ) ;
150+ } else if ( ownFillDir === 'x' ) {
151+ fillAxisIndex = 0 ;
152+ fillAxisZero = xa . c2p ( 0 , true ) ;
153+ }
154+
146155 // store node for tweaking by selectPoints
147156 cdscatter [ 0 ] [ plotinfo . isRangePlot ? 'nodeRangePlot3' : 'node3' ] = tr ;
148157
149158 var prevRevpath = '' ;
150159 var prevPolygons = [ ] ;
151160 var prevtrace = trace . _prevtrace ;
161+ var prevFillsegments = null ;
152162
153163 if ( prevtrace ) {
154164 prevRevpath = prevtrace . _prevRevpath || '' ;
155165 tonext = prevtrace . _nextFill ;
156- prevPolygons = prevtrace . _polygons ;
166+ prevPolygons = prevtrace . _ownPolygons ;
167+ prevFillsegments = prevtrace . _fillsegments ;
157168 }
158169
159170 var thispath ;
@@ -166,7 +177,15 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
166177 // functions for converting a point array to a path
167178 var pathfn , revpathbase , revpathfn ;
168179 // variables used before and after the data join
169- var pt0 , lastSegment , pt1 , thisPolygons ;
180+ var pt0 , lastSegment , pt1 ;
181+
182+ // thisPolygons always contains only the polygons of this trace only
183+ // whereas trace._polygons may be extended to include those of the previous
184+ // trace as well for exclusion during hover detection
185+ var thisPolygons = [ ] ;
186+ trace . _polygons = [ ] ;
187+
188+ var fillsegments = [ ] ;
170189
171190 // initialize line join data / method
172191 var segments = [ ] ;
@@ -220,35 +239,51 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
220239 } ) ;
221240
222241 // since we already have the pixel segments here, use them to make
223- // polygons for hover on fill
242+ // polygons for hover on fill; we first merge segments where the fill
243+ // is connected into "fillsegments"; the actual polygon construction
244+ // is deferred to later to distinguish between self and tonext/tozero fills.
224245 // TODO: can we skip this if hoveron!=fills? That would mean we
225246 // need to redraw when you change hoveron...
226- thisPolygons = trace . _polygons = new Array ( segments . length ) ;
247+ fillsegments = new Array ( segments . length ) ;
248+ var fillsegmentCount = 0 ;
227249 for ( i = 0 ; i < segments . length ; i ++ ) {
228- trace . _polygons [ i ] = polygonTester ( segments [ i ] ) ;
250+ var curpoints ;
251+ var pts = segments [ i ] ;
252+ if ( ! curpoints || ! ownFillDir ) {
253+ curpoints = pts . slice ( ) ;
254+ fillsegments [ fillsegmentCount ] = curpoints ;
255+ fillsegmentCount ++ ;
256+ } else {
257+ curpoints . push . apply ( curpoints , pts ) ;
258+ }
229259 }
260+ trace . _fillsegments = fillsegments . slice ( 0 , fillsegmentCount ) ;
261+ fillsegments = trace . _fillsegments ;
230262
231263 if ( segments . length ) {
232- pt0 = segments [ 0 ] [ 0 ] ;
264+ pt0 = segments [ 0 ] [ 0 ] . slice ( ) ;
233265 lastSegment = segments [ segments . length - 1 ] ;
234- pt1 = lastSegment [ lastSegment . length - 1 ] ;
266+ pt1 = lastSegment [ lastSegment . length - 1 ] . slice ( ) ;
235267 }
236268
237269 makeUpdate = function ( isEnter ) {
238270 return function ( pts ) {
239271 thispath = pathfn ( pts ) ;
240- thisrevpath = revpathfn ( pts ) ;
272+ thisrevpath = revpathfn ( pts ) ; // side-effect: reverses input
273+ // calculate SVG path over all segments for fills
241274 if ( ! fullpath ) {
242275 fullpath = thispath ;
243276 revpath = thisrevpath ;
244277 } else if ( ownFillDir ) {
278+ // for fills with fill direction: ignore gaps
245279 fullpath += 'L' + thispath . substr ( 1 ) ;
246280 revpath = thisrevpath + ( 'L' + revpath . substr ( 1 ) ) ;
247281 } else {
248282 fullpath += 'Z' + thispath ;
249283 revpath = thisrevpath + 'Z' + revpath ;
250284 }
251285
286+ // actual lines get drawn here, with gaps between segments if requested
252287 if ( subTypes . hasLines ( trace ) ) {
253288 var el = d3 . select ( this ) ;
254289
@@ -290,16 +325,58 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
290325 transition ( selection ) . attr ( 'd' , 'M0,0Z' ) ;
291326 }
292327
328+ // helper functions to create polygons for hoveron fill detection
329+ var makeSelfPolygons = function ( ) {
330+ var polygons = new Array ( fillsegments . length ) ;
331+ for ( i = 0 ; i < fillsegments . length ; i ++ ) {
332+ polygons [ i ] = polygonTester ( fillsegments [ i ] ) ;
333+ }
334+ return polygons ;
335+ } ;
336+
337+ var makePolygonsToPrevious = function ( prevFillsegments ) {
338+ var polygons , i ;
339+ if ( ! prevFillsegments || prevFillsegments . length === 0 ) {
340+ // if there are no fill segments of a previous trace, stretch the
341+ // polygon to the relevant axis
342+ polygons = new Array ( fillsegments . length ) ;
343+ for ( i = 0 ; i < fillsegments . length ; i ++ ) {
344+ var pt0 = fillsegments [ i ] [ 0 ] . slice ( ) ;
345+ var pt1 = fillsegments [ i ] [ fillsegments [ i ] . length - 1 ] . slice ( ) ;
346+
347+ pt0 [ fillAxisIndex ] = pt1 [ fillAxisIndex ] = fillAxisZero ;
348+
349+ var zeropoints = [ pt1 , pt0 ] ;
350+ var polypoints = zeropoints . concat ( fillsegments [ i ] ) ;
351+ polygons [ i ] = polygonTester ( polypoints ) ;
352+ }
353+ } else {
354+ // if there are more than one previous fill segment, the
355+ // way that fills work is to "self" fill all but the last segments
356+ // of the previous and then fill from the new trace to the last
357+ // segment of the previous.
358+ polygons = new Array ( prevFillsegments . length - 1 + fillsegments . length ) ;
359+ for ( i = 0 ; i < prevFillsegments . length - 1 ; i ++ ) {
360+ polygons [ i ] = polygonTester ( prevFillsegments [ i ] ) ;
361+ }
362+
363+ var reversedPrevFillsegment = prevFillsegments [ prevFillsegments . length - 1 ] . slice ( ) ;
364+ reversedPrevFillsegment . reverse ( ) ;
365+
366+ for ( i = 0 ; i < fillsegments . length ; i ++ ) {
367+ polygons [ prevFillsegments . length - 1 + i ] = polygonTester ( fillsegments [ i ] . concat ( reversedPrevFillsegment ) ) ;
368+ }
369+ }
370+ return polygons ;
371+ } ;
372+
373+ // draw fills and create hover detection polygons
293374 if ( segments . length ) {
294375 if ( ownFillEl3 ) {
295376 ownFillEl3 . datum ( cdscatter ) ;
296- if ( pt0 && pt1 ) {
377+ if ( pt0 && pt1 ) { // TODO(2023-12-10): this is always true if segments is not empty (?)
297378 if ( ownFillDir ) {
298- if ( ownFillDir === 'y' ) {
299- pt0 [ 1 ] = pt1 [ 1 ] = ya . c2p ( 0 , true ) ;
300- } else if ( ownFillDir === 'x' ) {
301- pt0 [ 0 ] = pt1 [ 0 ] = xa . c2p ( 0 , true ) ;
302- }
379+ pt0 [ fillAxisIndex ] = pt1 [ fillAxisIndex ] = fillAxisZero ;
303380
304381 // fill to zero: full trace path, plus extension of
305382 // the endpoints to the appropriate axis
@@ -308,12 +385,19 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
308385 // animations get a little crazy if the number of points changes.
309386 transition ( ownFillEl3 ) . attr ( 'd' , 'M' + pt1 + 'L' + pt0 + 'L' + fullpath . substr ( 1 ) )
310387 . call ( Drawing . singleFillStyle , gd ) ;
388+
389+ // create hover polygons that extend to the axis as well.
390+ thisPolygons = makePolygonsToPrevious ( null ) ; // polygon to axis
311391 } else {
312392 // fill to self: just join the path to itself
313393 transition ( ownFillEl3 ) . attr ( 'd' , fullpath + 'Z' )
314394 . call ( Drawing . singleFillStyle , gd ) ;
395+
396+ // and simply emit hover polygons for each segment
397+ thisPolygons = makeSelfPolygons ( ) ;
315398 }
316399 }
400+ trace . _polygons = thisPolygons ;
317401 } else if ( tonext ) {
318402 if ( trace . fill . substr ( 0 , 6 ) === 'tonext' && fullpath && prevRevpath ) {
319403 // fill to next: full trace path, plus the previous path reversed
@@ -324,6 +408,13 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
324408 // inside the other, but then that is a strange usage.
325409 transition ( tonext ) . attr ( 'd' , fullpath + 'Z' + prevRevpath + 'Z' )
326410 . call ( Drawing . singleFillStyle , gd ) ;
411+
412+ // and simply emit hover polygons for each segment
413+ thisPolygons = makeSelfPolygons ( ) ;
414+
415+ // we add the polygons of the previous trace which causes hover
416+ // detection to ignore points contained in them.
417+ trace . _polygons = thisPolygons . concat ( prevPolygons ) ; // this does not modify thisPolygons, on purpose
327418 } else {
328419 // tonextx/y: for now just connect endpoints with lines. This is
329420 // the correct behavior if the endpoints are at the same value of
@@ -332,20 +423,25 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
332423 // existing curve or off the end of it
333424 transition ( tonext ) . attr ( 'd' , fullpath + 'L' + prevRevpath . substr ( 1 ) + 'Z' )
334425 . call ( Drawing . singleFillStyle , gd ) ;
426+
427+ // create hover polygons that extend to the previous trace.
428+ thisPolygons = makePolygonsToPrevious ( prevFillsegments ) ;
429+
430+ // in this case our polygons do not cover that of previous traces,
431+ // so must not include previous trace polygons for hover detection.
432+ trace . _polygons = thisPolygons ;
335433 }
336- trace . _polygons = trace . _polygons . concat ( prevPolygons ) ;
337434 } else {
338435 clearFill ( tonext ) ;
339- trace . _polygons = null ;
340436 }
341437 }
342438 trace . _prevRevpath = revpath ;
343- trace . _prevPolygons = thisPolygons ;
344439 } else {
345440 if ( ownFillEl3 ) clearFill ( ownFillEl3 ) ;
346441 else if ( tonext ) clearFill ( tonext ) ;
347- trace . _polygons = trace . _prevRevpath = trace . _prevPolygons = null ;
442+ trace . _prevRevpath = null ;
348443 }
444+ trace . _ownPolygons = thisPolygons ;
349445
350446
351447 function visFilter ( d ) {
0 commit comments