@@ -144,8 +144,7 @@ exports.loneHover = function loneHover(hoverItem, opts) {
144
144
rotateLabels : false ,
145
145
bgColor : opts . bgColor || Color . background ,
146
146
container : container3 ,
147
- outerContainer : outerContainer3 ,
148
- hoverdistance : constants . MAXDIST
147
+ outerContainer : outerContainer3
149
148
} ;
150
149
151
150
var hoverLabel = createHoverText ( [ pointData ] , fullOpts , opts . gd ) ;
@@ -162,9 +161,10 @@ function _hover(gd, evt, subplot, noHoverEvent) {
162
161
// use those instead of finding overlayed plots
163
162
var subplots = Array . isArray ( subplot ) ? subplot : [ subplot ] ;
164
163
165
- var fullLayout = gd . _fullLayout ,
166
- plots = fullLayout . _plots || [ ] ,
167
- plotinfo = plots [ subplot ] ;
164
+ var fullLayout = gd . _fullLayout ;
165
+ var plots = fullLayout . _plots || [ ] ;
166
+ var plotinfo = plots [ subplot ] ;
167
+ var hasCartesian = fullLayout . _has ( 'cartesian' ) ;
168
168
169
169
// list of all overlaid subplots to look at
170
170
if ( plotinfo ) {
@@ -351,9 +351,29 @@ function _hover(gd, evt, subplot, noHoverEvent) {
351
351
trace : trace ,
352
352
xa : xaArray [ subploti ] ,
353
353
ya : yaArray [ subploti ] ,
354
+
355
+ // max distances for hover and spikes - for points that want to show but do not
356
+ // want to override other points, set distance/spikeDistance equal to max*Distance
357
+ // and it will not get filtered out but it will be guaranteed to have a greater
358
+ // distance than any point that calculated a real distance.
359
+ maxHoverDistance : hoverdistance ,
360
+ maxSpikeDistance : spikedistance ,
361
+
354
362
// point properties - override all of these
355
363
index : false , // point index in trace - only used by plotly.js hoverdata consumers
356
364
distance : Math . min ( distance , hoverdistance ) , // pixel distance or pseudo-distance
365
+
366
+ // distance/pseudo-distance for spikes. This distance should always be calculated
367
+ // as if in "closest" mode, and should only be set if this point should
368
+ // generate a spike.
369
+ spikeDistance : Infinity ,
370
+
371
+ // in some cases the spikes have different positioning from the hover label
372
+ // they don't need x0/x1, just one position
373
+ xSpike : undefined ,
374
+ ySpike : undefined ,
375
+
376
+ // where and how to display the hover label
357
377
color : Color . defaultLine , // trace color
358
378
name : trace . name ,
359
379
x0 : undefined ,
@@ -418,29 +438,37 @@ function _hover(gd, evt, subplot, noHoverEvent) {
418
438
}
419
439
420
440
// in closest mode, remove any existing (farther) points
421
- // and don't look any farther than this latest point (or points, if boxes)
441
+ // and don't look any farther than this latest point (or points, some
442
+ // traces like box & violin make multiple hover labels at once)
422
443
if ( hovermode === 'closest' && hoverData . length > closedataPreviousLength ) {
423
444
hoverData . splice ( 0 , closedataPreviousLength ) ;
424
445
distance = hoverData [ 0 ] . distance ;
425
446
}
426
447
427
448
// Now if there is range to look in, find the points to draw the spikelines
428
449
// Do it only if there is no hoverData
429
- if ( fullLayout . _has ( 'cartesian' ) && ( spikedistance !== 0 ) ) {
450
+ if ( hasCartesian && ( spikedistance !== 0 ) ) {
430
451
if ( hoverData . length === 0 ) {
431
452
pointData . distance = spikedistance ;
432
453
pointData . index = false ;
433
454
var closestPoints = trace . _module . hoverPoints ( pointData , xval , yval , 'closest' , fullLayout . _hoverlayer ) ;
434
455
if ( closestPoints ) {
456
+ closestPoints = closestPoints . filter ( function ( point ) {
457
+ // some hover points, like scatter fills, do not allow spikes,
458
+ // so will generate a hover point but without a valid spikeDistance
459
+ return point . spikeDistance <= spikedistance ;
460
+ } ) ;
461
+ }
462
+ if ( closestPoints && closestPoints . length ) {
435
463
var tmpPoint ;
436
464
var closestVPoints = closestPoints . filter ( function ( point ) {
437
465
return point . xa . showspikes ;
438
466
} ) ;
439
467
if ( closestVPoints . length ) {
440
468
var closestVPt = closestVPoints [ 0 ] ;
441
469
if ( isNumeric ( closestVPt . x0 ) && isNumeric ( closestVPt . y0 ) ) {
442
- tmpPoint = fillClosestPoint ( closestVPt ) ;
443
- if ( ! spikePoints . vLinePoint || ( spikePoints . vLinePoint . distance > tmpPoint . distance ) ) {
470
+ tmpPoint = fillSpikePoint ( closestVPt ) ;
471
+ if ( ! spikePoints . vLinePoint || ( spikePoints . vLinePoint . spikeDistance > tmpPoint . spikeDistance ) ) {
444
472
spikePoints . vLinePoint = tmpPoint ;
445
473
}
446
474
}
@@ -452,8 +480,8 @@ function _hover(gd, evt, subplot, noHoverEvent) {
452
480
if ( closestHPoints . length ) {
453
481
var closestHPt = closestHPoints [ 0 ] ;
454
482
if ( isNumeric ( closestHPt . x0 ) && isNumeric ( closestHPt . y0 ) ) {
455
- tmpPoint = fillClosestPoint ( closestHPt ) ;
456
- if ( ! spikePoints . hLinePoint || ( spikePoints . hLinePoint . distance > tmpPoint . distance ) ) {
483
+ tmpPoint = fillSpikePoint ( closestHPt ) ;
484
+ if ( ! spikePoints . hLinePoint || ( spikePoints . hLinePoint . spikeDistance > tmpPoint . spikeDistance ) ) {
457
485
spikePoints . hLinePoint = tmpPoint ;
458
486
}
459
487
}
@@ -464,47 +492,28 @@ function _hover(gd, evt, subplot, noHoverEvent) {
464
492
}
465
493
466
494
function selectClosestPoint ( pointsData , spikedistance ) {
467
- if ( ! pointsData . length ) return null ;
468
- var resultPoint ;
469
- var pointsDistances = pointsData . map ( function ( point , index ) {
470
- var xa = point . xa ,
471
- ya = point . ya ,
472
- xpx = xa . c2p ( xval ) ,
473
- ypx = ya . c2p ( yval ) ,
474
- dxy = function ( point ) {
475
- var rad = point . kink ,
476
- dx = ( point . x1 + point . x0 ) / 2 - xpx ,
477
- dy = ( point . y1 + point . y0 ) / 2 - ypx ;
478
- return Math . max ( Math . sqrt ( dx * dx + dy * dy ) - rad , 1 - 3 / rad ) ;
479
- } ;
480
- var distance = dxy ( point ) ;
481
- return { distance : distance , index : index } ;
482
- } ) ;
483
- pointsDistances = pointsDistances
484
- . filter ( function ( point ) {
485
- return point . distance <= spikedistance ;
486
- } )
487
- . sort ( function ( a , b ) {
488
- return a . distance - b . distance ;
489
- } ) ;
490
- if ( pointsDistances . length ) {
491
- resultPoint = pointsData [ pointsDistances [ 0 ] . index ] ;
492
- } else {
493
- resultPoint = null ;
495
+ var resultPoint = null ;
496
+ var minDistance = Infinity ;
497
+ var thisSpikeDistance ;
498
+ for ( var i = 0 ; i < pointsData . length ; i ++ ) {
499
+ thisSpikeDistance = pointsData [ i ] . spikeDistance ;
500
+ if ( thisSpikeDistance < minDistance && thisSpikeDistance <= spikedistance ) {
501
+ resultPoint = pointsData [ i ] ;
502
+ minDistance = thisSpikeDistance ;
503
+ }
494
504
}
495
505
return resultPoint ;
496
506
}
497
507
498
- function fillClosestPoint ( point ) {
508
+ function fillSpikePoint ( point ) {
499
509
if ( ! point ) return null ;
500
510
return {
501
511
xa : point . xa ,
502
512
ya : point . ya ,
503
- x0 : point . x0 ,
504
- x1 : point . x1 ,
505
- y0 : point . y0 ,
506
- y1 : point . y1 ,
513
+ x : point . xSpike !== undefined ? point . xSpike : ( point . x0 + point . x1 ) / 2 ,
514
+ y : point . ySpike !== undefined ? point . ySpike : ( point . y0 + point . y1 ) / 2 ,
507
515
distance : point . distance ,
516
+ spikeDistance : point . spikeDistance ,
508
517
curveNumber : point . trace . index ,
509
518
color : point . color ,
510
519
pointNumber : point . index
@@ -525,34 +534,34 @@ function _hover(gd, evt, subplot, noHoverEvent) {
525
534
gd . _spikepoints = newspikepoints ;
526
535
527
536
// Now if it is not restricted by spikedistance option, set the points to draw the spikelines
528
- if ( fullLayout . _has ( 'cartesian' ) && ( spikedistance !== 0 ) ) {
537
+ if ( hasCartesian && ( spikedistance !== 0 ) ) {
529
538
if ( hoverData . length !== 0 ) {
530
539
var tmpHPointData = hoverData . filter ( function ( point ) {
531
540
return point . ya . showspikes ;
532
541
} ) ;
533
542
var tmpHPoint = selectClosestPoint ( tmpHPointData , spikedistance ) ;
534
- spikePoints . hLinePoint = fillClosestPoint ( tmpHPoint ) ;
543
+ spikePoints . hLinePoint = fillSpikePoint ( tmpHPoint ) ;
535
544
536
545
var tmpVPointData = hoverData . filter ( function ( point ) {
537
546
return point . xa . showspikes ;
538
547
} ) ;
539
548
var tmpVPoint = selectClosestPoint ( tmpVPointData , spikedistance ) ;
540
- spikePoints . vLinePoint = fillClosestPoint ( tmpVPoint ) ;
549
+ spikePoints . vLinePoint = fillSpikePoint ( tmpVPoint ) ;
541
550
}
542
551
}
543
552
544
553
// if hoverData is empty check for the spikes to draw and quit if there are none
545
554
if ( hoverData . length === 0 ) {
546
555
var result = dragElement . unhoverRaw ( gd , evt ) ;
547
- if ( fullLayout . _has ( 'cartesian' ) && ( ( spikePoints . hLinePoint !== null ) || ( spikePoints . vLinePoint !== null ) ) ) {
556
+ if ( hasCartesian && ( ( spikePoints . hLinePoint !== null ) || ( spikePoints . vLinePoint !== null ) ) ) {
548
557
if ( spikesChanged ( oldspikepoints ) ) {
549
558
createSpikelines ( spikePoints , spikelineOpts ) ;
550
559
}
551
560
}
552
561
return result ;
553
562
}
554
563
555
- if ( fullLayout . _has ( 'cartesian' ) ) {
564
+ if ( hasCartesian ) {
556
565
if ( spikesChanged ( oldspikepoints ) ) {
557
566
createSpikelines ( spikePoints , spikelineOpts ) ;
558
567
}
@@ -653,20 +662,31 @@ function createHoverText(hoverData, opts, gd) {
653
662
// show the common label, if any, on the axis
654
663
// never show a common label in array mode,
655
664
// even if sometimes there could be one
656
- var showCommonLabel = c0 . distance <= opts . hoverdistance &&
657
- ( hovermode === 'x' || hovermode === 'y' ) ;
665
+ var showCommonLabel = (
666
+ ( t0 !== undefined ) &&
667
+ ( c0 . distance <= opts . hoverdistance ) &&
668
+ ( hovermode === 'x' || hovermode === 'y' )
669
+ ) ;
658
670
659
671
// all hover traces hoverinfo must contain the hovermode
660
672
// to have common labels
661
- var i , traceHoverinfo ;
662
- for ( i = 0 ; i < hoverData . length ; i ++ ) {
663
- traceHoverinfo = hoverData [ i ] . hoverinfo || hoverData [ i ] . trace . hoverinfo ;
664
- var parts = Array . isArray ( traceHoverinfo ) ? traceHoverinfo : traceHoverinfo . split ( '+' ) ;
665
- if ( parts . indexOf ( 'all' ) === - 1 &&
666
- parts . indexOf ( hovermode ) === - 1 ) {
667
- showCommonLabel = false ;
668
- break ;
673
+ if ( showCommonLabel ) {
674
+ var i , traceHoverinfo ;
675
+ var allHaveZ = true ;
676
+ for ( i = 0 ; i < hoverData . length ; i ++ ) {
677
+ if ( allHaveZ && hoverData [ i ] . zLabel === undefined ) allHaveZ = false ;
678
+
679
+ traceHoverinfo = hoverData [ i ] . hoverinfo || hoverData [ i ] . trace . hoverinfo ;
680
+ var parts = Array . isArray ( traceHoverinfo ) ? traceHoverinfo : traceHoverinfo . split ( '+' ) ;
681
+ if ( parts . indexOf ( 'all' ) === - 1 &&
682
+ parts . indexOf ( hovermode ) === - 1 ) {
683
+ showCommonLabel = false ;
684
+ break ;
685
+ }
669
686
}
687
+
688
+ // xyz labels put all info in their main label, so have no need of a common label
689
+ if ( allHaveZ ) showCommonLabel = false ;
670
690
}
671
691
672
692
var commonLabel = container . selectAll ( 'g.axistext' )
@@ -1170,7 +1190,9 @@ function cleanPoint(d, hovermode) {
1170
1190
fill ( 'fontColor' , 'htc' , 'hoverlabel.font.color' ) ;
1171
1191
fill ( 'nameLength' , 'hnl' , 'hoverlabel.namelength' ) ;
1172
1192
1173
- d . posref = hovermode === 'y' ? ( d . x0 + d . x1 ) / 2 : ( d . y0 + d . y1 ) / 2 ;
1193
+ d . posref = hovermode === 'y' ?
1194
+ ( d . xa . _offset + ( d . x0 + d . x1 ) / 2 ) :
1195
+ ( d . ya . _offset + ( d . y0 + d . y1 ) / 2 ) ;
1174
1196
1175
1197
// then constrain all the positions to be on the plot
1176
1198
d . x0 = Lib . constrain ( d . x0 , 0 , d . xa . _length ) ;
@@ -1262,8 +1284,8 @@ function createSpikelines(closestPoints, opts) {
1262
1284
hLinePointX = evt . pointerX ;
1263
1285
hLinePointY = evt . pointerY ;
1264
1286
} else {
1265
- hLinePointX = xa . _offset + ( hLinePoint . x0 + hLinePoint . x1 ) / 2 ;
1266
- hLinePointY = ya . _offset + ( hLinePoint . y0 + hLinePoint . y1 ) / 2 ;
1287
+ hLinePointX = xa . _offset + hLinePoint . x ;
1288
+ hLinePointY = ya . _offset + hLinePoint . y ;
1267
1289
}
1268
1290
var dfltHLineColor = tinycolor . readability ( hLinePoint . color , contrastColor ) < 1.5 ?
1269
1291
Color . contrast ( contrastColor ) : hLinePoint . color ;
@@ -1338,8 +1360,8 @@ function createSpikelines(closestPoints, opts) {
1338
1360
vLinePointX = evt . pointerX ;
1339
1361
vLinePointY = evt . pointerY ;
1340
1362
} else {
1341
- vLinePointX = xa . _offset + ( vLinePoint . x0 + vLinePoint . x1 ) / 2 ;
1342
- vLinePointY = ya . _offset + ( vLinePoint . y0 + vLinePoint . y1 ) / 2 ;
1363
+ vLinePointX = xa . _offset + vLinePoint . x ;
1364
+ vLinePointY = ya . _offset + vLinePoint . y ;
1343
1365
}
1344
1366
var dfltVLineColor = tinycolor . readability ( vLinePoint . color , contrastColor ) < 1.5 ?
1345
1367
Color . contrast ( contrastColor ) : vLinePoint . color ;
0 commit comments