@@ -209,14 +209,15 @@ exports.loneHover = function loneHover(hoverItems, opts) {
209
209
210
210
var rotateLabels = false ;
211
211
212
- var hoverLabel = createHoverText ( pointsData , {
212
+ var hoverText = createHoverText ( pointsData , {
213
213
gd : gd ,
214
214
hovermode : 'closest' ,
215
215
rotateLabels : rotateLabels ,
216
216
bgColor : opts . bgColor || Color . background ,
217
217
container : d3 . select ( opts . container ) ,
218
218
outerContainer : opts . outerContainer || opts . container
219
219
} ) ;
220
+ var hoverLabel = hoverText . hoverLabels ;
220
221
221
222
// Fix vertical overlap
222
223
var tooltipSpacing = 5 ;
@@ -819,7 +820,7 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) {
819
820
fullLayout . paper_bgcolor
820
821
) ;
821
822
822
- var hoverLabels = createHoverText ( hoverData , {
823
+ var hoverText = createHoverText ( hoverData , {
823
824
gd : gd ,
824
825
hovermode : hovermode ,
825
826
rotateLabels : rotateLabels ,
@@ -829,9 +830,10 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) {
829
830
commonLabelOpts : fullLayout . hoverlabel ,
830
831
hoverdistance : fullLayout . hoverdistance
831
832
} ) ;
833
+ var hoverLabels = hoverText . hoverLabels ;
832
834
833
835
if ( ! helpers . isUnifiedHover ( hovermode ) ) {
834
- hoverAvoidOverlaps ( hoverLabels , rotateLabels ? 'xa' : 'ya' , fullLayout ) ;
836
+ hoverAvoidOverlaps ( hoverLabels , rotateLabels , fullLayout , hoverText . commonLabelBoundingBox ) ;
835
837
alignHoverText ( hoverLabels , rotateLabels , fullLayout . _invScaleX , fullLayout . _invScaleY ) ;
836
838
} // TODO: tagName hack is needed to appease geo.js's hack of using eventTarget=true
837
839
// we should improve the "fx" API so other plots can use it without these hack.
@@ -942,6 +944,13 @@ function createHoverText(hoverData, opts) {
942
944
. classed ( 'axistext' , true ) ;
943
945
commonLabel . exit ( ) . remove ( ) ;
944
946
947
+ // set rect (without arrow) behind label below for later collision detection
948
+ var commonLabelRect = {
949
+ minX : 0 ,
950
+ maxX : 0 ,
951
+ minY : 0 ,
952
+ maxY : 0
953
+ } ;
945
954
commonLabel . each ( function ( ) {
946
955
var label = d3 . select ( this ) ;
947
956
var lpath = Lib . ensureSingle ( label , 'path' , '' , function ( s ) {
@@ -995,7 +1004,7 @@ function createHoverText(hoverData, opts) {
995
1004
996
1005
lpath . attr ( 'd' , 'M-' + ( halfWidth - HOVERARROWSIZE ) + ',0' +
997
1006
'L-' + ( halfWidth - HOVERARROWSIZE * 2 ) + ',' + topsign + HOVERARROWSIZE +
998
- 'H' + ( HOVERTEXTPAD + tbb . width / 2 ) +
1007
+ 'H' + ( halfWidth ) +
999
1008
'v' + topsign + ( HOVERTEXTPAD * 2 + tbb . height ) +
1000
1009
'H-' + halfWidth +
1001
1010
'V' + topsign + HOVERARROWSIZE +
@@ -1012,12 +1021,23 @@ function createHoverText(hoverData, opts) {
1012
1021
} else {
1013
1022
lpath . attr ( 'd' , 'M0,0' +
1014
1023
'L' + HOVERARROWSIZE + ',' + topsign + HOVERARROWSIZE +
1015
- 'H' + ( HOVERTEXTPAD + tbb . width / 2 ) +
1024
+ 'H' + ( halfWidth ) +
1016
1025
'v' + topsign + ( HOVERTEXTPAD * 2 + tbb . height ) +
1017
- 'H-' + ( HOVERTEXTPAD + tbb . width / 2 ) +
1026
+ 'H-' + ( halfWidth ) +
1018
1027
'V' + topsign + HOVERARROWSIZE +
1019
1028
'H-' + HOVERARROWSIZE + 'Z' ) ;
1020
1029
}
1030
+
1031
+ commonLabelRect . minX = lx - halfWidth ;
1032
+ commonLabelRect . maxX = lx + halfWidth ;
1033
+ if ( xa . side === 'top' ) {
1034
+ // label on negative y side
1035
+ commonLabelRect . minY = ly - ( HOVERTEXTPAD * 2 + tbb . height ) ;
1036
+ commonLabelRect . maxY = ly - HOVERTEXTPAD ;
1037
+ } else {
1038
+ commonLabelRect . minY = ly + HOVERTEXTPAD ;
1039
+ commonLabelRect . maxY = ly + ( HOVERTEXTPAD * 2 + tbb . height ) ;
1040
+ }
1021
1041
} else {
1022
1042
var anchor ;
1023
1043
var sgn ;
@@ -1045,6 +1065,17 @@ function createHoverText(hoverData, opts) {
1045
1065
'V-' + ( HOVERTEXTPAD + tbb . height / 2 ) +
1046
1066
'H' + leftsign + HOVERARROWSIZE + 'V-' + HOVERARROWSIZE + 'Z' ) ;
1047
1067
1068
+ commonLabelRect . minY = ly - ( HOVERTEXTPAD + tbb . height / 2 ) ;
1069
+ commonLabelRect . maxY = ly + ( HOVERTEXTPAD + tbb . height / 2 ) ;
1070
+ if ( ya . side === 'right' ) {
1071
+ commonLabelRect . minX = lx + HOVERARROWSIZE ;
1072
+ commonLabelRect . maxX = lx + HOVERARROWSIZE + ( HOVERTEXTPAD * 2 + tbb . width ) ;
1073
+ } else {
1074
+ // label on negative x side
1075
+ commonLabelRect . minX = lx - HOVERARROWSIZE - ( HOVERTEXTPAD * 2 + tbb . width ) ;
1076
+ commonLabelRect . maxX = lx - HOVERARROWSIZE ;
1077
+ }
1078
+
1048
1079
var halfHeight = tbb . height / 2 ;
1049
1080
var lty = outerTop - tbb . top - halfHeight ;
1050
1081
var clipId = 'clip' + fullLayout . _uid + 'commonlabel' + ya . _id ;
@@ -1370,7 +1401,10 @@ function createHoverText(hoverData, opts) {
1370
1401
} else if ( anchorStartOK ) {
1371
1402
hty += dy / 2 ;
1372
1403
d . anchor = 'start' ;
1373
- } else d . anchor = 'middle' ;
1404
+ } else {
1405
+ d . anchor = 'middle' ;
1406
+ }
1407
+ d . crossPos = hty ;
1374
1408
} else {
1375
1409
d . pos = hty ;
1376
1410
anchorStartOK = htx + dx / 2 + txTotalWidth <= outerWidth ;
@@ -1391,6 +1425,7 @@ function createHoverText(hoverData, opts) {
1391
1425
if ( overflowR > 0 ) htx -= overflowR ;
1392
1426
if ( overflowL < 0 ) htx += - overflowL ;
1393
1427
}
1428
+ d . crossPos = htx ;
1394
1429
}
1395
1430
1396
1431
tx . attr ( 'text-anchor' , d . anchor ) ;
@@ -1399,7 +1434,10 @@ function createHoverText(hoverData, opts) {
1399
1434
( rotateLabels ? strRotate ( YANGLE ) : '' ) ) ;
1400
1435
} ) ;
1401
1436
1402
- return hoverLabels ;
1437
+ return {
1438
+ hoverLabels : hoverLabels ,
1439
+ commonLabelBoundingBox : commonLabelRect
1440
+ } ;
1403
1441
}
1404
1442
1405
1443
function getHoverLabelText ( d , showCommonLabel , hovermode , fullLayout , t0 , g ) {
@@ -1493,7 +1531,9 @@ function getHoverLabelText(d, showCommonLabel, hovermode, fullLayout, t0, g) {
1493
1531
// know what happens if the group spans all the way from one edge to
1494
1532
// the other, though it hardly matters - there's just too much
1495
1533
// information then.
1496
- function hoverAvoidOverlaps ( hoverLabels , axKey , fullLayout ) {
1534
+ function hoverAvoidOverlaps ( hoverLabels , rotateLabels , fullLayout , commonLabelBoundingBox ) {
1535
+ var axKey = rotateLabels ? 'xa' : 'ya' ;
1536
+ var crossAxKey = rotateLabels ? 'ya' : 'xa' ;
1497
1537
var nummoves = 0 ;
1498
1538
var axSign = 1 ;
1499
1539
var nLabels = hoverLabels . size ( ) ;
@@ -1502,23 +1542,83 @@ function hoverAvoidOverlaps(hoverLabels, axKey, fullLayout) {
1502
1542
var pointgroups = new Array ( nLabels ) ;
1503
1543
var k = 0 ;
1504
1544
1545
+ // get extent of axis hover label
1546
+ var axisLabelMinX = commonLabelBoundingBox . minX ;
1547
+ var axisLabelMaxX = commonLabelBoundingBox . maxX ;
1548
+ var axisLabelMinY = commonLabelBoundingBox . minY ;
1549
+ var axisLabelMaxY = commonLabelBoundingBox . maxY ;
1550
+
1551
+ var pX = function ( x ) { return x * fullLayout . _invScaleX ; } ;
1552
+ var pY = function ( y ) { return y * fullLayout . _invScaleY ; } ;
1553
+
1505
1554
hoverLabels . each ( function ( d ) {
1506
1555
var ax = d [ axKey ] ;
1556
+ var crossAx = d [ crossAxKey ] ;
1507
1557
var axIsX = ax . _id . charAt ( 0 ) === 'x' ;
1508
1558
var rng = ax . range ;
1509
1559
1510
1560
if ( k === 0 && rng && ( ( rng [ 0 ] > rng [ 1 ] ) !== axIsX ) ) {
1511
1561
axSign = - 1 ;
1512
1562
}
1563
+ var pmin = 0 ;
1564
+ var pmax = ( axIsX ? fullLayout . width : fullLayout . height ) ;
1565
+ // in hovermode avoid overlap between hover labels and axis label
1566
+ if ( fullLayout . hovermode === 'x' || fullLayout . hovermode === 'y' ) {
1567
+ // extent of rect behind hover label on cross axis:
1568
+ var offsets = getHoverLabelOffsets ( d , rotateLabels ) ;
1569
+ var anchor = d . anchor ;
1570
+ var horzSign = anchor === 'end' ? - 1 : 1 ;
1571
+ var labelMin ;
1572
+ var labelMax ;
1573
+ if ( anchor === 'middle' ) {
1574
+ // use extent of centered rect either on x or y axis depending on current axis
1575
+ labelMin = d . crossPos + ( axIsX ? pY ( offsets . y - d . by / 2 ) : pX ( d . bx / 2 + d . tx2width / 2 ) ) ;
1576
+ labelMax = labelMin + ( axIsX ? pY ( d . by ) : pX ( d . bx ) ) ;
1577
+ } else {
1578
+ // use extend of path (see alignHoverText function) without arrow
1579
+ if ( axIsX ) {
1580
+ labelMin = d . crossPos + pY ( HOVERARROWSIZE + offsets . y ) - pY ( d . by / 2 - HOVERARROWSIZE ) ;
1581
+ labelMax = labelMin + pY ( d . by ) ;
1582
+ } else {
1583
+ var startX = pX ( horzSign * HOVERARROWSIZE + offsets . x ) ;
1584
+ var endX = startX + pX ( horzSign * d . bx ) ;
1585
+ labelMin = d . crossPos + Math . min ( startX , endX ) ;
1586
+ labelMax = d . crossPos + Math . max ( startX , endX ) ;
1587
+ }
1588
+ }
1589
+
1590
+ if ( axIsX ) {
1591
+ if ( axisLabelMinY !== undefined && axisLabelMaxY !== undefined && Math . min ( labelMax , axisLabelMaxY ) - Math . max ( labelMin , axisLabelMinY ) > 1 ) {
1592
+ // has at least 1 pixel overlap with axis label
1593
+ if ( crossAx . side === 'left' ) {
1594
+ pmin = crossAx . _mainLinePosition ;
1595
+ pmax = fullLayout . width ;
1596
+ } else {
1597
+ pmax = crossAx . _mainLinePosition ;
1598
+ }
1599
+ }
1600
+ } else {
1601
+ if ( axisLabelMinX !== undefined && axisLabelMaxX !== undefined && Math . min ( labelMax , axisLabelMaxX ) - Math . max ( labelMin , axisLabelMinX ) > 1 ) {
1602
+ // has at least 1 pixel overlap with axis label
1603
+ if ( crossAx . side === 'top' ) {
1604
+ pmin = crossAx . _mainLinePosition ;
1605
+ pmax = fullLayout . height ;
1606
+ } else {
1607
+ pmax = crossAx . _mainLinePosition ;
1608
+ }
1609
+ }
1610
+ }
1611
+ }
1612
+
1513
1613
pointgroups [ k ++ ] = [ {
1514
1614
datum : d ,
1515
1615
traceIndex : d . trace . index ,
1516
1616
dp : 0 ,
1517
1617
pos : d . pos ,
1518
1618
posref : d . posref ,
1519
1619
size : d . by * ( axIsX ? YFACTOR : 1 ) / 2 ,
1520
- pmin : 0 ,
1521
- pmax : ( axIsX ? fullLayout . width : fullLayout . height )
1620
+ pmin : pmin ,
1621
+ pmax : pmax
1522
1622
} ] ;
1523
1623
} ) ;
1524
1624
@@ -1662,6 +1762,42 @@ function hoverAvoidOverlaps(hoverLabels, axKey, fullLayout) {
1662
1762
}
1663
1763
}
1664
1764
1765
+ function getHoverLabelOffsets ( hoverLabel , rotateLabels ) {
1766
+ var offsetX = 0 ;
1767
+ var offsetY = hoverLabel . offset ;
1768
+
1769
+ if ( rotateLabels ) {
1770
+ offsetY *= - YSHIFTY ;
1771
+ offsetX = hoverLabel . offset * YSHIFTX ;
1772
+ }
1773
+
1774
+ return {
1775
+ x : offsetX ,
1776
+ y : offsetY
1777
+ } ;
1778
+ }
1779
+
1780
+ /**
1781
+ * Calculate the shift in x for text and text2 elements
1782
+ */
1783
+ function getTextShiftX ( hoverLabel ) {
1784
+ var alignShift = { start : 1 , end : - 1 , middle : 0 } [ hoverLabel . anchor ] ;
1785
+ var textShiftX = alignShift * ( HOVERARROWSIZE + HOVERTEXTPAD ) ;
1786
+ var text2ShiftX = textShiftX + alignShift * ( hoverLabel . txwidth + HOVERTEXTPAD ) ;
1787
+
1788
+ var isMiddle = hoverLabel . anchor === 'middle' ;
1789
+ if ( isMiddle ) {
1790
+ textShiftX -= hoverLabel . tx2width / 2 ;
1791
+ text2ShiftX += hoverLabel . txwidth / 2 + HOVERTEXTPAD ;
1792
+ }
1793
+
1794
+ return {
1795
+ alignShift : alignShift ,
1796
+ textShiftX : textShiftX ,
1797
+ text2ShiftX : text2ShiftX
1798
+ } ;
1799
+ }
1800
+
1665
1801
function alignHoverText ( hoverLabels , rotateLabels , scaleX , scaleY ) {
1666
1802
var pX = function ( x ) { return x * scaleX ; } ;
1667
1803
var pY = function ( y ) { return y * scaleY ; } ;
@@ -1675,21 +1811,12 @@ function alignHoverText(hoverLabels, rotateLabels, scaleX, scaleY) {
1675
1811
var tx = g . select ( 'text.nums' ) ;
1676
1812
var anchor = d . anchor ;
1677
1813
var horzSign = anchor === 'end' ? - 1 : 1 ;
1678
- var alignShift = { start : 1 , end : - 1 , middle : 0 } [ anchor ] ;
1679
- var txx = alignShift * ( HOVERARROWSIZE + HOVERTEXTPAD ) ;
1680
- var tx2x = txx + alignShift * ( d . txwidth + HOVERTEXTPAD ) ;
1681
- var offsetX = 0 ;
1682
- var offsetY = d . offset ;
1814
+ var shiftX = getTextShiftX ( d ) ;
1815
+ var offsets = getHoverLabelOffsets ( d , rotateLabels ) ;
1816
+ var offsetX = offsets . x ;
1817
+ var offsetY = offsets . y ;
1683
1818
1684
1819
var isMiddle = anchor === 'middle' ;
1685
- if ( isMiddle ) {
1686
- txx -= d . tx2width / 2 ;
1687
- tx2x += d . txwidth / 2 + HOVERTEXTPAD ;
1688
- }
1689
- if ( rotateLabels ) {
1690
- offsetY *= - YSHIFTY ;
1691
- offsetX = d . offset * YSHIFTX ;
1692
- }
1693
1820
1694
1821
g . select ( 'path' )
1695
1822
. attr ( 'd' , isMiddle ?
@@ -1705,7 +1832,7 @@ function alignHoverText(hoverLabels, rotateLabels, scaleX, scaleY) {
1705
1832
'V' + pY ( offsetY - HOVERARROWSIZE ) +
1706
1833
'Z' ) ) ;
1707
1834
1708
- var posX = offsetX + txx ;
1835
+ var posX = offsetX + shiftX . textShiftX ;
1709
1836
var posY = offsetY + d . ty0 - d . by / 2 + HOVERTEXTPAD ;
1710
1837
var textAlign = d . textAlign || 'auto' ;
1711
1838
@@ -1728,11 +1855,11 @@ function alignHoverText(hoverLabels, rotateLabels, scaleX, scaleY) {
1728
1855
if ( d . tx2width ) {
1729
1856
g . select ( 'text.name' )
1730
1857
. call ( svgTextUtils . positionText ,
1731
- pX ( tx2x + alignShift * HOVERTEXTPAD + offsetX ) ,
1858
+ pX ( shiftX . text2ShiftX + shiftX . alignShift * HOVERTEXTPAD + offsetX ) ,
1732
1859
pY ( offsetY + d . ty0 - d . by / 2 + HOVERTEXTPAD ) ) ;
1733
1860
g . select ( 'rect' )
1734
1861
. call ( Drawing . setRect ,
1735
- pX ( tx2x + ( alignShift - 1 ) * d . tx2width / 2 + offsetX ) ,
1862
+ pX ( shiftX . text2ShiftX + ( shiftX . alignShift - 1 ) * d . tx2width / 2 + offsetX ) ,
1736
1863
pY ( offsetY - d . by / 2 - 1 ) ,
1737
1864
pX ( d . tx2width ) , pY ( d . by + 2 ) ) ;
1738
1865
}
0 commit comments