@@ -209,14 +209,15 @@ exports.loneHover = function loneHover(hoverItems, opts) {
209209
210210 var rotateLabels = false ;
211211
212- var hoverLabel = createHoverText ( pointsData , {
212+ var hoverText = createHoverText ( pointsData , {
213213 gd : gd ,
214214 hovermode : 'closest' ,
215215 rotateLabels : rotateLabels ,
216216 bgColor : opts . bgColor || Color . background ,
217217 container : d3 . select ( opts . container ) ,
218218 outerContainer : opts . outerContainer || opts . container
219219 } ) ;
220+ var hoverLabel = hoverText . hoverLabels ;
220221
221222 // Fix vertical overlap
222223 var tooltipSpacing = 5 ;
@@ -819,7 +820,7 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) {
819820 fullLayout . paper_bgcolor
820821 ) ;
821822
822- var hoverLabels = createHoverText ( hoverData , {
823+ var hoverText = createHoverText ( hoverData , {
823824 gd : gd ,
824825 hovermode : hovermode ,
825826 rotateLabels : rotateLabels ,
@@ -829,9 +830,10 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) {
829830 commonLabelOpts : fullLayout . hoverlabel ,
830831 hoverdistance : fullLayout . hoverdistance
831832 } ) ;
833+ var hoverLabels = hoverText . hoverLabels ;
832834
833835 if ( ! helpers . isUnifiedHover ( hovermode ) ) {
834- hoverAvoidOverlaps ( hoverLabels , rotateLabels ? 'xa' : 'ya' , fullLayout ) ;
836+ hoverAvoidOverlaps ( hoverLabels , rotateLabels , fullLayout , hoverText . commonLabelBoundingBox ) ;
835837 alignHoverText ( hoverLabels , rotateLabels , fullLayout . _invScaleX , fullLayout . _invScaleY ) ;
836838 } // TODO: tagName hack is needed to appease geo.js's hack of using eventTarget=true
837839 // we should improve the "fx" API so other plots can use it without these hack.
@@ -942,6 +944,13 @@ function createHoverText(hoverData, opts) {
942944 . classed ( 'axistext' , true ) ;
943945 commonLabel . exit ( ) . remove ( ) ;
944946
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+ } ;
945954 commonLabel . each ( function ( ) {
946955 var label = d3 . select ( this ) ;
947956 var lpath = Lib . ensureSingle ( label , 'path' , '' , function ( s ) {
@@ -995,7 +1004,7 @@ function createHoverText(hoverData, opts) {
9951004
9961005 lpath . attr ( 'd' , 'M-' + ( halfWidth - HOVERARROWSIZE ) + ',0' +
9971006 'L-' + ( halfWidth - HOVERARROWSIZE * 2 ) + ',' + topsign + HOVERARROWSIZE +
998- 'H' + ( HOVERTEXTPAD + tbb . width / 2 ) +
1007+ 'H' + ( halfWidth ) +
9991008 'v' + topsign + ( HOVERTEXTPAD * 2 + tbb . height ) +
10001009 'H-' + halfWidth +
10011010 'V' + topsign + HOVERARROWSIZE +
@@ -1012,12 +1021,23 @@ function createHoverText(hoverData, opts) {
10121021 } else {
10131022 lpath . attr ( 'd' , 'M0,0' +
10141023 'L' + HOVERARROWSIZE + ',' + topsign + HOVERARROWSIZE +
1015- 'H' + ( HOVERTEXTPAD + tbb . width / 2 ) +
1024+ 'H' + ( halfWidth ) +
10161025 'v' + topsign + ( HOVERTEXTPAD * 2 + tbb . height ) +
1017- 'H-' + ( HOVERTEXTPAD + tbb . width / 2 ) +
1026+ 'H-' + ( halfWidth ) +
10181027 'V' + topsign + HOVERARROWSIZE +
10191028 'H-' + HOVERARROWSIZE + 'Z' ) ;
10201029 }
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+ }
10211041 } else {
10221042 var anchor ;
10231043 var sgn ;
@@ -1045,6 +1065,17 @@ function createHoverText(hoverData, opts) {
10451065 'V-' + ( HOVERTEXTPAD + tbb . height / 2 ) +
10461066 'H' + leftsign + HOVERARROWSIZE + 'V-' + HOVERARROWSIZE + 'Z' ) ;
10471067
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+
10481079 var halfHeight = tbb . height / 2 ;
10491080 var lty = outerTop - tbb . top - halfHeight ;
10501081 var clipId = 'clip' + fullLayout . _uid + 'commonlabel' + ya . _id ;
@@ -1370,7 +1401,10 @@ function createHoverText(hoverData, opts) {
13701401 } else if ( anchorStartOK ) {
13711402 hty += dy / 2 ;
13721403 d . anchor = 'start' ;
1373- } else d . anchor = 'middle' ;
1404+ } else {
1405+ d . anchor = 'middle' ;
1406+ }
1407+ d . crossPos = hty ;
13741408 } else {
13751409 d . pos = hty ;
13761410 anchorStartOK = htx + dx / 2 + txTotalWidth <= outerWidth ;
@@ -1391,6 +1425,7 @@ function createHoverText(hoverData, opts) {
13911425 if ( overflowR > 0 ) htx -= overflowR ;
13921426 if ( overflowL < 0 ) htx += - overflowL ;
13931427 }
1428+ d . crossPos = htx ;
13941429 }
13951430
13961431 tx . attr ( 'text-anchor' , d . anchor ) ;
@@ -1399,7 +1434,10 @@ function createHoverText(hoverData, opts) {
13991434 ( rotateLabels ? strRotate ( YANGLE ) : '' ) ) ;
14001435 } ) ;
14011436
1402- return hoverLabels ;
1437+ return {
1438+ hoverLabels : hoverLabels ,
1439+ commonLabelBoundingBox : commonLabelRect
1440+ } ;
14031441}
14041442
14051443function getHoverLabelText ( d , showCommonLabel , hovermode , fullLayout , t0 , g ) {
@@ -1493,7 +1531,9 @@ function getHoverLabelText(d, showCommonLabel, hovermode, fullLayout, t0, g) {
14931531// know what happens if the group spans all the way from one edge to
14941532// the other, though it hardly matters - there's just too much
14951533// 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' ;
14971537 var nummoves = 0 ;
14981538 var axSign = 1 ;
14991539 var nLabels = hoverLabels . size ( ) ;
@@ -1502,23 +1542,83 @@ function hoverAvoidOverlaps(hoverLabels, axKey, fullLayout) {
15021542 var pointgroups = new Array ( nLabels ) ;
15031543 var k = 0 ;
15041544
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+
15051554 hoverLabels . each ( function ( d ) {
15061555 var ax = d [ axKey ] ;
1556+ var crossAx = d [ crossAxKey ] ;
15071557 var axIsX = ax . _id . charAt ( 0 ) === 'x' ;
15081558 var rng = ax . range ;
15091559
15101560 if ( k === 0 && rng && ( ( rng [ 0 ] > rng [ 1 ] ) !== axIsX ) ) {
15111561 axSign = - 1 ;
15121562 }
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+
15131613 pointgroups [ k ++ ] = [ {
15141614 datum : d ,
15151615 traceIndex : d . trace . index ,
15161616 dp : 0 ,
15171617 pos : d . pos ,
15181618 posref : d . posref ,
15191619 size : d . by * ( axIsX ? YFACTOR : 1 ) / 2 ,
1520- pmin : 0 ,
1521- pmax : ( axIsX ? fullLayout . width : fullLayout . height )
1620+ pmin : pmin ,
1621+ pmax : pmax
15221622 } ] ;
15231623 } ) ;
15241624
@@ -1662,6 +1762,42 @@ function hoverAvoidOverlaps(hoverLabels, axKey, fullLayout) {
16621762 }
16631763}
16641764
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+
16651801function alignHoverText ( hoverLabels , rotateLabels , scaleX , scaleY ) {
16661802 var pX = function ( x ) { return x * scaleX ; } ;
16671803 var pY = function ( y ) { return y * scaleY ; } ;
@@ -1675,21 +1811,12 @@ function alignHoverText(hoverLabels, rotateLabels, scaleX, scaleY) {
16751811 var tx = g . select ( 'text.nums' ) ;
16761812 var anchor = d . anchor ;
16771813 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 ;
16831818
16841819 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- }
16931820
16941821 g . select ( 'path' )
16951822 . attr ( 'd' , isMiddle ?
@@ -1705,7 +1832,7 @@ function alignHoverText(hoverLabels, rotateLabels, scaleX, scaleY) {
17051832 'V' + pY ( offsetY - HOVERARROWSIZE ) +
17061833 'Z' ) ) ;
17071834
1708- var posX = offsetX + txx ;
1835+ var posX = offsetX + shiftX . textShiftX ;
17091836 var posY = offsetY + d . ty0 - d . by / 2 + HOVERTEXTPAD ;
17101837 var textAlign = d . textAlign || 'auto' ;
17111838
@@ -1728,11 +1855,11 @@ function alignHoverText(hoverLabels, rotateLabels, scaleX, scaleY) {
17281855 if ( d . tx2width ) {
17291856 g . select ( 'text.name' )
17301857 . call ( svgTextUtils . positionText ,
1731- pX ( tx2x + alignShift * HOVERTEXTPAD + offsetX ) ,
1858+ pX ( shiftX . text2ShiftX + shiftX . alignShift * HOVERTEXTPAD + offsetX ) ,
17321859 pY ( offsetY + d . ty0 - d . by / 2 + HOVERTEXTPAD ) ) ;
17331860 g . select ( 'rect' )
17341861 . call ( Drawing . setRect ,
1735- pX ( tx2x + ( alignShift - 1 ) * d . tx2width / 2 + offsetX ) ,
1862+ pX ( shiftX . text2ShiftX + ( shiftX . alignShift - 1 ) * d . tx2width / 2 + offsetX ) ,
17361863 pY ( offsetY - d . by / 2 - 1 ) ,
17371864 pX ( d . tx2width ) , pY ( d . by + 2 ) ) ;
17381865 }
0 commit comments