From 35b3b5de7d4d30b26df5595399a74147f5f857da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Mon, 18 Mar 2019 15:31:04 -0400 Subject: [PATCH] loop over hover label (not hover data) when fixing overlaps ... that way when some hover data pts are filtered out (e.g. when they are too close together in compare mode), the fix-overlap algo still works. --- src/components/fx/hover.js | 22 +++++++++++------ test/jasmine/tests/hover_label_test.js | 34 ++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 7fc51ce2184..4704f89abc6 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -704,7 +704,7 @@ function _hover(gd, evt, subplot, noHoverEvent) { var hoverLabels = createHoverText(hoverData, labelOpts, gd); - hoverAvoidOverlaps(hoverData, rotateLabels ? 'xa' : 'ya', fullLayout); + hoverAvoidOverlaps(hoverLabels, rotateLabels ? 'xa' : 'ya', fullLayout); alignHoverText(hoverLabels, rotateLabels); @@ -879,6 +879,7 @@ function createHoverText(hoverData, opts, gd) { // show all the individual labels + // first create the objects var hoverLabels = container.selectAll('g.hovertext') .data(hoverData, function(d) { @@ -1091,17 +1092,21 @@ function createHoverText(hoverData, opts, gd) { // know what happens if the group spans all the way from one edge to // the other, though it hardly matters - there's just too much // information then. -function hoverAvoidOverlaps(hoverData, ax, fullLayout) { +function hoverAvoidOverlaps(hoverLabels, ax, fullLayout) { var nummoves = 0; var axSign = 1; + var nLabels = hoverLabels.size(); // make groups of touching points - var pointgroups = hoverData.map(function(d, i) { + var pointgroups = new Array(nLabels); + + hoverLabels.each(function(d, i) { var axis = d[ax]; var axIsX = axis._id.charAt(0) === 'x'; var rng = axis.range; if(!i && rng && ((rng[0] > rng[1]) !== axIsX)) axSign = -1; - return [{ + pointgroups[i] = [{ + datum: d, i: i, traceIndex: d.trace.index, dp: 0, @@ -1111,8 +1116,9 @@ function hoverAvoidOverlaps(hoverData, ax, fullLayout) { pmin: 0, pmax: (axIsX ? fullLayout.width : fullLayout.height) }]; - }) - .sort(function(a, b) { + }); + + pointgroups.sort(function(a, b) { return (a[0].posref - b[0].posref) || // for equal positions, sort trace indices increasing or decreasing // depending on whether the axis is reversed or not... so stacked @@ -1198,7 +1204,7 @@ function hoverAvoidOverlaps(hoverData, ax, fullLayout) { // loop through groups, combining them if they overlap, // until nothing moves - while(!donepositioning && nummoves <= hoverData.length) { + while(!donepositioning && nummoves <= nLabels) { // to avoid infinite loops, don't move more times // than there are traces nummoves++; @@ -1246,7 +1252,7 @@ function hoverAvoidOverlaps(hoverData, ax, fullLayout) { var grp = pointgroups[i]; for(j = grp.length - 1; j >= 0; j--) { var pt = grp[j]; - var hoverPt = hoverData[pt.i]; + var hoverPt = pt.datum; hoverPt.offset = pt.dp; hoverPt.del = pt.del; } diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index 4eabcc61a9f..446bff0cf37 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -1685,6 +1685,40 @@ describe('hover info', function() { .catch(failTest) .then(done); }); + + it('should avoid overlaps on *too close* pts are filtered out', function(done) { + Plotly.plot(gd, [ + {name: 'A', x: [9, 10], y: [9, 10]}, + {name: 'B', x: [8, 9], y: [9, 10]}, + {name: 'C', x: [9, 10], y: [10, 11]} + ], { + xaxis: {range: [0, 100]}, + yaxis: {range: [0, 100]}, + width: 700, + height: 450 + }) + .then(function() { _hover(gd, 67, 239); }) + .then(function() { + var nodesA = hoverInfoNodes('A'); + var nodesC = hoverInfoNodes('C'); + + // Ensure layout correct + assertLabelsInsideBoxes(nodesA, 'A'); + assertLabelsInsideBoxes(nodesC, 'C'); + assertSecondaryRightToPrimaryBox(nodesA, 'A'); + assertSecondaryRightToPrimaryBox(nodesC, 'C'); + + // Ensure stacking, finally + var boxA = nodesA.primaryBox.getBoundingClientRect(); + var boxC = nodesC.primaryBox.getBoundingClientRect(); + + // Be robust against floating point arithmetic and subtle future layout changes + expect(calcLineOverlap(boxA.top, boxA.bottom, boxC.top, boxC.bottom)) + .toBeWithin(0, 1); + }) + .catch(failTest) + .then(done); + }); }); describe('hovertemplate', function() {