-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Sticky margins #7476
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
Sticky margins #7476
Conversation
832884e
to
231399c
Compare
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 looks very promising. I'm a little uncomfortable with the name stickies
, but I don't have a better suggestion yet. I really like the reduction in LOC.
x_stickies, y_stickies = r.get_stickies() | ||
(getattr(r.get_stickies(), | ||
{'vertical': 'y', 'horizontal': 'x'}[orientation]) | ||
.append(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.
It looks like you have a 4-physical-line obfuscation of the following 5 lines, correct?
x_stickies, y_stickies = r.get_stickies()
if orientation == 'vertical':
y_stickies.append(0)
else:
x_stickies.append(0)
But you only need x or y, so you could also do
if orientation == `vertical`:
r.get_stickies().y.append(0)
else:
r.get_stickies().x.append(0)
so we are back down to 4 physical lines that I find much easier to understand. And it would look even nicer with stickies
as a property.
@@ -926,98 +928,13 @@ def set_zorder(self, level): | |||
self.pchanged() | |||
self.stale = True | |||
|
|||
def get_top_margin(self): | |||
def get_stickies(self): |
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 suggest making stickies
a read-only property.
for patch in patches: | ||
(getattr(patch.get_stickies(), | ||
{'vertical': 'y', 'horizontal': 'x'}[orientation]) | ||
.append(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.
I suggest de-obfuscation as in the previous example.
231399c
to
f8bd0b4
Compare
@efiring I have addressed all your comments (which I agree with, in particular making stickies a readonly property. The only artist which I haven't handled yet is contour (and thus the related tests should fail). I am looking for a way to retrieve the extents of the contour, other than reusing the logic from |
This is a good example of why the ContourSet probably should be a compound artist, added to the Axes as a unit, instead of a bunch of collections that are independently added to the Axes. Given that now is not the time for that major refactoring, I think the thing to do is to save the data limits where they are calculated at the end of each implementation of |
f4f9e75
to
fdc07a0
Compare
|
|
No, it won't work in this case. The condition is |
fdc07a0
to
355de7b
Compare
It looks like the tests are passing now, with the exception of numpy 1.6 (due to isclose) and docs (which I haven't touched at all). (As a side note, perhaps we should think briefly whether it should be Realistically, I should probably be working on other stuff right now, so I would appreciate if someone could pick up the PR from there. If not I should be able to get back to it by the end of the month. |
Thanks for bringing it this far in a very short time! I expect that between @tacaswell and myself (and anyone else who is interested) we can pick it up, if Tom thinks this is the way to go, and we don't find some blocking problem with it. |
Closing and reopening to restart the tests after merge of #7483. |
Current coverage is 61.90% (diff: 90.90%)@@ master #7476 diff @@
==========================================
Files 173 173
Lines 56004 56103 +99
Methods 0 0
Messages 0 0
Branches 0 0
==========================================
+ Hits 34724 34731 +7
- Misses 21280 21372 +92
Partials 0 0
|
I am a bit confused why these tests are only failing with pytest and not with nose. |
I get legit failures locally now (not unbound local). |
The failure comes from a faulty calculation of
(which basically only autoscales based on the upper stacked histogram). This issue was not visible before because the previous implementation did not rely on autoscaling at that point (I think it relied on the calls to |
Is that perhaps related to #4414? |
Likely yes. |
xmin = max(xmin*0.9, minimum) if not input_empty else minimum | ||
xmin = min(xmin0, xmin) | ||
self.dataLim.intervalx = (xmin, xmax) | ||
self.dataLim.intervalx = (0, xmax) |
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 would fail with negative weights, or if there are already other artists present with greater extents.
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.
Rats! The idea of negative weights and negative histogram values never would have occurred to me; it can occur mathematically, but it doesn't make sense for what I think of as a "histogram".
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.
The original code was broken, even before 2.x; the present code is broken. It turns out that the present "sticky" algorithm is not doing the right thing for plain bar plots, either, when switching to a log scale; this was working in 1.5.3. I don't know if it was working with the "margins" algorithm in 2.x.
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.
log-scale bar histograms seem to work fine for me (np.random.seed(0); plt.hist(np.random.randn(1000), 10, log=True)
gives an identical plot with 2.0b4 and with this branch), so at least in theory it should also be possible to make it work correctly with stepfilled plots?
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.
Yes, everything can be made to work, at least under reasonable circumstances... but the devil is in the details. I am making progress but running into problems with the test framework. Rebasing on current master helps, I think. Do you mind if I force-push the rebase?
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.
Not at all, go ahead.
Let artists register lists of "sticky" x and y values. If an edge of the union of bounding boxes ends up falling on a sticky value, do not add margins to that edge. Docs still missing.
Now there is still one appveyor test failure (only in the 2.7 "test all" run):
|
That one also needs to have it's thershold bumped, but is also failing on master (unrelated to this). |
After yesterday's conversation, I think that we need to add one more component: an API for switching the |
See also #6281 (comment) re: reversibility. |
stickies = [artist.sticky_edges for artist in self.get_children()] | ||
x_stickies = sum([sticky.x for sticky in stickies], []) | ||
y_stickies = sum([sticky.y for sticky in stickies], []) | ||
if self.get_xscale().lower() == 'log': |
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.
Shouldn't the presence of negative stickies with log scale raise an error?
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.
Not any more than negative values in a plot do, I think?
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.
Well, I think it is a bug that we tolerate this :)
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.
@NelleV, you think that if a histogram plot is switched to a log scale it should fail rather than yielding a plot? In other words, simply bumping the "L" key when a histogram plot is active should raise an exception?
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.
If it means that the following code raises an error, then yes I think it should.
I think the fact that matplotlib accepts log scale when the user provides negative value is at high risk of having unintended bugs, and leads to wrong results and interpretation of analysis.
On the other hand, I'd be fine if the error was caught internally when in interactive mode.
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
ax.set_yscale("log")
ax.plot(np.arange(0, 10))
ax.plot(np.arange(-100, 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.
I think the behavior proposed by @NelleV is reasonable but this should not be handled by the sticky system (nor block this PR) -- it should affect all plots, including those who do not set sticky margins.
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 agree.
|
||
@use_sticky_edges.setter | ||
def use_sticky_edges(self, b): | ||
self._use_sticky_edges = bool(b) |
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.
Should this also re-trigger an auto-scaling?
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 was wondering about that also, but decided against it. I could be convinced otherwise for now, but long term I think we might want to consolidate the autoscaling at draw time instead of having calls to autoscale_view
sprinkled all over the place. I don't think it makes sense to trigger autoscaling with every change in the state of the 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.
I am convinced
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.
Do we actually need the axes as stale? This won't have an effect until the next autoscaling, which should also mark it as stale, right?
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.
Good point. You are welcome to replace it with a comment to that effect. I think I put the call in because we tend to have it in every function that potentially changes how the plot will look.
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.
Done, together with indentation fix below.
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.
Not sure I agree with this, if as Eric suggests above we move towards autoscaling a draw time (as late as possible) then marking the figure as stale will trigger a re-draw (and hence the re-autoscaling).
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.
Good point.
On the other hand this is easy enough to revert when we do switch to draw-time autoscaling (which makes complete sense).
I've bumped this back to "MRG+1" based on Tom's earlier approval and all tests passing. If people are happy enough with the API, including the |
**kwargs) | ||
if style == "image": | ||
im = mimage.AxesImage(self, cmap, norm, | ||
interpolation='nearest', |
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 guess it passes, but it's a bit of a weird indent.
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.
True. It's accidental. You are welcome to fix it.
x1 = tri.x.max() | ||
y0 = tri.y.min() | ||
y1 = tri.y.max() | ||
self.ax.update_datalim([(x0, y0), (x1, y1)]) |
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 do not think we need the line below this either, the autoscaling gets called later in the init.
Green button pushed. |
On the other hand, related tickets have probably not been closed. Can someone elte take care of this? |
I am in the process of backporting this |
Sticky margins Conflicts: lib/matplotlib/streamplot.py - discard conflicting diff, it is on changes to code which is not backported to 2.x lib/matplotlib/tests/test_triangulation.py - discard win-specific test changes (they do not exist on 2.x) lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.pdf lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.svg - kept version from master
backported to v2.x as 0289b4f |
Let artists register lists of "sticky" x and y values. If an edge of
the union of bounding boxes ends up falling on a sticky value, do not
add margins to that edge.
Docs still missing.
See #7471 (comment).
I may not have so much time to work on this in the coming days, feel free to directly push to my repo to update this PR.
TODO list