Skip to content

Commit 0588483

Browse files
authored
Merge pull request #3645 from plotly/hover-label-overlap-filtered-out
Fix avoid-overlap hover label algo when "too close" label get filtered out
2 parents fc236e2 + 35b3b5d commit 0588483

File tree

2 files changed

+48
-8
lines changed

2 files changed

+48
-8
lines changed

src/components/fx/hover.js

+14-8
Original file line numberDiff line numberDiff line change
@@ -704,7 +704,7 @@ function _hover(gd, evt, subplot, noHoverEvent) {
704704

705705
var hoverLabels = createHoverText(hoverData, labelOpts, gd);
706706

707-
hoverAvoidOverlaps(hoverData, rotateLabels ? 'xa' : 'ya', fullLayout);
707+
hoverAvoidOverlaps(hoverLabels, rotateLabels ? 'xa' : 'ya', fullLayout);
708708

709709
alignHoverText(hoverLabels, rotateLabels);
710710

@@ -879,6 +879,7 @@ function createHoverText(hoverData, opts, gd) {
879879

880880
// show all the individual labels
881881

882+
882883
// first create the objects
883884
var hoverLabels = container.selectAll('g.hovertext')
884885
.data(hoverData, function(d) {
@@ -1091,17 +1092,21 @@ function createHoverText(hoverData, opts, gd) {
10911092
// know what happens if the group spans all the way from one edge to
10921093
// the other, though it hardly matters - there's just too much
10931094
// information then.
1094-
function hoverAvoidOverlaps(hoverData, ax, fullLayout) {
1095+
function hoverAvoidOverlaps(hoverLabels, ax, fullLayout) {
10951096
var nummoves = 0;
10961097
var axSign = 1;
1098+
var nLabels = hoverLabels.size();
10971099

10981100
// make groups of touching points
1099-
var pointgroups = hoverData.map(function(d, i) {
1101+
var pointgroups = new Array(nLabels);
1102+
1103+
hoverLabels.each(function(d, i) {
11001104
var axis = d[ax];
11011105
var axIsX = axis._id.charAt(0) === 'x';
11021106
var rng = axis.range;
11031107
if(!i && rng && ((rng[0] > rng[1]) !== axIsX)) axSign = -1;
1104-
return [{
1108+
pointgroups[i] = [{
1109+
datum: d,
11051110
i: i,
11061111
traceIndex: d.trace.index,
11071112
dp: 0,
@@ -1111,8 +1116,9 @@ function hoverAvoidOverlaps(hoverData, ax, fullLayout) {
11111116
pmin: 0,
11121117
pmax: (axIsX ? fullLayout.width : fullLayout.height)
11131118
}];
1114-
})
1115-
.sort(function(a, b) {
1119+
});
1120+
1121+
pointgroups.sort(function(a, b) {
11161122
return (a[0].posref - b[0].posref) ||
11171123
// for equal positions, sort trace indices increasing or decreasing
11181124
// depending on whether the axis is reversed or not... so stacked
@@ -1198,7 +1204,7 @@ function hoverAvoidOverlaps(hoverData, ax, fullLayout) {
11981204

11991205
// loop through groups, combining them if they overlap,
12001206
// until nothing moves
1201-
while(!donepositioning && nummoves <= hoverData.length) {
1207+
while(!donepositioning && nummoves <= nLabels) {
12021208
// to avoid infinite loops, don't move more times
12031209
// than there are traces
12041210
nummoves++;
@@ -1246,7 +1252,7 @@ function hoverAvoidOverlaps(hoverData, ax, fullLayout) {
12461252
var grp = pointgroups[i];
12471253
for(j = grp.length - 1; j >= 0; j--) {
12481254
var pt = grp[j];
1249-
var hoverPt = hoverData[pt.i];
1255+
var hoverPt = pt.datum;
12501256
hoverPt.offset = pt.dp;
12511257
hoverPt.del = pt.del;
12521258
}

test/jasmine/tests/hover_label_test.js

+34
Original file line numberDiff line numberDiff line change
@@ -1685,6 +1685,40 @@ describe('hover info', function() {
16851685
.catch(failTest)
16861686
.then(done);
16871687
});
1688+
1689+
it('should avoid overlaps on *too close* pts are filtered out', function(done) {
1690+
Plotly.plot(gd, [
1691+
{name: 'A', x: [9, 10], y: [9, 10]},
1692+
{name: 'B', x: [8, 9], y: [9, 10]},
1693+
{name: 'C', x: [9, 10], y: [10, 11]}
1694+
], {
1695+
xaxis: {range: [0, 100]},
1696+
yaxis: {range: [0, 100]},
1697+
width: 700,
1698+
height: 450
1699+
})
1700+
.then(function() { _hover(gd, 67, 239); })
1701+
.then(function() {
1702+
var nodesA = hoverInfoNodes('A');
1703+
var nodesC = hoverInfoNodes('C');
1704+
1705+
// Ensure layout correct
1706+
assertLabelsInsideBoxes(nodesA, 'A');
1707+
assertLabelsInsideBoxes(nodesC, 'C');
1708+
assertSecondaryRightToPrimaryBox(nodesA, 'A');
1709+
assertSecondaryRightToPrimaryBox(nodesC, 'C');
1710+
1711+
// Ensure stacking, finally
1712+
var boxA = nodesA.primaryBox.getBoundingClientRect();
1713+
var boxC = nodesC.primaryBox.getBoundingClientRect();
1714+
1715+
// Be robust against floating point arithmetic and subtle future layout changes
1716+
expect(calcLineOverlap(boxA.top, boxA.bottom, boxC.top, boxC.bottom))
1717+
.toBeWithin(0, 1);
1718+
})
1719+
.catch(failTest)
1720+
.then(done);
1721+
});
16881722
});
16891723

16901724
describe('hovertemplate', function() {

0 commit comments

Comments
 (0)