Skip to content

Colorbar redo again! #20501

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
Jul 8, 2021

Conversation

jklymak
Copy link
Member

@jklymak jklymak commented Jun 23, 2021

PR Summary

OK, third time is the charm.

Previous overhaul packaged an inner and outer axes in a container "ColorbarAxes" and tried to dispatch methods between them.

This overhaul takes the much simpler approach of resizing the image using a custom _axes_locator that a) calls any existing locator b) or just uses the axes default position. The custom _axes_locator then shrinks the axes in the appropriate direction to make room for extend tri/rectangles. As with the previous fix, the extend tri/rectangles are drawn as patches in axes co-ordinates, rather than pcolormesh in "data" co-ordinates.

This addresses the concerns and closes #20479

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
Copy link
Member Author

jklymak commented Jun 24, 2021

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()

works fine on this PR.

@jklymak jklymak force-pushed the colorbar-try3-justmod-transform branch 3 times, most recently from 0370b32 to 735de0d Compare June 24, 2021 03:28
@@ -2369,6 +2369,8 @@ def _update_patch_limits(self, patch):
return
patch_trf = patch.get_transform()
updatex, updatey = patch_trf.contains_branch_seperately(self.transData)
if not (updatex or updatey):
return
Copy link
Member Author

Choose a reason for hiding this comment

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

Calling the transform below blows up needlessly because it is called, and then does nothing....

@jklymak jklymak force-pushed the colorbar-try3-justmod-transform branch 2 times, most recently from eab1f92 to 32b38d2 Compare June 24, 2021 05:17
@jklymak jklymak added this to the v3.5.0 milestone Jun 24, 2021
@jklymak jklymak added the Release critical For bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions. label Jun 24, 2021
@jklymak jklymak marked this pull request as ready for review June 24, 2021 05:20
@jklymak jklymak force-pushed the colorbar-try3-justmod-transform branch from 32b38d2 to 561d771 Compare June 24, 2021 14:32
@jklymak jklymak mentioned this pull request Jun 24, 2021
7 tasks
@jklymak
Copy link
Member Author

jklymak commented Jun 24, 2021

... interesting wrinkle. If an axes has an _axes_locator that is not None, tight_layout decides it can't handle it. Here the locator only is supposed to modify things in the direction perpendicular to the one tight_layout needs to worry about, so presumably it should be fine, but the warning still persists and breaks the docs build.

@greglucas greglucas linked an issue Jun 26, 2021 that may be closed by this pull request
@greglucas
Copy link
Contributor

I have to say, this was actually fewer image changes than I was expecting :)
I have a bunch of small nitpicks in there, but overall I think this is a great simplification and really makes the logic easier to understand what is going on than the multi-axes dispatching.

np.testing.assert_allclose(cb.ax.get_position().extents,
[0.78375, 0.536364, 0.796147, 0.9], rtol=2e-3)


Copy link
Contributor

Choose a reason for hiding this comment

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

Surely this test was there for a reason, especially saying it was a "funny" error. Makes it seem like this should be investigated and updated rather than removed.

Copy link
Member Author

Choose a reason for hiding this comment

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

This was testing the extend length being correct (its my test). We could go ahead and keep testing this, but it'd properly require an image test.

except TypeError:
pass
lim = self._cbar._long_axis().get_view_interval()
self._cbar._short_axis().set_view_interval(*lim)
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you also want to update the data interval of the short axis here?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not sure what you mean? We need to do this to make the aspect ratio of the colobar correct.

Copy link
Member Author

Choose a reason for hiding this comment

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

OK, this has been removed. See comments

if self._cbar.orientation == 'vertical':
if aspect:
ax.set_aspect(aspect*shrink)
offset = offset * pos.height
Copy link
Contributor

Choose a reason for hiding this comment

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

*= or even just explicitly put offset * pos.height into the translated call below.


# make the inner axes smaller to make room for the extend rectangle
top = elower + inner_length
top = eupper + 1
Copy link
Contributor

Choose a reason for hiding this comment

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

For consistency I'd suggest either defining bot = -elower here and using that below or using 1 + eupper in the calculations.

self.ax.outer_ax.add_patch(patch)
antialiased=False, transform=self.ax.transAxes,
hatch=hatches[0], clip_on=False)
self.ax.add_patch(patch)
Copy link
Contributor

Choose a reason for hiding this comment

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

Because you're setting clip_on=False here, do you need to adjust the zorder of the patch at all so that it is behind the main axes?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think this is as it was before?

Copy link
Member

Choose a reason for hiding this comment

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

This seems reasonable with the layer between the patch, the outline, and then the fill-ins for the extensions.

We do not keep a (named) reference to these patches, what do we do if we update the colormap?

Comment on lines 90 to 93
Colorbar(cax, cmap=cmap, norm=norm,
boundaries=boundaries, values=values,
extend=extension_type, extendfrac=extendfrac,
orientation='horizontal', spacing=spacing)
Copy link
Member

Choose a reason for hiding this comment

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

Dedent these also.


def __call__(self, ax, renderer):

# make sure that lims and scales are the same
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure why this is necessary; I understand from some comments that this is due to aspect ratio handling?

Copy link
Member Author

Choose a reason for hiding this comment

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

Right, the aspect of the axes is presently set via the usual ax.set_aspect, but that requires the limits and scales of the short and long axes to be the same.

However I'm starting to think that is a mistake, and we should perhaps just use something like set_box_aspect and get around all this awkwardness. Or just do it with the new colorbar axes locator.

Copy link
Member Author

Choose a reason for hiding this comment

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

See comments, this has all be re-engineered.

@jklymak jklymak force-pushed the colorbar-try3-justmod-transform branch from 182a488 to b573664 Compare June 30, 2021 20:56
@jklymak
Copy link
Member Author

jklymak commented Jun 30, 2021

latest commit changes the aspect ratio handling from a data-aspect ratio to a box aspect ratio. The "short" side of the colorbar now always has a linear scale and limits from 0-1, and only the "long" side gets a scale and variable data limits. This simplifies a lot of code..

Note I've added a colorbar.set_aspect and colorbar.set/get_scale. These will need whats-new entries (done)

Comment on lines -1468 to +1466
cax.set_aspect(aspect, anchor=loc_settings["anchor"], adjustable='box')
cax.set_anchor(anchor)
Copy link
Member

Choose a reason for hiding this comment

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

Was loc_settings["anchor"] an error?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I assume that anchor was just ignored if passed as a kwarg. I'm not sure what happens if it is passed as a kwarg.

Copy link
Member

Choose a reason for hiding this comment

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

Strange, it should just go through to set_anchor if it's not None.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think what is happening here is correct, and the old code was ignoring the user's specification of anchor, but maybe I'm not following...

@greglucas
Copy link
Contributor

Are the set_scale and set_aspect really necessary public functions on Colorbar and not just on the internal axis?

set_aspect: This is generally on an Axes, so why not leave it on cbar.ax and let the new locator work on that?

set_scale: I feel like this could cause confusion. The norm of the colorbar can be out of sync with the scale now? If I call cbar.set_scale('log')`, the ticks would be located in log-space, but the color normalization still done in linear space I believe.

@jklymak
Copy link
Member Author

jklymak commented Jul 1, 2021

Are the set_scale and set_aspect really necessary public functions on Colorbar and not just on the internal axis?

set_aspect: This is generally on an Axes, so why not leave it on cbar.ax and let the new locator work on that?

This is the same as colorbar(..., aspect=20), so it really is a colorbar method. The equivalent axes method is set_box_aspect, and indeed set_aspect will not work.

set_scale: I feel like this could cause confusion. The norm of the colorbar can be out of sync with the scale now? If I call cbar.set_scale('log')`, the ticks would be located in log-space, but the color normalization still done in linear space I believe.

I needed to make this method, and didn't see that it needed to be private, but I don't feel strongly about it. Indeed it will only change the scale and not have anything to do with the norm's scale.

@greglucas
Copy link
Contributor

Your new AxesLocator is the only thing calling cbar.set_aspect though because you just put it in. I'm suggesting that you can put these two calls up in the locator instead, without introducing new public API. (You added these two calls into ConstrainedLayout already, so I think the same idea could be applied in the Locator)

        self.ax.set_box_aspect(aspect)
        self.ax.set_aspect('auto')

Indeed it will only change the scale and not have anything to do with the norm's scale.

This seems like a real reason not to make this public. In your calls to self.set_scale(), you already have the long axis attribute above, so why not call self._long_axis().set_scale() there instead of introducing the new API?

@jklymak
Copy link
Member Author

jklymak commented Jul 1, 2021

I'm suggesting that you can put these two calls up in the locator instead, without introducing new public API.

Absolutely. I'm just suggesting it may be more-generally useful as well to be able to set this after creating the colorbar, so why not expose it?

so why not call self._long_axis().set_scale() there instead of introducing the new API?

Yes, there is no reason for it to be public, other than user convenience. As I said, I don't feel super strongly about exposing this, but we have had requests to change the scale without changing the norm. For instance, it is not completely unreasonable to have a linear scale and a logarithmic norm.

@jklymak jklymak force-pushed the colorbar-try3-justmod-transform branch from 514192f to e391e62 Compare July 1, 2021 16:44
@jklymak jklymak force-pushed the colorbar-try3-justmod-transform branch from 70621cd to 5a5d1bb Compare July 6, 2021 00:32
Comment on lines -1468 to +1466
cax.set_aspect(aspect, anchor=loc_settings["anchor"], adjustable='box')
cax.set_anchor(anchor)
Copy link
Member

Choose a reason for hiding this comment

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

Strange, it should just go through to set_anchor if it's not None.

@jklymak jklymak force-pushed the colorbar-try3-justmod-transform branch from f0ca1e0 to 914b750 Compare July 6, 2021 13:42
@jklymak jklymak mentioned this pull request Jul 7, 2021
7 tasks
@QuLogic
Copy link
Member

QuLogic commented Jul 7, 2021

I assume you'll fix the last thing.

@jklymak
Copy link
Member Author

jklymak commented Jul 7, 2021

@QuLogic, sorry, what "last thing"? The anchor passthrough? I think the new code is correct...

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.

I think this is good to go as well and the other issues with the norms/scales can be addressed in later PRs. Thanks for the updates and separating things out @jklymak! I'll let @QuLogic merge once he feels his comment is resolved.

@QuLogic
Copy link
Member

QuLogic commented Jul 7, 2021

@QuLogic, sorry, what "last thing"? The anchor passthrough? I think the new code is correct...

#20501 (comment) longax wasn't removed.

@jklymak jklymak force-pushed the colorbar-try3-justmod-transform branch 2 times, most recently from 8caf7dd to 7884314 Compare July 7, 2021 20:08
Copy link
Member

@tacaswell tacaswell left a comment

Choose a reason for hiding this comment

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

Anyone can merge on CI green!

@jklymak jklymak force-pushed the colorbar-try3-justmod-transform branch from 7884314 to b574177 Compare July 7, 2021 20:44
Previous overhaul packaged an inner and outer axes in a container
"ColorbarAxes" and tried to dispatch methods between them.

This overhaul takes the _much_ simpler approach of resizing the
image using a custom _axes_locator that a) calls any existing locator
b) or just uses the axes default position. The custom _axes_locator
then shrinks the axes in the appropriate direction to make room for
extend tri/rectangles.  As with the previous fix, the extend
tri/rectangles are drawn as patches in axes co-ordinates, rather than
pcolormesh in "data" co-ordinates.
@jklymak jklymak force-pushed the colorbar-try3-justmod-transform branch from b574177 to 277b5eb Compare July 7, 2021 20:46
@jklymak jklymak merged commit 4d1cd5e into matplotlib:master Jul 8, 2021
@jklymak jklymak deleted the colorbar-try3-justmod-transform branch July 8, 2021 00:12
@jklymak
Copy link
Member Author

jklymak commented Jul 8, 2021

Thanks @QuLogic and @greglucas for all your help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Release critical For bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions. topic: color/colorbar
Projects
None yet
Development

Successfully merging this pull request may close these issues.

matplotlib 3.5 breaks yt ColorbarAxes is an imperfect proxy for the Axes passed to Colorbar
5 participants