Skip to content
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

Scattergl lasso #1657

Merged
merged 66 commits into from
Jun 19, 2017
Merged

Scattergl lasso #1657

merged 66 commits into from
Jun 19, 2017

Conversation

dy
Copy link
Contributor

@dy dy commented May 9, 2017

Enables svg interactions for scattergl plot.
Some minor issues TODO:

  • zooming loses selection
  • autoscale loses cdata.plot
  • opacity of huge number of points is indistinguishable from non-selected points
  • blinking of some points on selection
  • picking is lost
  • fix tests
  • low fps on selection (ok at 1e5)
  • panning does not clear selection
  • add tests

Related dy/plotly-contrib#3

@jackparmer
Copy link
Contributor

This will be useful for the crossfilter project too, where large D3 line and scatter plots may not be zippy enough: #1316

@jackparmer
Copy link
Contributor

Hey @dfcreative will scattergl have plotly_selected and plotly_deselect events identical to its SVG counterpart?
http://codepen.io/plotly/pen/BpayyX

@etpinard
Copy link
Contributor

Hey @dfcreative will scattergl have plotly_selected and plotly_deselect

yes,

Copy link
Contributor

@etpinard etpinard left a comment

Choose a reason for hiding this comment

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

First pass comments.

This is looking promising. Nice job @dfcreative

Looks like you broke the gl2d hover spikes (i.e. those black lines to go from data point down to the axes).

I think for this iteration we should keep the current gl2d hover routine (the mouseContainer thing in scene2d.js) and add just what we need from Cartesian for selections.

@@ -12,23 +12,22 @@
var Scene2D = require('./scene2d');
var Plots = require('../plots');
var xmlnsNamespaces = require('../../constants/xmlns_namespaces');

var constants = require('../cartesian/constants');
var cartesian = require('../cartesian')
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd prefer

var Cartesian = require('../cartesian');

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

var lastX = 0, lastY = 0, lastMods = {shift: false, control: false, alt: false, meta: false};
mouseChange(element, handleInteraction);

// enable simple touch interactions
Copy link
Contributor

Choose a reason for hiding this comment

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

Amazing 🎉

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is unmerged #1507 here

