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

Recycled commits from abandoned fast trace toggle PRs #2860

Merged
merged 21 commits into from
Aug 2, 2018

Conversation

etpinard
Copy link
Contributor

based of #2823, supersedes #2837 and #2849.

I decided to abandoned the fast trace toggle PRs after #2849 (comment) and realizing that even a special 'legendonly' pathway wouldn't be enough (it would break categorical axes).

So, this PR attempts to ♻️ some of the work done in the past week, some of which have small but noticeable performance benefits.

Namely,

  • fullLayout._modules now includes trace modules of visible!==true traces. In particular, this leads to fullLayout._has('gl') returning true and some performance benefits for scattergl traces. For example,
Plotly.restyle(gd, 'visible', false)
setTimeout(() => {
  console.time('1')
  Plotly.restyle(gd, 'visible', true)
  console.timeEnd('1')
}, 500)

is 100ms faster now for scattergl graphs (no matter the number of points) as we no longer clear and reinit a WebGL context.

  • Axes.expand and ax._min/ax._max are replaced by findExtremes and _extremes containers in full trace and annotations/shapes items. Arguably, this improves maintenance as findExtremes does not mutate the full axis objects; it should make it easier to reuse cartesian/autorange.js in other subplot types. Moreover, this appears to have some performance benefits, perhaps as the pseudo O(n^2) expand/findExtremes algo is now done over a per-trace-not-axis-wide sample. For example, with
// this is the largest N before we use the average marker.size
// one Axes.expand / findExtremes call here clocks in at 20-50ms on average
var N = 1e5-1
var N2 = 1e5-1

var x = []
var y = []
var ms = []
var x2 = []
var y2 = []
var ms2 = []
var i

for(i = 0; i < N; i++) {
  x.push(Math.random())
  y.push(Math.random())
  ms.push(i % 2 ? 20 : 10)
}
for(i = 0; i < N2; i++) {  
  x2.push(Math.random() + 2)
  y2.push(Math.random())
  ms2.push(i % 2 ? 10 : 20)
}

Plotly.newPlot('graph', [{
  type: 'scattergl',
  mode: 'markers',
  x: x, y: y,
  marker: {size: ms}
}, {
  type: 'scattergl',
  mode: 'markers',
  x: x2, y: y2,
  marker: {size: ms2}
}])

The sum of the findExtremes computation times (2 per x/y axes) appear 10ms faster than the corresponding Axes.expand calls on master. Note that Axes.getAutorange is 10x slower on this branch (most likely from concatenating trace _extremes), but still clocks in well below the 1ms mark.

  • finally, this PR adds several tests the assert axis range after visible restyles.

But, please note this PR does not attempt to make trace and annotations/shapes visible a 'plot' edit type. They will remain a 🐢 'calc' edit type for the next few months 😞

cc @alexcjohnson

- DRY using .plot & .cleear with getViewport helper fn
- Dry .plot and .update with repeat helper fn
- compute viewport only once per subplot, as opposed to
  once per trace.
- so that gl-based trace can call their plot methods w/ an
  empty array of traces and just work.
- update and improve scatterlg visibility tests to reflect
  that `restyle(gd,visible,false)` no longer clear the context
- to not have to guard against visible!==true traces in _module.style
- to shortcut full list of modules in other places downstream
- Bar.setPositions mutates 'b' in bar trace calcdata,
  so to reinit 'b' during setPositions so that it doesn't
  conflit with Bar.calc
- a Axes.expend clone that does not append things to ax._min/ax._max,
  but instead returns two arrays, a min array and max array
  of potential data extremes
- pass gd, to look for _extremes in gd._fullData and eventually
  in layout container to can expand the axis ranges
- replacing Axe.expand
- for ErrorBars, we append the min/max arrays
  of the corresponding trace
- questionable commit, especially the part in autorange.js,
  but this doesn't make any test fail ?!?
- N.B. polar is the only subplot apart from cartesian that
  used Axes.expand and friends.
- by using ax.(_traceIndices, annIndices, shapeIndices),
  to keep track of traces, annotations and shapes plotted on
  axis ax. Adapt concatExtremes accordingly!
... by factoring out logic from findExtremes
@etpinard
Copy link
Contributor Author

etpinard commented Jul 31, 2018

Oh and I kept the work of f1cda60 squashed in dfada6a that moved the bar calc 'bar' mesures (i.e. calcdata[i][j].b) from Bar.calc to Bar.setPositions so that Bar.setPositions doesn't mutate what Bar.calc computed.

