Skip to content

Generalize hover picking routine #575

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 6 commits into from
May 31, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
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
58 changes: 38 additions & 20 deletions src/plots/cartesian/graph_interact.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,27 +310,45 @@ function hover(gd, evt, subplot) {

if(!subplot) subplot = 'xy';

// if the user passed in an array of subplots,
// use those instead of finding overlayed plots
var subplots = Array.isArray(subplot) ? subplot : [subplot];

var fullLayout = gd._fullLayout,
plotinfo = fullLayout._plots[subplot],

//If the user passed in an array of subplots, use those instead of finding overlayed plots
subplots = Array.isArray(subplot) ?
subplot :
// list of all overlaid subplots to look at
[subplot].concat(plotinfo.overlays
.map(function(pi) { return pi.id; })),

xaArray = subplots.map(function(spId) {
var ternary = (gd._fullLayout[spId] || {})._ternary;
if(ternary) return ternary.xaxis;
return Axes.getFromId(gd, spId, 'x');
}),
yaArray = subplots.map(function(spId) {
var ternary = (gd._fullLayout[spId] || {})._ternary;
if(ternary) return ternary.yaxis;
return Axes.getFromId(gd, spId, 'y');
}),
hovermode = evt.hovermode || fullLayout.hovermode;
plots = fullLayout._plots || [],
Copy link
Contributor Author

Choose a reason for hiding this comment

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

IMPORTANT keep fullLayout._plots for cartesian subplots (and gl2d although I'm having second doubt about this) only.

Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need to keep them separate? In my mind, this seems like it will just introduce more surface area for inconsistent state bugs.

Copy link
Contributor Author

@etpinard etpinard May 30, 2016

Choose a reason for hiding this comment

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

Mostly for convenience.

Most of our per-subplot code post-supply-defaults is designed as:

  1. loop over all subplots of a given type found fullLayout
  2. create a subplot object or update an existing subplot object in fullLayout[/* name of subplot */]._subplot

So, it becomes convenient to store a ref to the subplot object in its corresponding fullLayout attribute container.

Moreover, as finding all xy subplots in a given figure is somewhat expensive (see Axes.getSubplots), it becomes advantages to look for them in fullLayout._plots directly instead of calling Axes.getSubplots every time we need to loop over all the x/y subplots.

plotinfo = plots[subplot];

// list of all overlaid subplots to look at
if(plotinfo) {
var overlayedSubplots = plotinfo.overlays.map(function(pi) {
return pi.id;
});

subplots = subplots.concat(overlayedSubplots);
}

var len = subplots.length,
xaArray = new Array(len),
yaArray = new Array(len);

for(var i = 0; i < len; i++) {
var spId = subplots[i];

// 'cartesian' case
var plotObj = plots[spId];
Copy link
Contributor Author

@etpinard etpinard May 27, 2016

Choose a reason for hiding this comment

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

look in:

  • fullLayout._plots[/* subplot id */] then in
  • fullLayout[/* subplot id */]._subplot

if(plotObj) {
xaArray[i] = plotObj.xaxis;
yaArray[i] = plotObj.yaxis;
continue;
}

// other subplot types
var _subplot = fullLayout[spId]._subplot;
xaArray[i] = _subplot.xaxis;
Copy link
Contributor Author

@etpinard etpinard May 27, 2016

Choose a reason for hiding this comment

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

Mocking xaxis and yaxis for hover is pretty easy and also pretty convenient for hover, as the x/y axes are parallel to the pixel coordinate axes.

yaArray[i] = _subplot.yaxis;
}

var hovermode = evt.hovermode || fullLayout.hovermode;

if(['x', 'y', 'closest'].indexOf(hovermode) === -1 || !gd.calcdata ||
gd.querySelector('.zoombox') || gd._dragging) {
Expand Down
6 changes: 3 additions & 3 deletions src/plots/ternary/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ exports.plot = function plotTernary(gd) {
for(var i = 0; i < ternaryIds.length; i++) {
var ternaryId = ternaryIds[i],
fullTernaryData = Plots.getSubplotData(fullData, 'ternary', ternaryId),
ternary = fullLayout[ternaryId]._ternary;
ternary = fullLayout[ternaryId]._subplot;

// If ternary is not instantiated, create one!
if(ternary === undefined) {
Expand All @@ -50,7 +50,7 @@ exports.plot = function plotTernary(gd) {
fullLayout
);

fullLayout[ternaryId]._ternary = ternary;
fullLayout[ternaryId]._subplot = ternary;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

One step to generalize how we store instances to subplot instance in fullLayout.

To note, I haven't changed all the fullLayout.scene._scene and fullLayout.geo._geo occurrences as these refs may have leaked in the wild. We should do so in v2.0.0 though.

}

ternary.plot(fullTernaryData, fullLayout, gd._promises);
Expand All @@ -62,7 +62,7 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout)

for(var i = 0; i < oldTernaryKeys.length; i++) {
var oldTernaryKey = oldTernaryKeys[i];
var oldTernary = oldFullLayout[oldTernaryKey]._ternary;
var oldTernary = oldFullLayout[oldTernaryKey]._subplot;

if(!newFullLayout[oldTernaryKey] && !!oldTernary) {
oldTernary.plotContainer.remove();
Expand Down
13 changes: 0 additions & 13 deletions src/plots/ternary/ternary.js
Original file line number Diff line number Diff line change
Expand Up @@ -675,19 +675,6 @@ proto.initInteractions = function() {
dragger.onclick = function(evt) {
fx.click(gd, evt);
};

// make a fake plotinfo for fx.hover
// it hardly uses it, could probably be refactored out...
// but specifying subplot by name does seem nice for js applications
// that want to hook into this.
if(!gd._fullLayout._plots) gd._fullLayout._plots = {};
gd._fullLayout._plots[_this.id] = {
overlays: [],
xaxis: _this.xaxis,
yaxis: _this.yaxis,
x: function() { return _this.xaxis; },
y: function() { return _this.yaxis; }
};
};

function removeZoombox(gd) {
Expand Down
7 changes: 6 additions & 1 deletion src/traces/scatterternary/plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,16 @@ var scatterPlot = require('../scatter/plot');


module.exports = function plot(ternary, data) {
var plotContainer = ternary.plotContainer;

// remove all nodes inside the scatter layer
plotContainer.select('.scatterlayer').selectAll('*').remove();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This new line may look bad, but it's way better than previously.

Commit 39b8acf had a nasty side effect that took me quite some time to debug.

Previously, the ternary code stored a ref to a mock cartesian subplot object in fullLayout._plots which led to full framework redraws on every restyle. This was caused by this block where oldSubplot was equal to ternary and the new subplot was equal to '' as Axes.getSubplots only catches cartesian subplots. So every restyle call on ternary plots went through the slow makePlotFramework routine.

We could definitely do better than removing all the inner nodes, but keeping the ordering of lines, markers and text nodes is tricky. At least now, scatterternary traces are on par with scattergeo.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure how d3 manages selections internally, but I'd guess <selection>.html('') would be faster than selectAll('*').remove()?

Copy link
Contributor Author

@etpinard etpinard May 30, 2016

Choose a reason for hiding this comment

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

I remember using html('') at some point, but switch to selectAll('*').remove() because it wasn't working properly in IE.


// mimic cartesian plotinfo
var plotinfo = {
x: function() { return ternary.xaxis; },
y: function() { return ternary.yaxis; },
plot: ternary.plotContainer
plot: plotContainer
};

var calcdata = new Array(data.length),
Expand Down
31 changes: 31 additions & 0 deletions test/jasmine/tests/hover_label_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -574,3 +574,34 @@ describe('hover info on stacked subplots', function() {
});
});
});


describe('hover info on overlaid subplots', function() {
'use strict';

afterEach(destroyGraphDiv);

it('should respond to hover', function(done) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

might as well test this non-trivial logic in Fx.hover.

var mock = require('@mocks/autorange-tozero-rangemode.json');

Plotly.plot(createGraphDiv(), mock.data, mock.layout).then(function() {
mouseEvent('mousemove', 775, 352);

var axisText = d3.selectAll('g.axistext'),
hoverText = d3.selectAll('g.hovertext');

expect(axisText.size()).toEqual(1, 'with 1 label on axis');
expect(hoverText.size()).toEqual(2, 'with 2 labels on the overlaid pts');

expect(axisText.select('text').html()).toEqual('1', 'with correct axis label');

var textNodes = hoverText.selectAll('text');

expect(textNodes[0][0].innerHTML).toEqual('Take Rate', 'with correct hover labels');
expect(textNodes[0][1].innerHTML).toEqual('0.35', 'with correct hover labels');
expect(textNodes[1][0].innerHTML).toEqual('Revenue', 'with correct hover labels');
expect(textNodes[1][1].innerHTML).toEqual('2,352.5', 'with correct hover labels');

}).then(done);
});
});
2 changes: 1 addition & 1 deletion test/jasmine/tests/scatterternary_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ describe('scatterternary hover', function() {

beforeEach(function() {
var cd = gd.calcdata,
ternary = gd._fullLayout.ternary._ternary;
ternary = gd._fullLayout.ternary._subplot;

pointData = {
index: false,
Expand Down