-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Autosize / autorange fixes #2437
Conversation
// // To do so, we must manually set them back here using the _initialAutoSize cache. | ||
// if(['width', 'height'].indexOf(ai) !== -1 && vi === null) { | ||
// fullLayout[ai] = gd._initialAutoSize[ai]; | ||
// } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On closer inspection I think I understand what this is about, but I don't think it's actually the correct behavior, for example if gd
was resized external to plotly.js it wouldn't make sense to revert to when the plot was first drawn. I'm not quite sure how to handle this though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😬 auto-sizing always has a way to make my brain hurt.
Referencing #635 which did a lot of damage the streambed 🤕
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oy, I think we've all fought with that one at various points... If you don't mind, I'm going to leave this as it is now - with the TODO here to remind us about it if a related bug pops up, but without trying to implement anything around it. We have some great tests in config_test that I extended to make sure that all of those test cases work the same in relayout
and react
, I'd like to just freeze it until we have more test cases to look at.
src/plots/cartesian/autorange.js
Outdated
doAutoRange: doAutoRange, | ||
needsAutorange: needsAutorange, | ||
expand: expand | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I initially pulled this module out of axes.js
to avoid circular references; then I ended up moving clearCalc
into setConvert
instead of here, which mooted the circular reference problem, but I still like having these separate from the behemoth axes.js
.
src/plots/gl2d/scene2d.js
Outdated
@@ -261,7 +260,7 @@ proto.updateSize = function(canvas) { | |||
} | |||
|
|||
// make sure plots render right thing | |||
if(this.redraw) this.redraw(); | |||
// if(this.redraw) this.redraw(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I forgot to remove this line... but @dfcreative @etpinard I wanted to ask about it anyway: if I leave it in, then occasionally - most noticeably when I autorange an old gl2d plot (ie pointcloud) - I get some extra tick labels: the new tick values get placed on the old axis range (if they're visible) then the correct tick labels get drawn over them without removing the obsolete ones.
Anyway I couldn't see any ill effects from removing it. OK?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, looks like in scene2d.plot
this
plotly.js/src/plots/gl2d/scene2d.js
Line 474 in 8731cb5
this.glplot.draw(); |
does the same thing. 🔪 away!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:hocho -> 0ecaaaf
if(bounds[i] > bounds[i + 2]) { | ||
bounds[i] = -1; | ||
bounds[i + 2] = 1; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also seems like this.bounds
has stopped being used, so I 🔪 it. Can't wait until we can 🔪 this whole subplot type...
if(!ax._m) ax.setScale(); | ||
|
||
var len = data.length; | ||
var extrapad = options.padded || false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct me if I'm wrong, but this used to be the only place in expand()
that used ax._length
as:
plotly.js/src/plots/cartesian/axes.js
Line 455 in 8731cb5
extrappad = options.padded ? ax._length * 0.05 : 0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this used to be the only place in expand() that used
ax._length
Exactly - taking that out, and instead holding on to extrapad
inside the ax._min/_max
entries, to be evaluated during getAutorange
, is what fixed #2428 and enabled calc
->plot
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great! That makePadFn
helper above is brilliant ✨ and expand()
is much clearer and well commented 📚
Now, would mind mentioning in the jsDoc above that expand()
should not depend on pixel values (e.g ax._length), but only on calcdata
things?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fuller docs -> 9913afe
src/plots/cartesian/autorange.js
Outdated
|
||
var i, j, k, v, di, dmin, dmax, ppadiplus, ppadiminus, includeThis, vmin, vmax; | ||
|
||
function getPad(item) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could maybe rename this function. getPad
might be confused with the output of makePadFn
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
haha I noticed that too... I'll change it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
renamed -> 9913afe
} | ||
} | ||
|
||
function needsAutorange(ax) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels awkward to export this now that auto-range routines are out of axes.js
.
Maybe we should move fastAxisExpand
out of scattergl/index.js and into this file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or just bake the fastAxisExpand
functionality into expand
to be used automatically when the paddings are constant - then everyone wins! I think that should be possible... I'll try it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
then everyone wins!
That's an idea! But perhaps the TOO_MANY_POINTS
constant should remain per trace type?
@@ -86,7 +86,7 @@ module.exports = { | |||
values: ['normal', 'tozero', 'nonnegative'], | |||
dflt: 'normal', | |||
role: 'style', | |||
editType: 'calc', | |||
editType: 'plot', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to add a few tests like this one that 🔒 the new editType: 'plot'
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... as removing editType: 'calc'
is a pretty be deal 🐎 we wouldn't want to regress from that in the future!
and use this instead of ad-hoc logic in scattergl and pointcloud
also fix a bug with relayout of 2D arrays
} | ||
calcAxisExpansion(gd, trace, xa, ya, x, y, ppad); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very clean. This is great!
To note though, this new combined pathway is little slower. Previously at 1e6 pts, fastAxisExpand
would clock in at ~5ms for one coordinate, now we're more in a 20-30ms range. Even though this is significant, I think it's worthwhile to make scattergl autorange even more on-par with the rest of the library. 👍
The failing tests are in this new suite that got put in during #2404. Should be an easy fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huh, let's unpack that performance issue. Previously the inner loop was:
for(var i = 0; i < vals.length; i += 2) {
var v = vals[i];
if(v < b0) b0 = v;
if(v > b1) b1 = v;
}
this afternoon it was:
for(i = 0; i < data.length; i++) {
v = data[i];
if(Math.abs(v) < FP_SAFE) {
// data is not linearized yet so we still have to filter out negative logs
if(v < vmin && (!isLog || v > 0)) vmin = v;
if(v > vmax) vmax = v;
}
}
The old code was accidentally omitting half the points (i+= 2
), so we should be comparing 10ms to 20-30 ms (unless the 20-30ms is for both coordinates?). Then I'm doing two extra things: 1) comparing to FP_SAFE
so we don't get a broken graph from numbers very close to the limit of floating point numbers, and 2) ensuring positive numbers when on a log scale. The new code also has a bunch more logic before and after this loop, but nothing else scales with the data size, all that is presumably <1ms. In my own tests don't see the extra logic in the loop coming anywhere near doubling the time. What am I missing?
Anyway I noticed I could optimize it a bit by making two versions of the loop, one for log and one for other types. Seems worth a couple of extra lines since this gets so much traffic -> 247c626
Updated the failing test in cf48426
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great! You're latest patch in 247c626 got Axes.expand
within 5ms of fastAxisExpand
with i++
swapped in for i += 2
at 1e6 pts. Considering it's more robust and works with traces other than scattergl
, I'll call this a win. 🥇
}); | ||
|
||
Plotly.relayout(gd, attr, axLayoutEdits[attr]); | ||
expectReplot(attr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💯 / 💯 test!
expect(gd._fullLayout.xaxis.range).toBeCloseToArray([-0.05, 1.05], 'x range'); | ||
expect(gd._fullLayout.yaxis.range).toBeCloseToArray([-0.11, 1.11], 'y range'); | ||
expect(gd._fullLayout.xaxis.range).toBeCloseToArray([-0.119, 1.119], 2, 'x range'); | ||
expect(gd._fullLayout.yaxis.range).toBeCloseToArray([-0.199, 1.199], 2, 'y range'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In comparison, at 1e5 - 1
pts (i.e. below the big-data threshold), we get:
@alexcjohnson I'll reference #2417 - but honestly feel free to close it if you think Axes.expand
is satisfactory now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, so I guess that result is saying that in this case the constant pixel padding we choose is a bit bigger than it would need to be... but this is a random case, a lot of real cases will have correlations, like all the biggest markers are near one edge, so I think this is a pretty good compromise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so I think this is a pretty good compromise.
Yep. I think so too 👌
Great bug fixing / perf improvement PR 💃 💃 |
Fixes #2428 - despite its recent posting, I think this is one of our oldest bugs. You see it in the way a lot of autoranged plots will change just a little bit if you double-click them to cause the autorange to rerun... including a whole lot of our test images. They don't do that anymore. This happened when the margins get pushed automatically (mostly by the legend) and the axes are padded, because
Axes.expand
(called duringcalc
) depended onax._length
, which could change aftercalc
due to margin pushers. As a nice side benefit, now thatAxes.expand
is independent of axis length all the layout attributes that affect axis lengths can have theireditType
downgraded fromcalc
toplot
, speeding up all these edits.Fixes #2385 as well -
Plotly.react
now handles autosize and size changes correctly. This was mostly just a matter of adding the special autosize handling that we already had inrelayout
intoreact
as well, but then when I dropped axis size changes toeditType: 'plot'
it also required some more careful handling ofax._min/_max
.cc @etpinard