@@ -144,8 +144,7 @@ exports.loneHover = function loneHover(hoverItem, opts) {
144144 rotateLabels : false ,
145145 bgColor : opts . bgColor || Color . background ,
146146 container : container3 ,
147- outerContainer : outerContainer3 ,
148- hoverdistance : constants . MAXDIST
147+ outerContainer : outerContainer3
149148 } ;
150149
151150 var hoverLabel = createHoverText ( [ pointData ] , fullOpts , opts . gd ) ;
@@ -162,9 +161,10 @@ function _hover(gd, evt, subplot, noHoverEvent) {
162161 // use those instead of finding overlayed plots
163162 var subplots = Array . isArray ( subplot ) ? subplot : [ subplot ] ;
164163
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' ) ;
168168
169169 // list of all overlaid subplots to look at
170170 if ( plotinfo ) {
@@ -351,9 +351,29 @@ function _hover(gd, evt, subplot, noHoverEvent) {
351351 trace : trace ,
352352 xa : xaArray [ subploti ] ,
353353 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+
354362 // point properties - override all of these
355363 index : false , // point index in trace - only used by plotly.js hoverdata consumers
356364 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
357377 color : Color . defaultLine , // trace color
358378 name : trace . name ,
359379 x0 : undefined ,
@@ -418,29 +438,37 @@ function _hover(gd, evt, subplot, noHoverEvent) {
418438 }
419439
420440 // 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)
422443 if ( hovermode === 'closest' && hoverData . length > closedataPreviousLength ) {
423444 hoverData . splice ( 0 , closedataPreviousLength ) ;
424445 distance = hoverData [ 0 ] . distance ;
425446 }
426447
427448 // Now if there is range to look in, find the points to draw the spikelines
428449 // Do it only if there is no hoverData
429- if ( fullLayout . _has ( 'cartesian' ) && ( spikedistance !== 0 ) ) {
450+ if ( hasCartesian && ( spikedistance !== 0 ) ) {
430451 if ( hoverData . length === 0 ) {
431452 pointData . distance = spikedistance ;
432453 pointData . index = false ;
433454 var closestPoints = trace . _module . hoverPoints ( pointData , xval , yval , 'closest' , fullLayout . _hoverlayer ) ;
434455 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 ) {
435463 var tmpPoint ;
436464 var closestVPoints = closestPoints . filter ( function ( point ) {
437465 return point . xa . showspikes ;
438466 } ) ;
439467 if ( closestVPoints . length ) {
440468 var closestVPt = closestVPoints [ 0 ] ;
441469 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 ) ) {
444472 spikePoints . vLinePoint = tmpPoint ;
445473 }
446474 }
@@ -452,8 +480,8 @@ function _hover(gd, evt, subplot, noHoverEvent) {
452480 if ( closestHPoints . length ) {
453481 var closestHPt = closestHPoints [ 0 ] ;
454482 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 ) ) {
457485 spikePoints . hLinePoint = tmpPoint ;
458486 }
459487 }
@@ -464,47 +492,28 @@ function _hover(gd, evt, subplot, noHoverEvent) {
464492 }
465493
466494 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+ }
494504 }
495505 return resultPoint ;
496506 }
497507
498- function fillClosestPoint ( point ) {
508+ function fillSpikePoint ( point ) {
499509 if ( ! point ) return null ;
500510 return {
501511 xa : point . xa ,
502512 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 ,
507515 distance : point . distance ,
516+ spikeDistance : point . spikeDistance ,
508517 curveNumber : point . trace . index ,
509518 color : point . color ,
510519 pointNumber : point . index
@@ -525,34 +534,34 @@ function _hover(gd, evt, subplot, noHoverEvent) {
525534 gd . _spikepoints = newspikepoints ;
526535
527536 // 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 ) ) {
529538 if ( hoverData . length !== 0 ) {
530539 var tmpHPointData = hoverData . filter ( function ( point ) {
531540 return point . ya . showspikes ;
532541 } ) ;
533542 var tmpHPoint = selectClosestPoint ( tmpHPointData , spikedistance ) ;
534- spikePoints . hLinePoint = fillClosestPoint ( tmpHPoint ) ;
543+ spikePoints . hLinePoint = fillSpikePoint ( tmpHPoint ) ;
535544
536545 var tmpVPointData = hoverData . filter ( function ( point ) {
537546 return point . xa . showspikes ;
538547 } ) ;
539548 var tmpVPoint = selectClosestPoint ( tmpVPointData , spikedistance ) ;
540- spikePoints . vLinePoint = fillClosestPoint ( tmpVPoint ) ;
549+ spikePoints . vLinePoint = fillSpikePoint ( tmpVPoint ) ;
541550 }
542551 }
543552
544553 // if hoverData is empty check for the spikes to draw and quit if there are none
545554 if ( hoverData . length === 0 ) {
546555 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 ) ) ) {
548557 if ( spikesChanged ( oldspikepoints ) ) {
549558 createSpikelines ( spikePoints , spikelineOpts ) ;
550559 }
551560 }
552561 return result ;
553562 }
554563
555- if ( fullLayout . _has ( 'cartesian' ) ) {
564+ if ( hasCartesian ) {
556565 if ( spikesChanged ( oldspikepoints ) ) {
557566 createSpikelines ( spikePoints , spikelineOpts ) ;
558567 }
@@ -653,20 +662,31 @@ function createHoverText(hoverData, opts, gd) {
653662 // show the common label, if any, on the axis
654663 // never show a common label in array mode,
655664 // 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+ ) ;
658670
659671 // all hover traces hoverinfo must contain the hovermode
660672 // 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+ }
669686 }
687+
688+ // xyz labels put all info in their main label, so have no need of a common label
689+ if ( allHaveZ ) showCommonLabel = false ;
670690 }
671691
672692 var commonLabel = container . selectAll ( 'g.axistext' )
@@ -1170,7 +1190,9 @@ function cleanPoint(d, hovermode) {
11701190 fill ( 'fontColor' , 'htc' , 'hoverlabel.font.color' ) ;
11711191 fill ( 'nameLength' , 'hnl' , 'hoverlabel.namelength' ) ;
11721192
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 ) ;
11741196
11751197 // then constrain all the positions to be on the plot
11761198 d . x0 = Lib . constrain ( d . x0 , 0 , d . xa . _length ) ;
@@ -1262,8 +1284,8 @@ function createSpikelines(closestPoints, opts) {
12621284 hLinePointX = evt . pointerX ;
12631285 hLinePointY = evt . pointerY ;
12641286 } 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 ;
12671289 }
12681290 var dfltHLineColor = tinycolor . readability ( hLinePoint . color , contrastColor ) < 1.5 ?
12691291 Color . contrast ( contrastColor ) : hLinePoint . color ;
@@ -1338,8 +1360,8 @@ function createSpikelines(closestPoints, opts) {
13381360 vLinePointX = evt . pointerX ;
13391361 vLinePointY = evt . pointerY ;
13401362 } 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 ;
13431365 }
13441366 var dfltVLineColor = tinycolor . readability ( vLinePoint . color , contrastColor ) < 1.5 ?
13451367 Color . contrast ( contrastColor ) : vLinePoint . color ;
0 commit comments