Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Fix interaction between sticky_edges and shared axes. #16450

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

Merged
merged 1 commit into from
Feb 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 19 additions & 8 deletions lib/matplotlib/axes/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2436,14 +2436,25 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True):
if tight is not None:
self._tight = bool(tight)

if self.use_sticky_edges and (
(self._xmargin and scalex and self._autoscaleXon) or
(self._ymargin and scaley and self._autoscaleYon)):
stickies = [artist.sticky_edges for artist in self.get_children()]
else: # Small optimization.
stickies = []
x_stickies = np.sort([x for sticky in stickies for x in sticky.x])
y_stickies = np.sort([y for sticky in stickies for y in sticky.y])
x_stickies = y_stickies = np.array([])
if self.use_sticky_edges:
# Only iterate over axes and artists if needed. The check for
# ``hasattr(ax, "lines")`` is necessary because this can be called
# very early in the axes init process (e.g., for twin axes) when
# these attributes don't even exist yet, in which case
# `get_children` would raise an AttributeError.
if self._xmargin and scalex and self._autoscaleXon:
x_stickies = np.sort(np.concatenate([
artist.sticky_edges.x
Copy link
Member

Choose a reason for hiding this comment

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

Even w/ the comment, the hasattr(ax, "lines") is a bit mysterious to me. Why are you singling out lines in particular versus other children? Why does ax.get_children error out? It should just return None, shouldn't 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.

It doesn't matter which children you pick, the point is that for twinned axes this is called even before the lines attribute is created (i.e. the first time self.lines = [] is called on the Axes ever), so get_children() just raises an AttributeError when it tries to return self.lines (among other children). (Yes, Axes init is a mess.)

Copy link
Member

Choose a reason for hiding this comment

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

I believe you, but this seems to be a band-aid rather than fixing the underlying issue, which is that get_children is not working or self.lines should be set (I guess by calling cla)?

I don't understand how self.lines is not created since __init__ calls cla...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, but cla() calls self._sharex.get_xlim() even before setting self.lines, and get_xlim causes a call to unstale_viewLim and tragedy occurs.
Really self.lines should be set even earlier in __init__, and cla() should not do self.lines = [] but just for child in self.get_children(): child.remove() (IOW it's not cla()'s job to create the attributes, just to clear them out) but I'm not going to touch that here...

Copy link
Member

Choose a reason for hiding this comment

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

Can we change get_children to do:

if not hasattr(self, 'lines'):
    # sometimes we haven't even initialized the artist lists yet:
   return []    # or None?  

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 think this is more likely to hide bugs -- normally no one should call get_children() that early in the axes init process, so I'd rather just workaround this in the sole place I know this can happen. But I can also apply your patch if you really prefer that.

Copy link
Member

Choose a reason for hiding this comment

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

Its just really mystifying why the check would be there, even with the comment.

Copy link
Member

Choose a reason for hiding this comment

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

It is a shorthand for "is the Axes actually built yet?". Agree it would be better if we could be sure that we never call this method before the Axes is fully set up, but short of re-writing the Axes init process (which is if I understand it one of our biggest performance bottle knecks) and making sure things like this that need to be computed don't get computed until as late as possible, we have to put a band-aid someplace.

I agree with @anntzer that putting this check in get_children is likely to mask other bugs in addition to solving this one.

We can't put the fix outside of this function because we may need the rest of it to run.

We could explicitly add state _done_initing, but that get awkward when you start to have sub-classes as now the order (or if!) the subclass calls super ends up really mattering.

for ax in self._shared_x_axes.get_siblings(self)
if hasattr(ax, "lines")
for artist in ax.get_children()]))
if self._ymargin and scaley and self._autoscaleYon:
y_stickies = np.sort(np.concatenate([
artist.sticky_edges.y
for ax in self._shared_y_axes.get_siblings(self)
if hasattr(ax, "lines")
for artist in ax.get_children()]))
if self.get_xscale().lower() == 'log':
x_stickies = x_stickies[x_stickies > 0]
if self.get_yscale().lower() == 'log':
Expand Down
15 changes: 15 additions & 0 deletions lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,21 @@ def test_use_sticky_edges():
assert_allclose(ax.get_ylim(), (-0.5, 1.5))


@check_figures_equal(extensions=["png"])
def test_sticky_shared_axes(fig_test, fig_ref):
# Check that sticky edges work whether they are set in an axes that is a
# "master" in a share, or an axes that is a "follower".
Z = np.arange(15).reshape(3, 5)

ax0 = fig_test.add_subplot(211)
ax1 = fig_test.add_subplot(212, sharex=ax0)
ax1.pcolormesh(Z)

ax0 = fig_ref.add_subplot(212)
ax1 = fig_ref.add_subplot(211, sharex=ax0)
ax0.pcolormesh(Z)


@image_comparison(['offset_points'], remove_text=True)
def test_basic_annotate():
# Setup some data
Expand Down