Skip to content

Cache the last mousemove event #1304

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 11 commits into from
Feb 15, 2017
29 changes: 19 additions & 10 deletions src/plot_api/plot_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ Plotly.plot = function(gd, data, layout, config) {
Registry.getComponentMethod('updatemenus', 'draw')(gd);
}

Lib.syncOrAsync([
var seq = [
Plots.previousPromises,
addFrames,
drawFramework,
Expand All @@ -347,8 +347,11 @@ Plotly.plot = function(gd, data, layout, config) {
subroutines.layoutStyles,
drawAxes,
drawData,
finalDraw
], gd);
finalDraw,
Plots.rehover
];

Lib.syncOrAsync(seq, gd);

// even if everything we did was synchronous, return a promise
// so that the caller doesn't care which route we took
Expand Down Expand Up @@ -1201,8 +1204,7 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {

if(flags.fullReplot) {
seq.push(Plotly.plot);
}
else {
} else {
seq.push(Plots.previousPromises);

Plots.supplyDefaults(gd);
Expand All @@ -1211,6 +1213,8 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {
if(flags.docolorbars) seq.push(subroutines.doColorBars);
}

seq.push(Plots.rehover);

Queue.add(gd,
restyle, [gd, specs.undoit, specs.traces],
restyle, [gd, specs.redoit, specs.traces]
Expand Down Expand Up @@ -1695,9 +1699,11 @@ Plotly.relayout = function relayout(gd, astr, val) {
}

var aobj = {};
if(typeof astr === 'string') aobj[astr] = val;
else if(Lib.isPlainObject(astr)) aobj = astr;
else {
if(typeof astr === 'string') {
aobj[astr] = val;
} else if(Lib.isPlainObject(astr)) {
aobj = astr;
} else {
Lib.warn('Relayout fail.', astr, val);
return Promise.reject();
}
Expand All @@ -1715,8 +1721,7 @@ Plotly.relayout = function relayout(gd, astr, val) {

if(flags.layoutReplot) {
seq.push(subroutines.layoutReplot);
}
else if(Object.keys(aobj).length) {
} else if(Object.keys(aobj).length) {
seq.push(Plots.previousPromises);
Plots.supplyDefaults(gd);

Expand All @@ -1727,6 +1732,8 @@ Plotly.relayout = function relayout(gd, astr, val) {
if(flags.docamera) seq.push(subroutines.doCamera);
}

seq.push(Plots.rehover);

Queue.add(gd,
relayout, [gd, specs.undoit],
relayout, [gd, specs.redoit]
Expand Down Expand Up @@ -2127,6 +2134,8 @@ Plotly.update = function update(gd, traceUpdate, layoutUpdate, traces) {
if(relayoutFlags.doCamera) seq.push(subroutines.doCamera);
}

seq.push(Plots.rehover);

Queue.add(gd,
update, [gd, restyleSpecs.undoit, relayoutSpecs.undoit, restyleSpecs.traces],
update, [gd, restyleSpecs.redoit, relayoutSpecs.redoit, restyleSpecs.traces]
Expand Down
20 changes: 18 additions & 2 deletions src/plots/cartesian/graph_interact.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,20 @@ fx.init = function(gd) {
xa._length, ya._length, 'ns', 'ew');

maindrag.onmousemove = function(evt) {
// This is on `gd._fullLayout`, *not* fullLayout because the reference
// changes by the time this is called again.
gd._fullLayout._rehover = function() {
if(gd._fullLayout._hoversubplot === subplot) {
fx.hover(gd, evt, subplot);
}
};

fx.hover(gd, evt, subplot);
fullLayout._lasthover = maindrag;
fullLayout._hoversubplot = subplot;

// Note that we have *not* used the cached fullLayout variable here
// since that may be outdated when this is called as a callback later on
gd._fullLayout._lasthover = maindrag;
gd._fullLayout._hoversubplot = subplot;
};

/*
Expand All @@ -129,6 +140,11 @@ fx.init = function(gd) {
maindrag.onmouseout = function(evt) {
if(gd._dragging) return;

// When the mouse leaves this maindrag, unset the hovered subplot.
// This may cause problems if it leaves the subplot directly *onto*
// another subplot, but that's a tiny corner case at the moment.
gd._fullLayout._hoversubplot = null;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Inset plots seem like a not-so-tiny corner case. But does this actually cause problems? Presumably mouseout happens before mouseover in that case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, which happens first was the question. If mouseover and then mouseout, it would end up unset, but the only time that could happen is if you move exactly one pixel outside of the inset region, which is where it started to seem like a small corner case. The second pixel moved will immediately fix things.


dragElement.unhover(gd, evt);
};

Expand Down
8 changes: 7 additions & 1 deletion src/plots/plots.js
Original file line number Diff line number Diff line change
Expand Up @@ -1934,7 +1934,7 @@ plots.transition = function(gd, data, layout, traces, frameOpts, transitionOpts)
}
}

var seq = [plots.previousPromises, interruptPreviousTransitions, prepareTransitions, executeTransitions];
var seq = [plots.previousPromises, interruptPreviousTransitions, prepareTransitions, plots.rehover, executeTransitions];

var transitionStarting = Lib.syncOrAsync(seq, gd);

Expand Down Expand Up @@ -2057,6 +2057,12 @@ plots.doCalcdata = function(gd, traces) {
}
};

plots.rehover = function(gd) {
if(gd._fullLayout._rehover) {
gd._fullLayout._rehover();
}
};

plots.generalUpdatePerTraceModule = function(subplot, subplotCalcData, subplotLayout) {
var traceHashOld = subplot.traceHash,
traceHash = {},
Expand Down
76 changes: 76 additions & 0 deletions test/jasmine/tests/hover_label_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ var destroyGraphDiv = require('../assets/destroy_graph_div');
var mouseEvent = require('../assets/mouse_event');
var click = require('../assets/click');
var doubleClick = require('../assets/double_click');
var fail = require('../assets/fail_test');

describe('hover info', function() {
'use strict';
Expand Down Expand Up @@ -811,3 +812,78 @@ describe('hover on fill', function() {
}).then(done);
});
});

describe('hover updates', function() {
'use strict';

afterEach(destroyGraphDiv);

function assertLabelsCorrect(mousePos, labelPos, labelText) {
return new Promise(function(resolve) {
if(mousePos) {
mouseEvent('mousemove', mousePos[0], mousePos[1]);
}

setTimeout(function() {
var hoverText = d3.selectAll('g.hovertext');
if(labelPos) {
expect(hoverText.size()).toEqual(1);
expect(hoverText.text()).toEqual(labelText);

var transformParts = hoverText.attr('transform').split('(');
expect(transformParts[0]).toEqual('translate');
var transformCoords = transformParts[1].split(')')[0].split(',');
expect(+transformCoords[0]).toBeCloseTo(labelPos[0], -1, labelText + ':x');
expect(+transformCoords[1]).toBeCloseTo(labelPos[1], -1, labelText + ':y');
} else {
expect(hoverText.size()).toEqual(0);
}

resolve();
}, constants.HOVERMINTIME);
});
}

it('should update the labels on animation', function(done) {
var mock = {
data: [
{x: [0.5], y: [0.5], showlegend: false},
{x: [0], y: [0], showlegend: false},
],
layout: {
margin: {t: 0, r: 0, b: 0, l: 0},
width: 200,
height: 200,
xaxis: {range: [0, 1]},
yaxis: {range: [0, 1]},
}
};

var gd = createGraphDiv();
Plotly.plot(gd, mock).then(function() {
// The label text gets concatenated together when queried. Such is life.
return assertLabelsCorrect([100, 100], [103, 100], 'trace 00.5');
}).then(function() {
return Plotly.animate(gd, [{
data: [{x: [0], y: [0]}, {x: [0.5], y: [0.5]}],
traces: [0, 1],
}], {frame: {redraw: false, duration: 0}});
}).then(function() {
// No mouse event this time. Just change the data and check the label.
// Ditto on concatenation. This is "trace 1" + "0.5"
return assertLabelsCorrect(null, [103, 100], 'trace 10.5');
}).then(function() {
// Restyle to move the point out of the window:
return Plotly.relayout(gd, {'xaxis.range': [2, 3]});
}).then(function() {
// Assert label removed:
return assertLabelsCorrect(null, null);
}).then(function() {
// Move back to the original xaxis range:
return Plotly.relayout(gd, {'xaxis.range': [0, 1]});
}).then(function() {
// Assert label restored:
return assertLabelsCorrect(null, [103, 100], 'trace 10.5');
}).catch(fail).then(done);
});
});