@@ -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