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

Fix relayout constrained axes + various multi-subplot perf improvements #2628

Merged
merged 11 commits into from
May 14, 2018

Conversation

etpinard
Copy link
Contributor

@etpinard etpinard commented May 11, 2018

This PR started with a fix for relayout on constrained axes #2540 (comment). This fix made the rangesAltered stash in relayout

// figure out if we need to recalculate axis constraints
var constraints = fullLayout._axisConstraintGroups || [];
for(axId in rangesAltered) {
for(i = 0; i < constraints.length; i++) {
var group = constraints[i];
if(group[axId]) {
// Always recalc if we're changing constrained ranges.
// Otherwise it's possible to violate the constraints by
// specifying arbitrary ranges for all axes in the group.
// this way some ranges may expand beyond what's specified,
// as they do at first draw, to satisfy the constraints.
flags.calc = true;
for(var groupAxId in group) {
if(!rangesAltered[groupAxId]) {
Axes.getFromId(gd, groupAxId)._constraintShrinkable = true;
}
}
}
}
}

work properly and lead me to use the same stash to implement per-axis "axrange" edits (as planned in #2547). While at it, I added a few more splom-related perf improvements for multi-subplot graphs, namely


Some benchmarks:

  • Implementing per-axis "axrange" relayout edits trims ~200ms on e.g Plotly.relayout(gd, 'xaxis.range[0], 0) on a 50-dimensions splom. At 15 dimensions (i.e. with svg grid lines), it trims ~100ms.
  • Making all sploms except those with showlowerhalf: false have a set anchor trims 40-50ms.

So, starting with https://codepen.io/etpinard/pen/wjmqmO and benchmarking Plotly.relayout(gd, 'xaxis.range', [0, 1]) we get:

# dims # values per dim results w/ 1.37.1 [ms] results w/ this branch [ms]
15 100 160 40
30 100 220 130
50 100 500 250

... by extracting correct prop "leaf" end index which
    is used to keep track of "altered ranges" which than
    lead to `flags.calc: true` under axis constraints.

    In the test added, the # of xgrid ended being the same
    before and after the relayout call.
- updating axes with constraints triggers a 'calc' edit,
  so no need to do this here.
- these clip paths are only used for shapes and layout images,
  which make hasOnlyLargeSploms false, so we don't need them.
- this can trim ~50ms on first render at 50 splom dimensions.
- by passing (now correct) alteredRange hash object to doTicksRelayout
- by addding new doTicks(gd, [axId, axId2, ...]) call signature
- clean up doTicks docstring
- N.B. not implemented for Plotly.react (yet)
- so that supplyDefaults and "easy" edits (e.g. axrange, modebar)
  can be faster.
- mv "axrange" react test out of relayout switchboard describe
  keeping "real" graph div out the relayout switchboard describe
- this way, the Grid component can generate axes with set 'anchor'
  for all sploms except when `showlowerhalf: false`. Having
  set anchored axes is a performance boost (~40ms at 50 dimensions).
... which had their y-axis shift a little bit
    by the previous commit that made their axes anchored.
... which in my opinion is now more "correct" than previously
@etpinard etpinard added bug something broken status: reviewable labels May 11, 2018
@etpinard
Copy link
Contributor Author

etpinard commented May 11, 2018

A few more comments on constrained axes, that should be followed up in #2540:

  • Could making range relayout go through editType "plot" (instead of "calc") be sufficient? When replacing this line

flags.calc = true;

with flags.plot = true, no tests are failing.

  • I think Plotly.react is broken with constrained axes. I'm not sure if it's always been broken or if the recently-added "axrange" edit did it 😕

@@ -2171,7 +2169,9 @@ exports.update = function update(gd, traceUpdate, layoutUpdate, _traces) {
if(relayoutFlags.layoutstyle) seq.push(subroutines.layoutStyles);
if(relayoutFlags.axrange) {
seq.push(
subroutines.doTicksRelayout,
function(gd) {
return subroutines.doTicksRelayout(gd, relayoutSpecs.rangesAltered);
Copy link
Contributor Author

@etpinard etpinard May 11, 2018

Choose a reason for hiding this comment

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

N.B. @alexcjohnson I didn't implement per-axis "axrange" edit for Plotly.react. My main concern was improvement perf on dragend axis range relayout calls. I didn't think adding an exception to the react diffing algo was worth it for this case.

@@ -324,6 +329,48 @@ exports.lsInner = function(gd) {
return gd._promises.length && Promise.all(gd._promises);
};

function findMainSubplot(ax, fullLayout) {
Copy link
Contributor Author

@etpinard etpinard May 11, 2018

Choose a reason for hiding this comment

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

@alexcjohnson I didn't bother making the "find main subplot" algo faster as you proposed in #2549 (comment), I was happy enough with skipping this whole thing except for sploms with showlowerhalf: false with c947b2c

@alexcjohnson
Copy link
Collaborator

Could making range relayout go through editType "plot" (instead of "calc") be sufficient?

Do we have a test of setting mutually-exclusive range values, as per the comment on that line? If we do, and the result correctly fixes them for the constraints, then go for it!

@@ -1806,7 +1806,7 @@ function _relayout(gd, aobj) {
var plen = p.parts.length;
// p.parts may end with an index integer if the property is an array
var pend = plen - 1;
while(pend > 0 && typeof p.parts[plen - 1] !== 'string') { pend--; }
while(pend > 0 && typeof p.parts[pend] !== 'string') pend--;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ha, nice catch!

*
* Signature 5: Axes.doTicks(gd, [axId, axId2, ...])
* where the items are axis id string,
* use this to update multiple axes in one call
Copy link
Collaborator

Choose a reason for hiding this comment

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

This was already a bit out of control, do we really want to add another signature?

Can we perhaps break it into 2 separate functions, one for signatures 1 & 2 and a second for 3, 4, 5? That's how it functions anyway - 3,4,5 just map into a bunch of calls to signature 2 (ignoring skipTitle) and then return.

Copy link
Contributor Author

@etpinard etpinard May 11, 2018

Choose a reason for hiding this comment

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

Sure that would be nice. But since doTicks is one of our most referenced function in existing issues, I'm a little reluctant to do so.

But if you insist, than I'm thinking of making Axes.doTicks the multi-cartesian-axis version (that accepts '', 'redraw' and arrays of axis ids), and adding Axes.doTicksSingle (that accepts one axis id string or an axis object).

On master, we have:

image

@alexcjohnson
Copy link
Collaborator

💪 Well done, this is a really fantastic collection of boosts and fixes! Thanks for the Axes.doTicks separation, a nice first step toward more comprehensible axes.js.

#2628 (comment) is non-blocking.
💃

@etpinard
Copy link
Contributor Author

#2628 (comment) is non-blocking.

Moved to #2540 (comment)

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