Skip to content

Commit 23e1c20

Browse files
committed
hover: in compare modes, select all points sharing axis position
1 parent 1de5072 commit 23e1c20

File tree

2 files changed

+137
-6
lines changed

2 files changed

+137
-6
lines changed

src/components/fx/hover.js

+49-2
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ function _hover(gd, evt, subplot, noHoverEvent) {
365365
// find the closest point in each trace
366366
// this is minimum dx and/or dy, depending on mode
367367
// and the pixel position for the label (labelXpx, labelYpx)
368-
function findHoverPoints(vals) {
368+
function findHoverPoints(customXVal, customYVal) {
369369
for(curvenum = 0; curvenum < searchData.length; curvenum++) {
370370
cd = searchData[curvenum];
371371

@@ -467,6 +467,9 @@ function _hover(gd, evt, subplot, noHoverEvent) {
467467
mode = mode ? 'closest' : 'y';
468468
}
469469
}
470+
} else if(customXVal !== undefined && customYVal !== undefined) {
471+
xval = customXVal;
472+
yval = customYVal;
470473
} else {
471474
xval = xvalArray[subploti];
472475
yval = yvalArray[subploti];
@@ -625,6 +628,46 @@ function _hover(gd, evt, subplot, noHoverEvent) {
625628

626629
hoverData.sort(function(d1, d2) { return d1.distance - d2.distance; });
627630

631+
// If in compare mode, select every point at position
632+
if(hoverData[0].length !== 0 &&
633+
['x', 'y'].indexOf(mode) !== -1 &&
634+
hoverData[0].trace.type !== 'splom' // TODO: add support for splom
635+
) {
636+
var hd = hoverData[0];
637+
var cd0 = hd.cd[hd.index];
638+
var isGrouped = (fullLayout.boxmode === 'group' || fullLayout.violinmode === 'group');
639+
640+
var xVal = hd.xVal;
641+
var ax = hd.xa;
642+
if(ax.type === 'category') xVal = ax._categoriesMap[xVal];
643+
if(ax.type === 'date') xVal = ax.d2c(xVal);
644+
if(cd0 && cd0.t && cd0.t.posLetter === ax._id && isGrouped) {
645+
xVal += cd0.t.dPos;
646+
}
647+
648+
var yVal = hd.yVal;
649+
ax = hd.ya;
650+
if(ax.type === 'category') yVal = ax._categoriesMap[yVal];
651+
if(ax.type === 'date') yVal = ax.d2c(yVal);
652+
if(cd0 && cd0.t && cd0.t.posLetter === ax._id && isGrouped) {
653+
yVal += cd0.t.dPos;
654+
}
655+
656+
findHoverPoints(xVal, yVal);
657+
658+
// Remove duplicated hoverData points
659+
// note that d3 also filters identical points in the rendering steps
660+
// TODO: use ES6 map
661+
var repeated = {};
662+
hoverData = hoverData.filter(function(hd) {
663+
var key = hoverDataKey(hd);
664+
if(!repeated[key]) {
665+
repeated[key] = true;
666+
return repeated[key];
667+
}
668+
});
669+
}
670+
628671
// lastly, emit custom hover/unhover events
629672
var oldhoverdata = gd._hoverdata;
630673
var newhoverdata = [];
@@ -703,6 +746,10 @@ function _hover(gd, evt, subplot, noHoverEvent) {
703746
});
704747
}
705748

749+
function hoverDataKey(d) {
750+
return [d.trace.index, d.index, d.x0, d.y0, d.name, d.attr, d.xa, d.ya || ''].join(',');
751+
}
752+
706753
var EXTRA_STRING_REGEX = /<extra>([\s\S]*)<\/extra>/;
707754

708755
function createHoverText(hoverData, opts, gd) {
@@ -1032,7 +1079,7 @@ function createHoverText(hoverData, opts, gd) {
10321079
.data(hoverData, function(d) {
10331080
// N.B. when multiple items have the same result key-function value,
10341081
// only the first of those items in hoverData gets rendered
1035-
return [d.trace.index, d.index, d.x0, d.y0, d.name, d.attr, d.xa, d.ya || ''].join(',');
1082+
return hoverDataKey(d);
10361083
});
10371084
hoverLabels.enter().append('g')
10381085
.classed('hovertext', true)

test/jasmine/tests/hover_label_test.js

+88-4
Original file line numberDiff line numberDiff line change
@@ -996,8 +996,14 @@ describe('hover info', function() {
996996

997997
_hover(gd, 250, 270);
998998
assertHoverLabelContent({
999-
nums: 'x: 1\ny: 1\nz: 5.56',
1000-
name: 'one'
999+
nums: [
1000+
'x: 1\ny: 1\nz: 5.56',
1001+
'x: 1\ny: 1\nz: 0'
1002+
],
1003+
name: [
1004+
'one',
1005+
'two'
1006+
]
10011007
});
10021008
})
10031009
.then(function() {
@@ -1012,8 +1018,8 @@ describe('hover info', function() {
10121018

10131019
_hover(gd, 250, 270);
10141020
assertHoverLabelContent({
1015-
nums: 'f(1.0, 1.0)=5.56',
1016-
name: ''
1021+
nums: ['f(1.0, 1.0)=5.56', 'f(1.0, 1.0)=0'],
1022+
name: ['', '']
10171023
});
10181024
})
10191025
.catch(failTest)
@@ -3629,6 +3635,84 @@ describe('hover distance', function() {
36293635
});
36303636
});
36313637
});
3638+
3639+
describe('compare', function() {
3640+
var gd;
3641+
3642+
beforeEach(function() {
3643+
gd = createGraphDiv();
3644+
});
3645+
3646+
afterEach(destroyGraphDiv);
3647+
3648+
it('selects all points at the same position on a linear axis', function(done) {
3649+
var x = [0, 1, 2];
3650+
var mock = {
3651+
data: [{type: 'bar', x: x, y: [4, 5, 6]}, {x: x, y: [5, 6, 7]}],
3652+
layout: {width: 400, height: 400, xaxis: {type: 'linear'}}
3653+
};
3654+
3655+
Plotly.newPlot(gd, mock)
3656+
.then(function(gd) {
3657+
Fx.hover(gd, {xpx: 65});
3658+
3659+
expect(gd._hoverdata.length).toEqual(2);
3660+
})
3661+
.catch(failTest)
3662+
.then(done);
3663+
});
3664+
3665+
it('selects all points at the same position on a log axis', function(done) {
3666+
var x = [0, 1, 2];
3667+
var mock = {
3668+
data: [{type: 'bar', x: x, y: [4, 5, 6]}, {x: x, y: [5, 6, 7]}],
3669+
layout: {width: 400, height: 400, xaxis: {type: 'log'}}
3670+
};
3671+
3672+
Plotly.newPlot(gd, mock)
3673+
.then(function(gd) {
3674+
Fx.hover(gd, {xpx: 65});
3675+
3676+
expect(gd._hoverdata.length).toEqual(2);
3677+
})
3678+
.catch(failTest)
3679+
.then(done);
3680+
});
3681+
3682+
it('selects all points at the same position on a category axis', function(done) {
3683+
var x = ['a', 'b', 'c'];
3684+
var mock = {
3685+
data: [{type: 'bar', x: x, y: [4, 5, 6]}, {x: x, y: [5, 6, 7]}],
3686+
layout: {width: 400, height: 400, xaxis: {type: 'category'}}
3687+
};
3688+
3689+
Plotly.newPlot(gd, mock)
3690+
.then(function(gd) {
3691+
Fx.hover(gd, {xpx: 65});
3692+
3693+
expect(gd._hoverdata.length).toEqual(2);
3694+
})
3695+
.catch(failTest)
3696+
.then(done);
3697+
});
3698+
3699+
it('selects all points at the same position on a date axis', function(done) {
3700+
var x = ['2018', '2019', '2020'];
3701+
var mock = {
3702+
data: [{type: 'bar', x: x, y: [4, 5, 6]}, {x: x, y: [5, 6, 7]}],
3703+
layout: {width: 400, height: 400, xaxis: {type: 'date'}}
3704+
};
3705+
3706+
Plotly.newPlot(gd, mock)
3707+
.then(function(gd) {
3708+
Fx.hover(gd, {xpx: 65});
3709+
3710+
expect(gd._hoverdata.length).toEqual(2);
3711+
})
3712+
.catch(failTest)
3713+
.then(done);
3714+
});
3715+
});
36323716
});
36333717

36343718
describe('hover label rotation:', function() {

0 commit comments

Comments
 (0)