Skip to content

compare/unified hover include all points at same coordinate #4664

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 19, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 49 additions & 2 deletions src/components/fx/hover.js
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ function _hover(gd, evt, subplot, noHoverEvent) {
// find the closest point in each trace
// this is minimum dx and/or dy, depending on mode
// and the pixel position for the label (labelXpx, labelYpx)
function findHoverPoints(vals) {
function findHoverPoints(customXVal, customYVal) {
for(curvenum = 0; curvenum < searchData.length; curvenum++) {
cd = searchData[curvenum];

Expand Down Expand Up @@ -467,6 +467,9 @@ function _hover(gd, evt, subplot, noHoverEvent) {
mode = mode ? 'closest' : 'y';
}
}
} else if(customXVal !== undefined && customYVal !== undefined) {
xval = customXVal;
yval = customYVal;
} else {
xval = xvalArray[subploti];
yval = yvalArray[subploti];
Expand Down Expand Up @@ -625,6 +628,46 @@ function _hover(gd, evt, subplot, noHoverEvent) {

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

// If in compare mode, select every point at position
if(hoverData[0].length !== 0 &&
['x', 'y'].indexOf(mode) !== -1 &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

concerning

['x', 'y'].indexOf(mode) !== -1 

creating a dynamic array of strings just for index checking might be slow.
Could we create a constant e.g.

var XY = ['x', 'y'];

at the upper scope and reuse that here?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or even var XY = {x: 1, y: 1} -> if(XY[mode]) 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in b8b9395

hoverData[0].trace.type !== 'splom' // TODO: add support for splom
) {
var hd = hoverData[0];
var cd0 = hd.cd[hd.index];
var isGrouped = (fullLayout.boxmode === 'group' || fullLayout.violinmode === 'group');

var xVal = hd.xVal;
var ax = hd.xa;
if(ax.type === 'category') xVal = ax._categoriesMap[xVal];
if(ax.type === 'date') xVal = ax.d2c(xVal);
if(cd0 && cd0.t && cd0.t.posLetter === ax._id && isGrouped) {
xVal += cd0.t.dPos;
}

var yVal = hd.yVal;
ax = hd.ya;
if(ax.type === 'category') yVal = ax._categoriesMap[yVal];
if(ax.type === 'date') yVal = ax.d2c(yVal);
if(cd0 && cd0.t && cd0.t.posLetter === ax._id && isGrouped) {
yVal += cd0.t.dPos;
}

findHoverPoints(xVal, yVal);

// Remove duplicated hoverData points
// note that d3 also filters identical points in the rendering steps
// TODO: use ES6 map
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we can make that a TODO just yet - perhaps if & when we convert the whole syntax to ES6 we can make sure we include polyfills for this and other feature, THEN we can use it whenever we wish...

var repeated = {};
hoverData = hoverData.filter(function(hd) {
var key = hoverDataKey(hd);
if(!repeated[key]) {
repeated[key] = true;
return repeated[key];
}
});
}

// lastly, emit custom hover/unhover events
var oldhoverdata = gd._hoverdata;
var newhoverdata = [];
Expand Down Expand Up @@ -703,6 +746,10 @@ function _hover(gd, evt, subplot, noHoverEvent) {
});
}

function hoverDataKey(d) {
return [d.trace.index, d.index, d.x0, d.y0, d.name, d.attr, d.xa, d.ya || ''].join(',');
}

var EXTRA_STRING_REGEX = /<extra>([\s\S]*)<\/extra>/;

function createHoverText(hoverData, opts, gd) {
Expand Down Expand Up @@ -1032,7 +1079,7 @@ function createHoverText(hoverData, opts, gd) {
.data(hoverData, function(d) {
// N.B. when multiple items have the same result key-function value,
// only the first of those items in hoverData gets rendered
return [d.trace.index, d.index, d.x0, d.y0, d.name, d.attr, d.xa, d.ya || ''].join(',');
return hoverDataKey(d);
});
hoverLabels.enter().append('g')
.classed('hovertext', true)
Expand Down
92 changes: 88 additions & 4 deletions test/jasmine/tests/hover_label_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -996,8 +996,14 @@ describe('hover info', function() {

_hover(gd, 250, 270);
assertHoverLabelContent({
nums: 'x: 1\ny: 1\nz: 5.56',
name: 'one'
nums: [
'x: 1\ny: 1\nz: 5.56',
'x: 1\ny: 1\nz: 0'
],
name: [
'one',
'two'
]
});
})
.then(function() {
Expand All @@ -1012,8 +1018,8 @@ describe('hover info', function() {

_hover(gd, 250, 270);
assertHoverLabelContent({
nums: 'f(1.0, 1.0)=5.56',
name: ''
nums: ['f(1.0, 1.0)=5.56', 'f(1.0, 1.0)=0'],
name: ['', '']
});
})
.catch(failTest)
Expand Down Expand Up @@ -3629,6 +3635,84 @@ describe('hover distance', function() {
});
});
});

describe('compare', function() {
var gd;

beforeEach(function() {
gd = createGraphDiv();
});

afterEach(destroyGraphDiv);

it('selects all points at the same position on a linear axis', function(done) {
var x = [0, 1, 2];
var mock = {
data: [{type: 'bar', x: x, y: [4, 5, 6]}, {x: x, y: [5, 6, 7]}],
layout: {width: 400, height: 400, xaxis: {type: 'linear'}}
};

Plotly.newPlot(gd, mock)
.then(function(gd) {
Fx.hover(gd, {xpx: 65});

expect(gd._hoverdata.length).toEqual(2);
})
.catch(failTest)
.then(done);
});

it('selects all points at the same position on a log axis', function(done) {
var x = [0, 1, 2];
var mock = {
data: [{type: 'bar', x: x, y: [4, 5, 6]}, {x: x, y: [5, 6, 7]}],
layout: {width: 400, height: 400, xaxis: {type: 'log'}}
};

Plotly.newPlot(gd, mock)
.then(function(gd) {
Fx.hover(gd, {xpx: 65});

expect(gd._hoverdata.length).toEqual(2);
})
.catch(failTest)
.then(done);
});

it('selects all points at the same position on a category axis', function(done) {
var x = ['a', 'b', 'c'];
var mock = {
data: [{type: 'bar', x: x, y: [4, 5, 6]}, {x: x, y: [5, 6, 7]}],
layout: {width: 400, height: 400, xaxis: {type: 'category'}}
};

Plotly.newPlot(gd, mock)
.then(function(gd) {
Fx.hover(gd, {xpx: 65});

expect(gd._hoverdata.length).toEqual(2);
})
.catch(failTest)
.then(done);
});

it('selects all points at the same position on a date axis', function(done) {
var x = ['2018', '2019', '2020'];
var mock = {
data: [{type: 'bar', x: x, y: [4, 5, 6]}, {x: x, y: [5, 6, 7]}],
layout: {width: 400, height: 400, xaxis: {type: 'date'}}
};

Plotly.newPlot(gd, mock)
.then(function(gd) {
Fx.hover(gd, {xpx: 65});

expect(gd._hoverdata.length).toEqual(2);
})
.catch(failTest)
.then(done);
});
});
});

describe('hover label rotation:', function() {
Expand Down