More info off #2849 (comment)

opts[i] = opt;
}
return opts;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

non blocking, would probably make a good Lib function. Pretty sure we use this in other places as well, though I'm not quite sure how to search for it...

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 33b4085

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I didn't find any other usage of a repeat helper in src/, but making a lib function now should help us remember that this helper exists.

@@ -69,6 +69,7 @@ exports.getModuleCalcData = function(calcdata, arg1) {
for(var i = 0; i < calcdata.length; i++) {
var cd = calcdata[i];
var trace = cd[0].trace;
// N.B. 'legendonly' traces do not make it pass here
Copy link
Collaborator

Choose a reason for hiding this comment

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

pass past

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed in 89aebd1

for(var i = 0; i < cd.length; i++) {
cd[i][0].trace._extremes[ax._id] = extremes;
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Now that we're not trying to make _extremes survive visibility changes, does it actually help to have the same thing repeated in all traces here? Seems like that will just waste time later on. Can we just put it in the first one and clear all the others?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for pointing this out, but turns out we still need to attach _extremes to all bar traces at the moment for error bars. Error bars concat their min/max extremes into the trace._extremes in which they are linked and assume that trace._extremes[ax._id] exists.

We could alternatively linked error bar _extremes in their trace.error_(x|y) containers and make getAutorange look for error_(x|y) in each trace. Or perhaps, making error bar create trace._extremes[ax._id] containers when needed would suffice.

But, I wouldn't worry too much about performance. I can't imagine getAutorange taking more than 1ms even in the worse of scenario.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah OK. I suppose the first one could have the actual values, then all the others could just get [] to give something for errorbars to append to... but yeah, no big deal, no block. These extras will get pruned quickly during your improved _concat that I hadn't noticed when I made that comment.

// set the width of all boxes
calcTrace[0].t.dPos = dPos;
// link extremes to all boxes
calcTrace[0].trace._extremes[posAxis._id] = extremes;
Copy link
Collaborator

Choose a reason for hiding this comment

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

same question as for bars... can we just put these extremes in the first one?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

... for consistency I'm leaning on voting for making all traces have an _extremes container. Like in my last comment, I can't imagine getAutorange ever being a performance drag.

if(Registry.traceIs(trace, 'cartesian')) {
var axId = ax._id;
if(extremes[axId]) {
out = out.concat(extremes[axId][ext]);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think my #2849 (comment) and #2849 (comment) still hold, and we should prune these the same way as in findExtremes to minimize O(min.length * max.length) in doAutoRange.

Copy link
Collaborator

Choose a reason for hiding this comment

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

oh sorry, you did that in a0bfaf3 🎉 and also 🌴 🏆 💯

Copy link
Contributor Author

@etpinard etpinard Aug 1, 2018

Choose a reason for hiding this comment

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

Yeah I tried to squash a0bfaf3 and 72f06a6 into ad1ac1f, but that was too much of a 🤕

@@ -55,6 +55,7 @@ function handleDefaults(contIn, contOut, coerce, opts) {
// propagate the template.
var axOut = contOut[axName] = {};
axOut._id = axOut._name = axName;
axOut._traceIndices = subplotData.map(function(t) { return t.index; });
Copy link
Collaborator

Choose a reason for hiding this comment

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

In cartesian you use t._expandedIndex - should it be the same here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good eye. Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed and 🔒 in d233f3b

@alexcjohnson
Copy link
Collaborator

Great job plucking out the good bits, this still ended up as a pretty big PR, that cleans up a lot of things! Should we give it a type: bug as well? It still includes the splom fix #2837 (comment)

@etpinard
Copy link
Contributor Author

etpinard commented Aug 1, 2018

It still includes the splom fix #2837 (comment)

Yes, it does. Thanks!

@etpinard etpinard added the bug something broken label Aug 1, 2018
@alexcjohnson
Copy link
Collaborator

Beautiful. 🍋 -> 🍹
💃

@etpinard etpinard merged commit 20db59a into faster-axis-autorange Aug 2, 2018
@etpinard etpinard deleted the per-trace-ax-extremes-maintenance branch August 2, 2018 15:59
@etpinard etpinard mentioned this pull request Aug 2, 2018
etpinard added a commit that referenced this pull request Aug 2, 2018
@etpinard etpinard mentioned this pull request Sep 4, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug something broken
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants