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

Skip to content

FIX: ColorbarAxes properly in the axes stack #20484

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

Closed
wants to merge 4 commits into from

Conversation

jklymak
Copy link
Member

@jklymak jklymak commented Jun 22, 2021

PR Summary

Closes #20479

ColorbarAxes was not sitting in the figure Axes stacks correctly - rather cb.outer_axes was still in the queue. Which worked fine, but didn't make mouseevents pass through properly.

The solution here is to swap cb.ax for cb.outer_axes in the figure axes stacks, and to redefine draw on the ColorbarAxes to draw the outer axes first (i.e. set the aspect ratio and size of the bounding box for the colorbar).

I was testing on the following, though the real test is simply that cb.ax appears in the figure axes list now:

import numpy as np
import matplotlib.pyplot as plt
#from matplotlib.backend_bases import MouseButton

def pick_fn(mouseevent):
    if mouseevent.inaxes is colorbar.ax:
        print('pick\n\nPICKKKKKKK')
        print(mouseevent.ydata, mouseevent.button)


def motion_fn(mouseevent):
    if mouseevent.inaxes is colorbar.ax:
        print(mouseevent.ydata, mouseevent.button)
        pass


fig, ax = plt.subplots()
canvas = fig.canvas
arr = np.random.randn(100, 100)
axesimage = plt.imshow(arr)
colorbar = plt.colorbar(axesimage, ax=ax, use_gridspec=True)

# helps you see what value you are about to set the range to
colorbar.ax.set_navigate(True)

canvas.mpl_connect("motion_notify_event", motion_fn)
colorbar.ax.set_picker(True)
canvas.mpl_connect("button_press_event", pick_fn)

plt.show()

PR Checklist

  • Has pytest style unit tests (and pytest passes).
  • Is Flake 8 compliant (run flake8 on changed files to check).
  • New features are documented, with examples if plot related.
  • Documentation is sphinx and numpydoc compliant (the docs should build without error).
  • Conforms to Matplotlib style conventions (install flake8-docstrings and run flake8 --docstring-convention=all).
  • New features have an entry in doc/users/next_whats_new/ (follow instructions in README.rst there).
  • API changes documented in doc/api/next_api_changes/ (follow instructions in README.rst there).

@jklymak jklymak force-pushed the fix-colorbar-axes-stack branch from e94d77c to eb879a9 Compare June 22, 2021 16:19
@jklymak jklymak force-pushed the fix-colorbar-axes-stack branch from c2cf781 to 4c7dbd7 Compare June 22, 2021 21:16
@jklymak jklymak force-pushed the fix-colorbar-axes-stack branch from 4c7dbd7 to fb768c7 Compare June 22, 2021 21:23
@jklymak
Copy link
Member Author

jklymak commented Jun 22, 2021

... of course this turned out more complicated for some of the reasons I think @greglucas found - in particular axes_grid had a pretty strange atypical way of adding colorbar axes. Basically the axes called a colorbar method on itself, which is exactly the opposite way the main library works, where the colorbar takes an axes and draws itself there. So the extra change here is to add the colorbar the usual way in that method. I may have been too cavalier in the ripping out untested axes_grid stuff, but at some point I despair of keeping incompatible convenience additions around unless they have tests...

@jklymak
Copy link
Member Author

jklymak commented Jun 22, 2021

... 98.5% sure CI failures are unrelated to this PR (see #20487)

Copy link
Contributor

@greglucas greglucas left a comment

Choose a reason for hiding this comment

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

Bummer you ran into that here too! I didn't realize the AxesStack wasn't being updated, but this actually simplifies a lot of the interactive code I was working on as well, so thanks for this!

I never did get up to speed on the axes_grid implementation, but making it more similar to a normal colorbar seems good to me. I guess I'd suggest that piece being a separate PR (or at least a separate commit) solely focused on that piece.

else:
outer_ax = parent

# swap axes in the stack if its in there:
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure how it wouldn't be there if you've created it above or gotten it passed in? But, wouldn't you want to add(self) regardless of whether the outer one is there or not?

Copy link
Member Author

Choose a reason for hiding this comment

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

There was some test where we made a colorbar and it didn't actually ever get added. I can take ti out again to figure out which one...

for attr in ["get_position", "set_position", "set_aspect"]:
for attr in ["get_position", "set_aspect",
"_remove_method", "_set_position",
"set_position", "cla", "draw"]:
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm still not sure about these only being mapped to the outer_ax... I would have thought it should be something like:

def draw(self, renderer):
    super().draw(renderer)
    self.outer_ax.draw(renderer)

so that you draw both the inner and outer? Same for the cla and remove. The position's I think are appropriate to map straight to the outer though.

Copy link
Member Author

Choose a reason for hiding this comment

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

inner is an artist on outer, so inner gets drawn if outer gets drawn ;-)

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh yeah, it is an inset of the outer! Makes sense now, thanks for clarifying.

if userax:
self._colorbar_info = 'user'
# point the parent's methods all at this axes...
origdict = parent.__dict__
parent.__dict__ = self.__dict__
Copy link
Contributor

Choose a reason for hiding this comment

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

can you just update it?

Suggested change
parent.__dict__ = self.__dict__
parent.__dict__.update(self.__dict__)

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, maybe? Thanks!

Copy link
Member Author

Choose a reason for hiding this comment

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

No, sadly not. I'll admit I'm probably not enough of a pythionista to dig around in the guts like this. Its pretty apparent to me that this copies all the attributes over, but if you iterate through __dict__ like a normal dictionary it does not capture everything in __dict__. Its all a little confusing. If someone has a more elegant less tacky way to do this, we can certainly try that approach...

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe try {**parent.__dict__, **self.__dict__} which would produce a new dict...

@jklymak
Copy link
Member Author

jklymak commented Jun 22, 2021

@greglucas thanks for taking a look. The problem with making the axes grid stuff a different PR is that of course the changes needed to get picking to work break axes_grid. (I honestly didn't set out to fix axes grid ;-)

@jklymak
Copy link
Member Author

jklymak commented Jun 22, 2021

I guess the other approach here is rather than create a new container axes to just attach the stuff we want to the existing axes, and re-direct the appropriate attributes to the inner axes, keeping the outer axes as similar as possible. I avoided that route because I think far more of the "inner" methods are user-facing, but it certainly is difficult to do this dance robustly.

@jklymak
Copy link
Member Author

jklymak commented Jun 23, 2021

OK, having thought about this a lot of the day, here is another approach:

Old, we did a bunch of drawing by hand
master: we replace the users cax in colorbar(im, cax=cax) by a new ColorbarAxes and have a cb.ax.inner_ax and cb.ax.outer_ax. This has the disadvantage of assert cb.ax == cax failing, and is somewhat frail, as we feared.

New proposal: instead of trying to replace cax passed to us, we simply keep it, so now assert cb.ax == cax is true. The colorbar gains a new attribute cb.inner_ax that does the actual drawing etc except for the extend regions, and to make things easier on the user we dispatch many user-facing methods from cb.ax.set_xlabel to cb.inner_ax.set_xlabel. This is basically the same as the old changes but architected in a way that makes the user-supplied axes behave more like it used to (ideally exactly like it used to). The only disadvantage is that internally we need to be careful to call colorbar.inner_ax.add_patch etc instead of colorbar.ax.add_patch. But since thats internal, it should be easy enough to handle.

@QuLogic QuLogic mentioned this pull request Jun 23, 2021
7 tasks
@jklymak
Copy link
Member Author

jklymak commented Jun 23, 2021

Closing for #20501

@jklymak jklymak closed this Jun 23, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ColorbarAxes is an imperfect proxy for the Axes passed to Colorbar
2 participants