@@ -143,17 +143,28 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
143
143
var ownFillDir = trace . fill . charAt ( trace . fill . length - 1 ) ;
144
144
if ( ownFillDir !== 'x' && ownFillDir !== 'y' ) ownFillDir = '' ;
145
145
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
+
146
155
// store node for tweaking by selectPoints
147
156
cdscatter [ 0 ] [ plotinfo . isRangePlot ? 'nodeRangePlot3' : 'node3' ] = tr ;
148
157
149
158
var prevRevpath = '' ;
150
159
var prevPolygons = [ ] ;
151
160
var prevtrace = trace . _prevtrace ;
161
+ var prevFillsegments = null ;
152
162
153
163
if ( prevtrace ) {
154
164
prevRevpath = prevtrace . _prevRevpath || '' ;
155
165
tonext = prevtrace . _nextFill ;
156
- prevPolygons = prevtrace . _polygons ;
166
+ prevPolygons = prevtrace . _ownPolygons ;
167
+ prevFillsegments = prevtrace . _fillsegments ;
157
168
}
158
169
159
170
var thispath ;
@@ -166,7 +177,15 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
166
177
// functions for converting a point array to a path
167
178
var pathfn , revpathbase , revpathfn ;
168
179
// 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 = [ ] ;
170
189
171
190
// initialize line join data / method
172
191
var segments = [ ] ;
@@ -220,35 +239,51 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
220
239
} ) ;
221
240
222
241
// 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.
224
245
// TODO: can we skip this if hoveron!=fills? That would mean we
225
246
// 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 ;
227
249
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
+ }
229
259
}
260
+ trace . _fillsegments = fillsegments . slice ( 0 , fillsegmentCount ) ;
261
+ fillsegments = trace . _fillsegments ;
230
262
231
263
if ( segments . length ) {
232
- pt0 = segments [ 0 ] [ 0 ] ;
264
+ pt0 = segments [ 0 ] [ 0 ] . slice ( ) ;
233
265
lastSegment = segments [ segments . length - 1 ] ;
234
- pt1 = lastSegment [ lastSegment . length - 1 ] ;
266
+ pt1 = lastSegment [ lastSegment . length - 1 ] . slice ( ) ;
235
267
}
236
268
237
269
makeUpdate = function ( isEnter ) {
238
270
return function ( pts ) {
239
271
thispath = pathfn ( pts ) ;
240
- thisrevpath = revpathfn ( pts ) ;
272
+ thisrevpath = revpathfn ( pts ) ; // side-effect: reverses input
273
+ // calculate SVG path over all segments for fills
241
274
if ( ! fullpath ) {
242
275
fullpath = thispath ;
243
276
revpath = thisrevpath ;
244
277
} else if ( ownFillDir ) {
278
+ // for fills with fill direction: ignore gaps
245
279
fullpath += 'L' + thispath . substr ( 1 ) ;
246
280
revpath = thisrevpath + ( 'L' + revpath . substr ( 1 ) ) ;
247
281
} else {
248
282
fullpath += 'Z' + thispath ;
249
283
revpath = thisrevpath + 'Z' + revpath ;
250
284
}
251
285
286
+ // actual lines get drawn here, with gaps between segments if requested
252
287
if ( subTypes . hasLines ( trace ) ) {
253
288
var el = d3 . select ( this ) ;
254
289
@@ -290,16 +325,58 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
290
325
transition ( selection ) . attr ( 'd' , 'M0,0Z' ) ;
291
326
}
292
327
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
293
374
if ( segments . length ) {
294
375
if ( ownFillEl3 ) {
295
376
ownFillEl3 . datum ( cdscatter ) ;
296
- if ( pt0 && pt1 ) {
377
+ if ( pt0 && pt1 ) { // TODO(2023-12-10): this is always true if segments is not empty (?)
297
378
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 ;
303
380
304
381
// fill to zero: full trace path, plus extension of
305
382
// the endpoints to the appropriate axis
@@ -308,12 +385,19 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
308
385
// animations get a little crazy if the number of points changes.
309
386
transition ( ownFillEl3 ) . attr ( 'd' , 'M' + pt1 + 'L' + pt0 + 'L' + fullpath . substr ( 1 ) )
310
387
. call ( Drawing . singleFillStyle , gd ) ;
388
+
389
+ // create hover polygons that extend to the axis as well.
390
+ thisPolygons = makePolygonsToPrevious ( null ) ; // polygon to axis
311
391
} else {
312
392
// fill to self: just join the path to itself
313
393
transition ( ownFillEl3 ) . attr ( 'd' , fullpath + 'Z' )
314
394
. call ( Drawing . singleFillStyle , gd ) ;
395
+
396
+ // and simply emit hover polygons for each segment
397
+ thisPolygons = makeSelfPolygons ( ) ;
315
398
}
316
399
}
400
+ trace . _polygons = thisPolygons ;
317
401
} else if ( tonext ) {
318
402
if ( trace . fill . substr ( 0 , 6 ) === 'tonext' && fullpath && prevRevpath ) {
319
403
// fill to next: full trace path, plus the previous path reversed
@@ -324,6 +408,13 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
324
408
// inside the other, but then that is a strange usage.
325
409
transition ( tonext ) . attr ( 'd' , fullpath + 'Z' + prevRevpath + 'Z' )
326
410
. 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
327
418
} else {
328
419
// tonextx/y: for now just connect endpoints with lines. This is
329
420
// 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
332
423
// existing curve or off the end of it
333
424
transition ( tonext ) . attr ( 'd' , fullpath + 'L' + prevRevpath . substr ( 1 ) + 'Z' )
334
425
. 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 ;
335
433
}
336
- trace . _polygons = trace . _polygons . concat ( prevPolygons ) ;
337
434
} else {
338
435
clearFill ( tonext ) ;
339
- trace . _polygons = null ;
340
436
}
341
437
}
342
438
trace . _prevRevpath = revpath ;
343
- trace . _prevPolygons = thisPolygons ;
344
439
} else {
345
440
if ( ownFillEl3 ) clearFill ( ownFillEl3 ) ;
346
441
else if ( tonext ) clearFill ( tonext ) ;
347
- trace . _polygons = trace . _prevRevpath = trace . _prevPolygons = null ;
442
+ trace . _prevRevpath = null ;
348
443
}
444
+ trace . _ownPolygons = thisPolygons ;
349
445
350
446
351
447
function visFilter ( d ) {
0 commit comments