@@ -303,6 +318,11 @@ proto.update = function(options) {
// not quite on-par with 'scatter', but close enough for now
// does not handle the colorscale case
this.color = getTraceColor(options, {});

//provide reference for selecting points
if (cdscatter && cdscatter[0] && !cdscatter[0].plot) {
Copy link
Contributor

Choose a reason for hiding this comment

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

.plot might not be best here. This is really a trace module though .trace is already taken.

Maybe .glTrace?


// reuse the Scatter3D 'dummy' calc step so that legends know what to do
ScatterGl.calc = require('../scatter3d/calc');
ScatterGl.calc = require('../scatter/calc');
Copy link
Contributor

Choose a reason for hiding this comment

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

Smart 👍

Now, can you rm src/trace/scattergl/calc.js?

Copy link
Contributor

@etpinard etpinard May 26, 2017

Choose a reason for hiding this comment

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

... but that might slow down scattergl for 1e5+ point graphs. 😕

Copy link
Contributor

Choose a reason for hiding this comment

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

For the record, we must make sure plotting 1e6 points with scattergl (passing through updateFast) clocks in at around 1 second.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is required to obtain selection in scattergl/select. Mb it is possible to optimize that, just haven't got that a chance.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually I see no speed difference compared to old implementation for 1e6.

Copy link
Contributor Author

@dy dy Jun 6, 2017

Choose a reason for hiding this comment

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

@etpinard ~3300ms vs ~3500ms for 1e6 points, that is 200ms slowdown due to

    var x = xa.makeCalcdata(trace, 'x'),
        y = ya.makeCalcdata(trace, 'y');

Copy link
Contributor

Choose a reason for hiding this comment

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

That's ~3300ms on master?

Copy link
Contributor Author

@dy dy Jun 6, 2017

Choose a reason for hiding this comment

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

@etpinard just updated master and plugged laptop: ~3000ms vs ~3200ms, I hardly notice the difference due to fluctuations

Copy link
Contributor

Choose a reason for hiding this comment

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

Wow. That's brutal. Does anyone else have to wait 3000ms when plotting 1e6 points?

Copy link
Contributor

Choose a reason for hiding this comment

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

With 6ac722d we're back in the ~1500ms for 1e6 first render as on master 🎉

Most of scattergl/calc.js is now skipped when dragmode is set to pan/zoom (the default).

Calling Plotly.relayout from pan/zoom to lasso/select now trigger a recalc where gd.calcdata is filled so that the selection logic knows what to do.

@@ -79,7 +79,7 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) {
var yIDs = [ya0._id];

// if we're dragging two axes at once, also drag overlays
subplots = [plotinfo].concat((ns && ew) ? plotinfo.overlays : []);
subplots = [plotinfo].concat((ns && ew && plotinfo.overlays) ? plotinfo.overlays : []);
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 the extra && plotinfo.overlays now?

// 3. errorbars for bars and scatter
// 4. scatter
// 5. box plots
function joinPlotLayers(parent) {
Copy link
Contributor

Choose a reason for hiding this comment

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

... e.g all of these are useless in gl2d

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.

@@ -359,7 +358,6 @@ proto.destroy = function() {

if(!this.staticPlot) this.container.removeChild(this.canvas);
this.container.removeChild(this.svgContainer);
this.container.removeChild(this.mouseContainer);
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm. That probably breaks gl2d/camera.js

Copy link
Contributor

Choose a reason for hiding this comment

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

So wait, are we 🔪 the gl2d picking routine?

It would be nice to merge the cartesian and gl2d hover logic at some point, but swapping in cartesian hover in gl2d probably means poor performance on graphs with 1e5 and up data points at this stage.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The problem is that mouseContainer intercepts mousemove, needed for cartesian selection to work. There seems to be no good way to hook up gl2d/camera so to enable cartesian/select − these are too separate concerns.
It is possible to switch to cartesian hover only for lasso/select mode, but the pick style differs visually, which is confusing.

Copy link
Contributor

Choose a reason for hiding this comment

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

Probably resolved by 1451802

error_x: scatterAttrs.error_x
error_x: scatterAttrs.error_x,

hoveron: {
Copy link
Contributor

Choose a reason for hiding this comment

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

This has no effect in gl2d. You must have added this in to make other things not break, correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Picking was broken without hoveron property

Copy link
Contributor

@etpinard etpinard Jun 6, 2017

Choose a reason for hiding this comment

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

if(sel && !sel[index] && j === 3) {
color *= DESELECTDIM;
}
this.scatter.options.colors[4 * i + j] = color;
Copy link
Contributor

Choose a reason for hiding this comment

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

Very nice. Thanks for covering both updateFast and updateFancy !!

Copy link
Contributor

Choose a reason for hiding this comment

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

@dfcreative Something broke selection for fancy traces e.g. with array marker.size:

peek 2017-06-06 17-26

var x = [];
var y = [];
var size = []

for( var i = 0; i < 1e4; i++)
{
  x.push(i);
  y.push(i*Math.random());
  size.push(15 * Math.random())
}

var scatterglMarkers = {
  x: x, y: y, type: "scattergl", mode: "markers",
  marker: {size: size, opacity: 1}
};

console.time('scattergl')
Plotly.newPlot('graph',  [scatterglMarkers], {dragmode: 'lasso'})
.then(() => console.timeEnd('scattergl'))

Copy link
Contributor

Choose a reason for hiding this comment

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

Did you get the time to investigate ⤴️ ? If not, I can look at it this afternoon.

Copy link
Contributor

@etpinard etpinard Jun 7, 2017

Choose a reason for hiding this comment

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

Well, looks like this was never implemented.

this.selectedIds in the scene2d.updateFancy scope doesn't reference anything.

Copy link
Contributor Author

@dy dy Jun 7, 2017

Choose a reason for hiding this comment

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

@etpinard done.

Copy link
Contributor

Choose a reason for hiding this comment

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

Beautifully done. Thanks very much!

ScatterGl.plot = require('./convert');
ScatterGl.selectPoints = require('./select');
Copy link
Contributor

Choose a reason for hiding this comment

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

🎉

ax.range = ax._rl = axes.getAutoRange(ax);

ax._r = ax.range.slice();
ax._rl = Lib.simpleMap(ax._r, ax.r2l);
Copy link
Contributor

Choose a reason for hiding this comment

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

This is an interesting patch. I wonder if this might be fixing an undiscovered bugs(s).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@monfera
Copy link
Contributor

monfera commented Jun 10, 2017

Started leaning on this branch as this feature is essential for crossfiltering if the number of points is over around 1k. No specific need for it now, but at some point, maybe we'd want to add the properties curveNumber and pointNumber to elements of the eventData.points array for feature parity w/ scattergl. These numbers can work for indexing calcdata eg. gd.calcdata[curveNumber][pointNumber] (speaking of this object, at some point it could have the data property that gets fed from customdata). The eventData points already have the id which is all I need right now.

@monfera
Copy link
Contributor

monfera commented Jun 10, 2017

Quick perf check, it works well at 10k points as it's pushed rn:
scattergl-10k

It currently seems overly slow with 125k points where color is an array (haven't tested otherwise as crossfilter needs salient selected points):
scattergl-100k

Also, as seen on this 2nd animgif (also present with 10k points) the hover tooltip, for some reason, appears on initial render with no interaction (top left) and is stuck there even when mouse is moved (with or without hovermode: 'closest').

@monfera
Copy link
Contributor

monfera commented Jun 10, 2017

⬆️ cursory test shows that readPixels in

function SelectBuffer(gl, fbo, buffer) {
  this.gl     = gl
  this.fbo    = fbo
  this.buffer = buffer
  this._readTimeout = null
  var self = this

  this._readCallback = function() {
    if(!self.gl) {
      return
    }
    fbo.bind()
    gl.readPixels(0,0,fbo.shape[0],fbo.shape[1],gl.RGBA,gl.UNSIGNED_BYTE,self.buffer)
    self._readTimeout = null
  }
}

is the culprit (slow irrespective of how many of the 125k points is currently in the visible, zoomed domain):

image

@etpinard
Copy link
Contributor

etpinard commented Jun 13, 2017

TODO:

@dy
Copy link
Contributor Author

dy commented Jun 13, 2017

@monfera may I ask you to help reproducing this bug with stuck hover tooltip? I faced it once, but it works fine rest of the time. Is some special scenario to that?

@monfera
Copy link
Contributor

monfera commented Jun 13, 2017

@dfcreative not showing up on my current branch right now, I'll make a minimal example if it occurs again

@monfera
Copy link
Contributor

monfera commented Jun 13, 2017

@dfcreative I don't have an isolated case, just a hint at this time - the tooltip seems to get stuck if I delete the marker.opacity line here:

Plotly.newPlot(
      plotlyChartHolder01,
      [{
        type: 'scattergl',
        mode: 'markers',
        marker: {
          size: 3,
          color: 'rgba(255,255,255, 0.25)',
          opacity: atomic.map(d => 1)
        },
        x: atomic.map(d => d.logValueMmUsd),
        y: atomic.map(d => d.logKilotons),
        ids: atomic.map(d => d.key),
      }],
      {
        hovermode: 'closest',
        dragmode: 'select',
        width: 500
      }
    )

In addition, when the opacity array is not present, and then do a rectangular lassoing (which is the initial state due to dragmode: 'select') there's an error complaining that positions === null. There are 2300 points. I can't work on a minimal case rn but thought to share it in case the above is a sufficient condition.

etpinard added a commit that referenced this pull request Jun 14, 2017
y = ya.c2p(di.y);
if(polygon.contains([x, y])) {
selection.push({
id: i,
Copy link
Contributor

Choose a reason for hiding this comment

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

TODO: i here isn't the id, it's the pointNumber.

Would be best to merge #1770 first, as this here will break after commit 3399a58 is applied.

Copy link
Contributor

Choose a reason for hiding this comment

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

done in debdd46

@@ -444,3 +444,149 @@ describe('Test hover and click interactions', function() {
.then(done);
});
});

describe('Test gl2d lasso/select:', function() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Painful! For some reason, these tests doesn't work on CI. Looks like the cases below can't even draw the scene:

image

https://circleci.com/gh/plotly/plotly.js/4535#tests/containers/1

Can anyone try to pull down this branch and try this out locally?

Copy link
Contributor Author

@dy dy Jun 15, 2017

Choose a reason for hiding this comment

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

Works fine for me:
image
Although couple of times 'should output correct event data for scattergl' failed due to delay - in some reason karma runs chrome with blank tab, I have to manually switch to tests tab, but this is irrelevant.

@etpinard
Copy link
Contributor

Would anyone be opposed to merging this without CI test support?

I can't figure out why these new tests keep failing on CircleCI. With the @noCI tag, they will run locally on npm run test-jasmine and be part of the pre-release checklist in ./tasks/noci_test.sh.

@monfera
Copy link
Contributor

monfera commented Jun 16, 2017

@etpinard sounds good to me!

@etpinard etpinard merged commit a227ae1 into plotly:master Jun 19, 2017
@etpinard etpinard deleted the scattergl-lasso branch June 19, 2017 14:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature something new
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants