8000 MNT: colorbar locators properties by jklymak · Pull Request #22244 · matplotlib/matplotlib · GitHub
[go: up one dir, main page]

Skip to content

MNT: colorbar locators properties #22244

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 6 commits into from
Jan 22, 2022

Conversation

jklymak
Copy link
Member
@jklymak jklymak commented Jan 16, 2022

PR Summary

This is to prepare to address #22233

Currently the user can set the colorbar locator and formatter by direct access to the properties cb.formatter, cb.locator, and cb.minorlocator. However, they can also set cb.ax.yaxis.set_major_locator etc.

This moves the attributes to properties with setters and getters. The advantage of this is keeping the attributes in sync, but also we can track if the user set the locators via the properties. If not, then we assume we can replace the locators and formatters with defaults.

This does not catch if the user set the locator and formatter using cb.ax.yaxis.*, so we may still have some things to figure out there.

PR Checklist

Tests and Styling

  • Has pytest style unit tests (and pytest passes).
  • Is Flake 8 compliant (install flake8-docstrings and run flake8 --docstring-convention=all).

Documentation

  • New features are documented, with examples if plot related.
  • 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).
  • Documentation is sphinx and numpydoc compliant (the docs should build without error).

@jklymak jklymak force-pushed the mnt-colorbar-locators-properties branch from 2d1c72c to b65ec3c Compare January 16, 2022 10:42
@jklymak
Copy link
Member Author
jklymak commented Jan 16, 2022

Q: Should we also have cb.set/get_major/minor_locator/formatter or should we simply be encouraging the cb.ax.yaxis.set_* methods?

Comment on lines 515 to 517
"""
Major `~.Locator` being used for colorbar.
"""
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"""
Major `~.Locator` being used for colorbar.
"""
"""The major tick `.Locator` of the colorbar."""
  • If there is just one line, we put the quotes on the same line https://matplotlib.org/devdocs/devel/documenting_mpl.html#quote-positions
  • ~ removes leading package/module names from links, but here you don't have any, so we can leave it out, which makes the code a bit more readable.
  • "being used" does not add information -> can be left out.
    Similar for all getters.
  • I've added "tick Locator", while locators are a technical term and you can find out through the link, it may be worth associating all these properties with ticks explicitly.

Copy link
Member Author

Choose a reason for hiding this comment

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

Went for Major tick .Locator for the colorbar

@timhoffm
Copy link
Member

Q: Should we also have cb.set/get_major/minor_locator/formatter or should we simply be encouraging the cb.ax.yaxis.set_* methods?

IMHO we should not encourage cb.ax.yaxis. 1) yaxis/xaxis depends on the orientation, which is not a nice API 2) This goes deep into the internals of colorbars. If people use this widely, we'll get into troubles in case we want to change the implementation.

I'm not quite sure whether we need the suggested cb.set_* methods. We have the properties now, so technically we don't need additional API. However, I see the point that all other places use the setter/getter philosophy, so for consistency we should have setters and getters. One idea could be to make the long axis accessible via a property, e.g. as cb.long_axis or even only cb.axis (from a user perspective Colorbar has just one axis). - I don't know if exposing the axis completely is an issue. If so, we can make a proxy axis object instead that only provides certain axis operations. Effectively, this gives you namespacing, so that you'll have cb.axis.set_major_locator, which is a quite nice and clean API.

@jklymak
Copy link
Member Author
jklymak commented Jan 17, 2022

or even only cb.axis (from a user perspective Colorbar has just one axis)

I don't think there is a problem with that. However, I worry that for a normal axes ax.yaxis.* is already obscure enough. Now the user needs to know that there is a cb.axis property to operate on the colorbar axis. I think discoverability is definitely an issue there. Note that we currently have cb.long_axis(), but as a method.

if people use this widely, we'll get into troubles in case we want to change the implementation.

I don't know that it would make sense to go backwards on this. The point of the recent work is so that colorbar axes do not have their own incompatible ticking system. OTOH I am sympathetic to the idea that changing the location of the colorbar should not

@timhoffm
Copy link
Member

or even only cb.axis (from a user perspective Colorbar has just one axis)

I don't think there is a problem with that. However, I worry that for a normal axes ax.yaxis.* is already obscure enough. Now the user needs to know that there is a cb.axis property to operate on the colorbar axis. I think discoverability is definitely an issue there. Note that we currently have cb.long_axis(), but as a method.

I consider ax.yaxis.* a desirable API. yaxis gives you a namespace for aggregating all related functionality. While Axes-xy-methods (e.g. set_xlim) are handy, it's not practical to map all the complex Axis functionality that way.

I was somehow thinking of Colorbar as an Axes and then we'd have a strong analogy that both should have axises. I.e. if I have ax.xaxis.set_major_formatter, I'd follow to cb.axis.set_major_formatter. So it's not that clear. OTOH, even if Colorbar is not an Axes it might be helpful to model certain API aspects analogously, in particular an axis namespace.
However, I also see an argument for keeping everything flat. Which one is better will depend on a) the amount of Axis methods we'd have to remap and b) the amount of other functions in Colorbar and c) how close the API of Colorbar already is to Axes- this is mainly a concern of having a reasonable Colorbar namespace.

Note that _long_axis() is a private method. If we plan to make that public, that should be via an cb.axis property, not via a method.

@jklymak
Copy link
Member Author
jklymak commented Jan 19, 2022

Right @greglucas even wanted to make a colorbar be a subclass of Axes, and it wasn't a ton of work.

As a medium solution, I'm more than fine w/ cb.axis being a property. We already have cb.set_ticks and cb.minorticks_on, so allowing cb.axis seems consistent.

I think that is a little orthogonal to this (we need to maintain the locator/formatter properties anyways as they currently exist), but happy to put in the same PR if that is easier.

@timhoffm
Copy link
Member

Right @greglucas even wanted to make a colorbar be a subclass of Axes, and it wasn't a ton of work.

That's not the right way, but discussed elsewhere. For now, let's just have a mental model of the similarity and define analogous methods where appropriate.

As a medium solution, I'm more than fine w/ cb.axis being a property. We already have cb.set_ticks and cb.minorticks_on, so allowing cb.axis seems consistent.

👍

I think that is a little orthogonal to this (we need to maintain the locator/formatter properties anyways as they currently exist), but happy to put in the same PR if that is easier.

Yes, we need the properties for compatibility. Everything else can be separate PR. We got a bit side-tracked by your question on the methods. Note that there are still two comments open above.

@jklymak jklymak force-pushed the mnt-colorbar-locators-properties branch from 410ad06 to f3bba62 Compare January 20, 2022 08:25
@jklymak jklymak force-pushed the mnt-colorbar-locators-properties branch from f3bba62 to 13b4167 Compare January 20, 2022 10:51
@jklymak jklymak force-pushed the mnt-colorbar-locators-properties branch from 13b4167 to 29ef83e Compare January 20, 2022 10:55
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.

Seems reasonable to me. I just had one small comment on the new _minorformatter private variable.

Some comments for potential follow-up PRs, but shouldn't impact this one.

  • This had me confused why we even need to keep track of the Nones at all. I'm wondering if we can just dispatch the property out to the long-axis get/set methods without needing to keep track of the private property. I think the None is only needed to set the default cases, so I'm wondering if that can be handled elsewhere...
  • @timhoffm I like the idea of making a cb.axis property, that does seem nice and clean to me for colorbars. One additional consideration would be whether it would be useful to treat this as a "spines"-like API. So you could do ax.axis["x"], ax.axis["r"], ax.axis["all"], ... (thinking more generally than just colorbar)
  • One slight API mismatch here is that we have "locator"/"formatter" on Colorbars, without the "major" qualifier, but the underlying setters/getters use set_major_locator instead of set_locator. So, something to keep in mind here is that I think Colorbar is the only place without the major qualifier compared to the rest of the library.

self._locator = None
self._minorlocator = None
self._formatter = None
self._minorformatter = None
Copy link
Contributor

Choose a reason for hiding this comment

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

The None here doesn't get used anywhere below to update a default? It also doesn't get reset in the _reset_locator_formatter_scale() call, so I'm wondering if we need the _minorformatter private variable, or if you can simply call the the get/set methods on the long-axis from the new property.

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 haven't traced minor formatter all the way through the piping yet, however, I added it to be parallel with the other properties. I get a little nervous about how often the colorbar changes state, particularly now that it seems to change state if the mappable's norm is changed ;-) If I change the norm on the mappable, but I have manually set locator, should that stay or should we overwrite it? I think now it stays unless self._locator is None, in which case we use the default appropriate to the norm. We could simplify our lives by saying that any norm changes invalidates any manual setting of the colorbar locators/formatters, and that the user would have to redo those. I think that is what happens if a user changes the scale of an axis, so it would be consistent here.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree that all of this is quite confusing with when things are validated/invalidated in the Colorbar pipeline. What I was trying to get at here was only for the minorformatter. I think you should also set it to None down below too where the other locators/formatters are also reset, since you are now keeping track of it.

@timhoffm
Copy link
Member

One additional consideration would be whether it would be useful to treat this as a "spines"-like API. So you could do ax.axis["x"], ax.axis["r"], ax.axis["all"], ... (thinking more generally than just colorbar)

@greglucas Thanks for the suggustion! I haven't thought about that possibility before. It's always good to get fresh ideas. However, from a practical point of view I have some doubts:

  • ax.axis["x"] is significantly more complicated to type than ax.xaxis, and it cannot be completed.
  • In contrast to spines ax.axis["all"] seems less needed. Axises are more individual than spines, typically your settings are related to data, e.g. formatters and locators. Note also that there is ax.tick_params() which can handle a lot of formatting possibly applying to both axes.
  • Spines were a plain dict before, so item-access was the natural way to go. Here, we currently have xaxis/yaxis. We'd need a compelling reason to add item access.

@jklymak
Copy link
Member Author
jklymak commented Jan 21, 2022

So, something to keep in mind here is that I think Colorbar is the only place without the major qualifier compared to the rest of the library.

Yes, I would suggest we discourage the properties, and encourage cb.axis.set_major/minor_locator/formatter once we have the axis property put in.

@jklymak jklymak added this to the v3.6.0 milestone Jan 21, 2022
self.formatter = None
self._locator = None
self._minorlocator = None
self._formatter = None
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
self._formatter = None
self._formatter = None
self._minorformatter = None

9E88
self._locator = None
self._minorlocator = None
self._formatter = None
self._minorformatter = None
Copy link
Contributor

Choose a reason for hiding this comment

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

I agree that all of this is quite confusing with when things are validated/invalidated in the Colorbar pipeline. What I was trying to get at here was only for the minorformatter. I think you should also set it to None down below too where the other locators/formatters are also reset, since you are now keeping track of it.

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.

4 participants
0