@@ -46,6 +46,12 @@ var multipleHoverPoints = {
46
46
candlestick : true
47
47
} ;
48
48
49
+ var cartesianScatterPoints = {
50
+ scatter : true ,
51
+ scattergl : true ,
52
+ splom : true
53
+ } ;
54
+
49
55
// fx.hover: highlight data on hover
50
56
// evt can be a mousemove event, or an object with data about what points
51
57
// to hover on
@@ -574,12 +580,15 @@ function _hover(gd, evt, subplot, noHoverEvent) {
574
580
575
581
findHoverPoints ( ) ;
576
582
577
- function selectClosestPoint ( pointsData , spikedistance ) {
583
+ function selectClosestPoint ( pointsData , spikedistance , spikeOnWinning ) {
578
584
var resultPoint = null ;
579
585
var minDistance = Infinity ;
580
586
var thisSpikeDistance ;
587
+
581
588
for ( var i = 0 ; i < pointsData . length ; i ++ ) {
582
589
thisSpikeDistance = pointsData [ i ] . spikeDistance ;
590
+ if ( spikeOnWinning && i === 0 ) thisSpikeDistance = - Infinity ;
591
+
583
592
if ( thisSpikeDistance <= minDistance && thisSpikeDistance <= spikedistance ) {
584
593
resultPoint = pointsData [ i ] ;
585
594
minDistance = thisSpikeDistance ;
@@ -616,19 +625,30 @@ function _hover(gd, evt, subplot, noHoverEvent) {
616
625
} ;
617
626
gd . _spikepoints = newspikepoints ;
618
627
628
+ var sortHoverData = function ( ) {
629
+ hoverData . sort ( function ( d1 , d2 ) { return d1 . distance - d2 . distance ; } ) ;
630
+
631
+ // move period positioned points and box/bar-like traces to the end of the list
632
+ hoverData = orderRangePoints ( hoverData , hovermode ) ;
633
+ } ;
634
+ sortHoverData ( ) ;
635
+
636
+ var axLetter = hovermode . charAt ( 0 ) ;
637
+ var spikeOnWinning = ( axLetter === 'x' || axLetter === 'y' ) && hoverData [ 0 ] && cartesianScatterPoints [ hoverData [ 0 ] . trace . type ] ;
638
+
619
639
// Now if it is not restricted by spikedistance option, set the points to draw the spikelines
620
640
if ( hasCartesian && ( spikedistance !== 0 ) ) {
621
641
if ( hoverData . length !== 0 ) {
622
642
var tmpHPointData = hoverData . filter ( function ( point ) {
623
643
return point . ya . showspikes ;
624
644
} ) ;
625
- var tmpHPoint = selectClosestPoint ( tmpHPointData , spikedistance ) ;
645
+ var tmpHPoint = selectClosestPoint ( tmpHPointData , spikedistance , spikeOnWinning ) ;
626
646
spikePoints . hLinePoint = fillSpikePoint ( tmpHPoint ) ;
627
647
628
648
var tmpVPointData = hoverData . filter ( function ( point ) {
629
649
return point . xa . showspikes ;
630
650
} ) ;
631
- var tmpVPoint = selectClosestPoint ( tmpVPointData , spikedistance ) ;
651
+ var tmpVPoint = selectClosestPoint ( tmpVPointData , spikedistance , spikeOnWinning ) ;
632
652
spikePoints . vLinePoint = fillSpikePoint ( tmpVPoint ) ;
633
653
}
634
654
}
@@ -650,14 +670,6 @@ function _hover(gd, evt, subplot, noHoverEvent) {
650
670
}
651
671
}
652
672
653
- var sortHoverData = function ( ) {
654
- hoverData . sort ( function ( d1 , d2 ) { return d1 . distance - d2 . distance ; } ) ;
655
-
656
- // move period positioned points and box/bar-like traces to the end of the list
657
- hoverData = orderRangePoints ( hoverData , hovermode ) ;
658
- } ;
659
- sortHoverData ( ) ;
660
-
661
673
if (
662
674
helpers . isXYhover ( _mode ) &&
663
675
hoverData [ 0 ] . length !== 0 &&
@@ -1071,41 +1083,89 @@ function createHoverText(hoverData, opts, gd) {
1071
1083
legendDraw ( gd , mockLegend ) ;
1072
1084
1073
1085
// Position the hover
1074
- var winningPoint = hoverData [ 0 ] ;
1075
- var ly = axLetter === 'y' ?
1076
- ( winningPoint . y0 + winningPoint . y1 ) / 2 : Lib . mean ( hoverData . map ( function ( c ) { return ( c . y0 + c . y1 ) / 2 ; } ) ) ;
1077
- var lx = axLetter === 'x' ?
1078
- ( winningPoint . x0 + winningPoint . x1 ) / 2 : Lib . mean ( hoverData . map ( function ( c ) { return ( c . x0 + c . x1 ) / 2 ; } ) ) ;
1079
-
1080
1086
var legendContainer = container . select ( 'g.legend' ) ;
1081
1087
var tbb = legendContainer . node ( ) . getBoundingClientRect ( ) ;
1082
- lx += xa . _offset ;
1083
- ly += ya . _offset - tbb . height / 2 ;
1084
-
1085
- // Change horizontal alignment to end up on screen
1086
- var txWidth = tbb . width + 2 * HOVERTEXTPAD ;
1087
- var anchorStartOK = lx + txWidth <= outerWidth ;
1088
- var anchorEndOK = lx - txWidth >= 0 ;
1089
- if ( ! anchorStartOK && anchorEndOK ) {
1090
- lx -= txWidth ;
1088
+ var tWidth = tbb . width + 2 * HOVERTEXTPAD ;
1089
+ var tHeight = tbb . height + 2 * HOVERTEXTPAD ;
1090
+ var winningPoint = hoverData [ 0 ] ;
1091
+ var avgX = ( winningPoint . x0 + winningPoint . x1 ) / 2 ;
1092
+ var avgY = ( winningPoint . y0 + winningPoint . y1 ) / 2 ;
1093
+ // When a scatter (or e.g. heatmap) point wins, it's OK for the hovelabel to occlude the bar and other points.
1094
+ var pointWon = ! (
1095
+ Registry . traceIs ( winningPoint . trace , 'bar-like' ) ||
1096
+ Registry . traceIs ( winningPoint . trace , 'box-violin' )
1097
+ ) ;
1098
+
1099
+ var lyBottom , lyTop ;
1100
+ if ( axLetter === 'y' ) {
1101
+ if ( pointWon ) {
1102
+ lyTop = avgY - HOVERTEXTPAD ;
1103
+ lyBottom = avgY + HOVERTEXTPAD ;
1104
+ } else {
1105
+ lyTop = Math . min . apply ( null , hoverData . map ( function ( c ) { return Math . min ( c . y0 , c . y1 ) ; } ) ) ;
1106
+ lyBottom = Math . max . apply ( null , hoverData . map ( function ( c ) { return Math . max ( c . y0 , c . y1 ) ; } ) ) ;
1107
+ }
1108
+ } else {
1109
+ lyTop = lyBottom = Lib . mean ( hoverData . map ( function ( c ) { return ( c . y0 + c . y1 ) / 2 ; } ) ) - tHeight / 2 ;
1110
+ }
1111
+
1112
+ var lxRight , lxLeft ;
1113
+ if ( axLetter === 'x' ) {
1114
+ if ( pointWon ) {
1115
+ lxRight = avgX + HOVERTEXTPAD ;
1116
+ lxLeft = avgX - HOVERTEXTPAD ;
1117
+ } else {
1118
+ lxRight = Math . max . apply ( null , hoverData . map ( function ( c ) { return Math . max ( c . x0 , c . x1 ) ; } ) ) ;
1119
+ lxLeft = Math . min . apply ( null , hoverData . map ( function ( c ) { return Math . min ( c . x0 , c . x1 ) ; } ) ) ;
1120
+ }
1091
1121
} else {
1092
- lx += 2 * HOVERTEXTPAD ;
1122
+ lxRight = lxLeft = Lib . mean ( hoverData . map ( function ( c ) { return ( c . x0 + c . x1 ) / 2 ; } ) ) - tWidth / 2 ;
1093
1123
}
1094
1124
1095
- // Change vertical alignement to end up on screen
1096
- var txHeight = tbb . height + 2 * HOVERTEXTPAD ;
1097
- var overflowTop = ly <= outerTop ;
1098
- var overflowBottom = ly + txHeight >= outerHeight ;
1099
- var canFit = txHeight <= outerHeight ;
1100
- if ( canFit ) {
1101
- if ( overflowTop ) {
1102
- ly = ya . _offset + 2 * HOVERTEXTPAD ;
1103
- } else if ( overflowBottom ) {
1104
- ly = outerHeight - txHeight ;
1125
+ var xOffset = xa . _offset ;
1126
+ var yOffset = ya . _offset ;
1127
+ lyBottom += yOffset ;
1128
+ lxRight += xOffset ;
1129
+ lxLeft += xOffset - tWidth ;
1130
+ lyTop += yOffset - tHeight ;
1131
+
1132
+ var lx , ly ; // top and left positions of the hover box
1133
+
1134
+ // horizontal alignment to end up on screen
1135
+ if ( lxRight + tWidth < outerWidth && lxRight >= 0 ) {
1136
+ lx = lxRight ;
1137
+ } else if ( lxLeft + tWidth < outerWidth && lxLeft >= 0 ) {
1138
+ lx = lxLeft ;
1139
+ } else if ( xOffset + tWidth < outerWidth ) {
1140
+ lx = xOffset ; // subplot left corner
1141
+ } else {
1142
+ // closest left or right side of the paper
1143
+ if ( lxRight - avgX < avgX - lxLeft + tWidth ) {
1144
+ lx = outerWidth - tWidth ;
1145
+ } else {
1146
+ lx = 0 ;
1105
1147
}
1106
1148
}
1107
- legendContainer . attr ( 'transform' , strTranslate ( lx , ly ) ) ;
1149
+ lx += HOVERTEXTPAD ;
1150
+
1151
+ // vertical alignement to end up on screen
1152
+ if ( lyBottom + tHeight < outerHeight && lyBottom >= 0 ) {
1153
+ ly = lyBottom ;
1154
+ } else if ( lyTop + tHeight < outerHeight && lyTop >= 0 ) {
1155
+ ly = lyTop ;
1156
+ } else if ( yOffset + tHeight < outerHeight ) {
1157
+ ly = yOffset ; // subplot top corner
1158
+ } else {
1159
+ // closest top or bottom side of the paper
1160
+ if ( lyBottom - avgY < avgY - lyTop + tHeight ) {
1161
+ ly = outerHeight - tHeight ;
1162
+ } else {
1163
+ ly = 0 ;
1164
+ }
1165
+ }
1166
+ ly += HOVERTEXTPAD ;
1108
1167
1168
+ legendContainer . attr ( 'transform' , strTranslate ( lx - 1 , ly - 1 ) ) ;
1109
1169
return legendContainer ;
1110
1170
}
1111
1171
@@ -1934,7 +1994,10 @@ function getCoord(axLetter, winningPoint, fullLayout) {
1934
1994
var val = winningPoint [ axLetter + 'Val' ] ;
1935
1995
1936
1996
if ( ax . type === 'category' ) val = ax . _categoriesMap [ val ] ;
1937
- else if ( ax . type === 'date' ) val = ax . d2c ( val ) ;
1997
+ else if ( ax . type === 'date' ) {
1998
+ var period = winningPoint [ axLetter + 'Period' ] ;
1999
+ val = ax . d2c ( period !== undefined ? period : val ) ;
2000
+ }
1938
2001
1939
2002
var cd0 = winningPoint . cd [ winningPoint . index ] ;
1940
2003
if ( cd0 && cd0 . t && cd0 . t . posLetter === ax . _id ) {
0 commit comments