Skip to content

Commit 2fde3dc

Browse files
committed
better ordering of trace hoverlabels for matching positions
1 parent 4e71dfa commit 2fde3dc

File tree

3 files changed

+115
-5
lines changed

3 files changed

+115
-5
lines changed

src/components/fx/hover.js

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -951,21 +951,32 @@ function createHoverText(hoverData, opts, gd) {
951951
function hoverAvoidOverlaps(hoverData, ax, fullLayout) {
952952
var nummoves = 0;
953953

954-
// make groups of touching points
954+
var axSign = 1;
955+
956+
// make groups of touching points
955957
var pointgroups = hoverData.map(function(d, i) {
956958
var axis = d[ax];
959+
var axIsX = axis._id.charAt(0) === 'x';
960+
var rng = axis.range;
961+
if(!i && rng && ((rng[0] > rng[1]) !== axIsX)) axSign = -1;
957962
return [{
958963
i: i,
964+
traceIndex: d.trace.index,
959965
dp: 0,
960966
pos: d.pos,
961967
posref: d.posref,
962-
size: d.by * (axis._id.charAt(0) === 'x' ? YFACTOR : 1) / 2,
968+
size: d.by * (axIsX ? YFACTOR : 1) / 2,
963969
pmin: 0,
964-
pmax: (axis._id.charAt(0) === 'x' ? fullLayout.width : fullLayout.height)
970+
pmax: (axIsX ? fullLayout.width : fullLayout.height)
965971
}];
966972
})
967973
.sort(function(a, b) {
968-
return a[0].posref - b[0].posref;
974+
return (a[0].posref - b[0].posref) ||
975+
// for equal positions, sort trace indices increasing or decreasing
976+
// depending on whether the axis is reversed or not... so stacked
977+
// traces will generally keep their order even if one trace adds
978+
// nothing to the stack.
979+
(axSign * (b[0].traceIndex - a[0].traceIndex));
969980
});
970981

971982
var donepositioning, topOverlap, bottomOverlap, i, j, pti, sumdp;

test/jasmine/assets/custom_assertions.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ exports.assertHoverLabelContent = function(expectation, msg) {
122122
expect(ptCnt)
123123
.toBe(expectation.name.length, ptMsg + ' # of visible labels');
124124

125+
var bboxes = [];
125126
d3.selectAll(ptSelector).each(function(_, i) {
126127
assertLabelContent(
127128
d3.select(this).select('text.nums'),
@@ -133,7 +134,20 @@ exports.assertHoverLabelContent = function(expectation, msg) {
133134
expectation.name[i],
134135
ptMsg + ' (name ' + i + ')'
135136
);
137+
bboxes.push({bbox: this.getBoundingClientRect(), index: i});
136138
});
139+
if(expectation.vOrder) {
140+
bboxes.sort(function(a, b) {
141+
return (a.bbox.top + a.bbox.bottom - b.bbox.top - b.bbox.bottom) / 2;
142+
});
143+
expect(bboxes.map(function(d) { return d.index; })).toEqual(expectation.vOrder);
144+
}
145+
if(expectation.hOrder) {
146+
bboxes.sort(function(a, b) {
147+
return (b.bbox.left + b.bbox.right - a.bbox.left - a.bbox.right) / 2;
148+
});
149+
expect(bboxes.map(function(d) { return d.index; })).toEqual(expectation.hOrder);
150+
}
137151
} else {
138152
if(expectation.nums) {
139153
fail(ptMsg + ': expecting *nums* labels, did not find any.');

test/jasmine/tests/hover_label_test.js

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,92 @@ describe('hover info', function() {
520520
Lib.clearThrottle();
521521
}
522522

523-
describe('\'hover info for x/y/z traces', function() {
523+
describe('hover label order for stacked traces with zeros', function() {
524+
var gd;
525+
beforeEach(function() {
526+
gd = createGraphDiv();
527+
});
528+
529+
it('puts the top trace on top', function(done) {
530+
Plotly.plot(gd, [
531+
{y: [1, 2, 3], type: 'bar', name: 'a'},
532+
{y: [2, 0, 1], type: 'bar', name: 'b'},
533+
{y: [1, 0, 1], type: 'bar', name: 'c'},
534+
{y: [2, 1, 0], type: 'bar', name: 'd'}
535+
], {
536+
width: 500,
537+
height: 400,
538+
margin: {l: 0, t: 0, r: 0, b: 0},
539+
barmode: 'stack'
540+
})
541+
.then(function() {
542+
_hover(gd, 250, 250);
543+
assertHoverLabelContent({
544+
nums: ['2', '0', '0', '1'],
545+
name: ['a', 'b', 'c', 'd'],
546+
// a, b, c are all in the same place but keep their order
547+
// d is included mostly as a sanity check
548+
vOrder: [3, 2, 1, 0],
549+
axis: '1'
550+
});
551+
552+
// reverse the axis, labels should reverse
553+
return Plotly.relayout(gd, 'yaxis.range', gd.layout.yaxis.range.slice().reverse());
554+
})
555+
.then(function() {
556+
_hover(gd, 250, 250);
557+
assertHoverLabelContent({
558+
nums: ['2', '0', '0', '1'],
559+
name: ['a', 'b', 'c', 'd'],
560+
vOrder: [0, 1, 2, 3],
561+
axis: '1'
562+
});
563+
})
564+
.catch(failTest)
565+
.then(done);
566+
});
567+
568+
it('puts the right trace on the right', function(done) {
569+
Plotly.plot(gd, [
570+
{x: [1, 2, 3], type: 'bar', name: 'a', orientation: 'h'},
571+
{x: [2, 0, 1], type: 'bar', name: 'b', orientation: 'h'},
572+
{x: [1, 0, 1], type: 'bar', name: 'c', orientation: 'h'},
573+
{x: [2, 1, 0], type: 'bar', name: 'd', orientation: 'h'}
574+
], {
575+
width: 500,
576+
height: 400,
577+
margin: {l: 0, t: 0, r: 0, b: 0},
578+
barmode: 'stack'
579+
})
580+
.then(function() {
581+
_hover(gd, 250, 250);
582+
assertHoverLabelContent({
583+
nums: ['2', '0', '0', '1'],
584+
name: ['a', 'b', 'c', 'd'],
585+
// a, b, c are all in the same place but keep their order
586+
// d is included mostly as a sanity check
587+
hOrder: [3, 2, 1, 0],
588+
axis: '1'
589+
});
590+
591+
// reverse the axis, labels should reverse
592+
return Plotly.relayout(gd, 'xaxis.range', gd.layout.xaxis.range.slice().reverse());
593+
})
594+
.then(function() {
595+
_hover(gd, 250, 250);
596+
assertHoverLabelContent({
597+
nums: ['2', '0', '0', '1'],
598+
name: ['a', 'b', 'c', 'd'],
599+
hOrder: [0, 1, 2, 3],
600+
axis: '1'
601+
});
602+
})
603+
.catch(failTest)
604+
.then(done);
605+
});
606+
});
607+
608+
describe('hover info for x/y/z traces', function() {
524609
var gd;
525610
beforeEach(function() {
526611
gd = createGraphDiv();

0 commit comments

Comments
 (0)