Skip to content

Commit 68521df

Browse files
committed
constraint common hovermode:'x' hover labels into graph viewport
... and place hover label arrow to left or right of text box.
1 parent 41899b1 commit 68521df

File tree

2 files changed

+110
-19
lines changed

2 files changed

+110
-19
lines changed

src/components/fx/hover.js

+58-19
Original file line numberDiff line numberDiff line change
@@ -790,41 +790,80 @@ function createHoverText(hoverData, opts, gd) {
790790
label.attr('transform', '');
791791

792792
var tbb = ltext.node().getBoundingClientRect();
793+
var lx, ly;
794+
793795
if(hovermode === 'x') {
796+
var topsign = xa.side === 'top' ? '-' : '';
797+
794798
ltext.attr('text-anchor', 'middle')
795799
.call(svgTextUtils.positionText, 0, (xa.side === 'top' ?
796800
(outerTop - tbb.bottom - HOVERARROWSIZE - HOVERTEXTPAD) :
797801
(outerTop - tbb.top + HOVERARROWSIZE + HOVERTEXTPAD)));
798802

799-
var topsign = xa.side === 'top' ? '-' : '';
800-
lpath.attr('d', 'M0,0' +
801-
'L' + HOVERARROWSIZE + ',' + topsign + HOVERARROWSIZE +
802-
'H' + (HOVERTEXTPAD + tbb.width / 2) +
803-
'v' + topsign + (HOVERTEXTPAD * 2 + tbb.height) +
804-
'H-' + (HOVERTEXTPAD + tbb.width / 2) +
805-
'V' + topsign + HOVERARROWSIZE + 'H-' + HOVERARROWSIZE + 'Z');
806-
807-
label.attr('transform', 'translate(' +
808-
(xa._offset + (c0.x0 + c0.x1) / 2) + ',' +
809-
(ya._offset + (xa.side === 'top' ? 0 : ya._length)) + ')');
803+
lx = xa._offset + (c0.x0 + c0.x1) / 2;
804+
ly = ya._offset + (xa.side === 'top' ? 0 : ya._length);
805+
806+
var halfWidth = tbb.width / 2 + HOVERTEXTPAD;
807+
808+
if(lx < halfWidth) {
809+
lx = halfWidth;
810+
811+
lpath.attr('d', 'M-' + (halfWidth - HOVERARROWSIZE) + ',0' +
812+
'L-' + (halfWidth - HOVERARROWSIZE * 2) + ',' + topsign + HOVERARROWSIZE +
813+
'H' + (HOVERTEXTPAD + tbb.width / 2) +
814+
'v' + topsign + (HOVERTEXTPAD * 2 + tbb.height) +
815+
'H-' + halfWidth +
816+
'V' + topsign + HOVERARROWSIZE +
817+
'Z');
818+
} else if(lx > (fullLayout.width - halfWidth)) {
819+
lx = fullLayout.width - halfWidth;
820+
821+
lpath.attr('d', 'M' + (halfWidth - HOVERARROWSIZE) + ',0' +
822+
'L' + halfWidth + ',' + topsign + HOVERARROWSIZE +
823+
'v' + topsign + (HOVERTEXTPAD * 2 + tbb.height) +
824+
'H-' + halfWidth +
825+
'V' + topsign + HOVERARROWSIZE +
826+
'H' + (halfWidth - HOVERARROWSIZE * 2) + 'Z');
827+
} else {
828+
lpath.attr('d', 'M0,0' +
829+
'L' + HOVERARROWSIZE + ',' + topsign + HOVERARROWSIZE +
830+
'H' + (HOVERTEXTPAD + tbb.width / 2) +
831+
'v' + topsign + (HOVERTEXTPAD * 2 + tbb.height) +
832+
'H-' + (HOVERTEXTPAD + tbb.width / 2) +
833+
'V' + topsign + HOVERARROWSIZE +
834+
'H-' + HOVERARROWSIZE + 'Z');
835+
}
810836
} else {
811-
ltext.attr('text-anchor', ya.side === 'right' ? 'start' : 'end')
812-
.call(svgTextUtils.positionText,
813-
(ya.side === 'right' ? 1 : -1) * (HOVERTEXTPAD + HOVERARROWSIZE),
814-
outerTop - tbb.top - tbb.height / 2);
837+
var anchor;
838+
var sgn;
839+
var leftsign;
840+
if(ya.side === 'right') {
841+
anchor = 'start';
842+
sgn = 1;
843+
leftsign = '';
844+
lx = xa._offset + xa._length;
845+
} else {
846+
anchor = 'end';
847+
sgn = -1;
848+
leftsign = '-';
849+
lx = xa._offset;
850+
}
851+
852+
ly = ya._offset + (c0.y0 + c0.y1) / 2;
853+
854+
ltext.attr('text-anchor', anchor);
815855

816-
var leftsign = ya.side === 'right' ? '' : '-';
817856
lpath.attr('d', 'M0,0' +
818857
'L' + leftsign + HOVERARROWSIZE + ',' + HOVERARROWSIZE +
819858
'V' + (HOVERTEXTPAD + tbb.height / 2) +
820859
'h' + leftsign + (HOVERTEXTPAD * 2 + tbb.width) +
821860
'V-' + (HOVERTEXTPAD + tbb.height / 2) +
822861
'H' + leftsign + HOVERARROWSIZE + 'V-' + HOVERARROWSIZE + 'Z');
823862

824-
label.attr('transform', 'translate(' +
825-
(xa._offset + (ya.side === 'right' ? xa._length : 0)) + ',' +
826-
(ya._offset + (c0.y0 + c0.y1) / 2) + ')');
827863
}
864+
865+
label.attr('transform', 'translate(' + lx + ',' + ly + ')');
866+
828867
// remove the "close but not quite" points
829868
// because of error bars, only take up to a space
830869
hoverData = hoverData.filter(function(d) {

test/jasmine/tests/hover_label_test.js

+52
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ var d3 = require('d3');
33
var Plotly = require('@lib/index');
44
var Fx = require('@src/components/fx');
55
var Lib = require('@src/lib');
6+
var Drawing = require('@src/components/drawing');
7+
68
var HOVERMINTIME = require('@src/components/fx').constants.HOVERMINTIME;
79
var MINUS_SIGN = require('@src/constants/numerical').MINUS_SIGN;
810

@@ -1700,6 +1702,56 @@ describe('hover info', function() {
17001702
});
17011703
});
17021704

1705+
describe('constraints info graph viewport', function() {
1706+
var gd;
1707+
1708+
beforeEach(function() { gd = createGraphDiv(); });
1709+
1710+
it('hovermode:x common label should fit in the graph div width', function(done) {
1711+
function _assert(msg, exp) {
1712+
return function() {
1713+
var label = d3.select('g.axistext');
1714+
if(label.node()) {
1715+
expect(label.text()).toBe(exp.txt, 'common label text| ' + msg);
1716+
expect(Drawing.getTranslate(label).x)
1717+
.toBeWithin(exp.lx, 5, 'common label translate-x| ' + msg);
1718+
1719+
var startOfPath = label.select('path').attr('d').split('L')[0];
1720+
expect(startOfPath).not.toBe('M0,0', 'offset start of label path| ' + msg);
1721+
} else {
1722+
fail('fail to generate common hover label');
1723+
}
1724+
};
1725+
}
1726+
1727+
function _hoverLeft() { return _hover(gd, 30, 300); }
1728+
1729+
function _hoverRight() { return _hover(gd, 370, 300); }
1730+
1731+
Plotly.plot(gd, [{
1732+
type: 'bar',
1733+
x: ['2019-01-01', '2019-06-01', '2020-01-01'],
1734+
y: [2, 5, 10]
1735+
}], {
1736+
xaxis: {range: ['2019-02-06', '2019-12-01']},
1737+
margin: {l: 0, r: 0},
1738+
width: 400,
1739+
height: 400
1740+
})
1741+
.then(_hoverLeft)
1742+
.then(_assert('left-edge hover', {txt: 'Jan 1, 2019', lx: 37}))
1743+
.then(_hoverRight)
1744+
.then(_assert('right-edge hover', {txt: 'Jan 1, 2020', lx: 362}))
1745+
.then(function() { return Plotly.relayout(gd, 'xaxis.side', 'top'); })
1746+
.then(_hoverLeft)
1747+
.then(_assert('left-edge hover (side:top)', {txt: 'Jan 1, 2019', lx: 37}))
1748+
.then(_hoverRight)
1749+
.then(_assert('right-edge hover (side:top)', {txt: 'Jan 1, 2020', lx: 362}))
1750+
.catch(failTest)
1751+
.then(done);
1752+
});
1753+
});
1754+
17031755
describe('hovertemplate', function() {
17041756
var mockCopy;
17051757

0 commit comments

Comments
 (0)