Skip to content
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

[ENH]: Use new style format strings for colorbar ticks #21542

Merged
merged 2 commits into from Nov 9, 2021

Conversation

vfdev-5
Copy link
Contributor

@vfdev-5 vfdev-5 commented Nov 4, 2021

Fixes #21378

PR Summary

This PR introduces formatter as property to fetch axis format and thus new style string formatter is used
If applying a similar logic to major and minor locators, we can end up with removing the code from update_ticks.
Tricky part is with self.norm.
If we are ok with this solution, I can work on doing the same for locator and minorlocator.

Context: PR from PyData Global 2021 mentored sprint

cc @tacaswell

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

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Thank you for opening your first PR into Matplotlib!

If you have not heard from us in a while, please feel free to ping @matplotlib/developers or anyone who has commented on the PR. Most of our reviewers are volunteers and sometimes things fall through the cracks.

You can also join us on gitter for real-time discussion.

For details on testing, writing docs, and our review process, please see the developer guide

We strive to be a welcoming and open project. Please follow our Code of Conduct.

@tacaswell tacaswell added this to the v3.6.0 milestone Nov 4, 2021
Copy link
Member

@timhoffm timhoffm left a comment

Choose a reason for hiding this comment

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

Thanks for the contribution!

There are two things to watch out for:

  • A hard change form %-style to to new-style for the the format parameter would be a breaking change. We cannot do that because users would have to stictly adapt their code with the changed Matplotlib version, which would be really annoying.

    What you have to do is accept new style format strings along-side %-style.

  • Mapping formatter by a property is a separate topic. I suggest to follow up on this in a separate PR if you are interested. Deduplicating state by using a property instead of holding the formatter in self.formatter and in the long axis seems a reasonable approach, however there are some things to watch out for:

    • You'll loose the formatter with your approach if you switch the orientation. I'm not quite sure if switching the orientation works at all, but if so that's a de-facto feature that we have to continue to support.

    • As implemented here, this would allow for code like

      >>> cbar.formatter = "{x:.3f}"
      >>> cbar.formatter
      <StrMethodFormatter>
      

      This asymmetry is a somewhat unexpected behavior for a property, and I'm not sure, we want to allow that.

    • The same mechanism is used for locator and minorlocator. If we switch to properties all three should be changed together.

@jklymak
Copy link
Member

jklymak commented Nov 5, 2021

The long-term goal is to make colorbar axises as similar as possible to normal axises. Ideally colorbar.locator and colorbar.formatter would just be dumb reflections of what is on the long axis.

@vfdev-5
Copy link
Contributor Author

vfdev-5 commented Nov 5, 2021

@timhoffm thanks for the review! Yes, I agree that the way it is done here is unaffordable breaking change.

What you have to do is accept new style format strings along-side %-style.

So, the idea is to revert the changes and do something like

        if isinstance(format, str):
            # Check format type between FormatStrFormatter and StrMethodFormatter
            try:
                self.formatter = ticker.FormatStrFormatter(format)
                _ = self.formatter(0)
            except TypeError:
                self.formatter = ticker.StrMethodFormatter(format)
        else:
            self.formatter = format  # Assume it is a Formatter or None

?

Mapping formatter by a property is a separate topic. I suggest to follow up on this in a separate PR if you are interested.

Yes, I can send other PRs with that however I do not quite understand the whole picture of what it can break etc (some guidance could be needed).

You'll loose the formatter with your approach if you switch the orientation. I'm not quite sure if switching the orientation works at all, but if so that's a de-facto feature that we have to continue to support.

Could you please explain what it the switch of the orientation (with some code if possible) ? Thanks

The long-term goal is to make colorbar axises as similar as possible to normal axises.

Yes, Thomas said something like that during the mentored sprint and that's why I went using property.

@timhoffm
Copy link
Member

timhoffm commented Nov 7, 2021

So, the idea is to revert the changes and do something like ...

Yes.

Could you please explain what it the switch of the orientation (with some code if possible)

I thought you could swich the orienation by assigning to Colorbar.orientation

from matplotlib.ticker import PercentFormatter

plt.imshow(np.random.random((3, 3)))
cb = plt.colorbar(format=PercentFormatter(), orientation='vertial')
cb.orientation = 'horizontal'

but apparently, this does not have any effect.

Yes, I can send other PRs with that however I do not quite understand the whole picture of what it can break etc (some guidance could be needed).

Currently, Colorbar holds the state and pushes the information down to the respective axis when needed; e.g.

self._long_axis().set(label_position=self.ticklocation,
ff.

This is safe against operations like changing orientation (i.e. long and short axis) or clearing of an axis (I don't know that is happens, but would be ok in the current scenario). If the axis holds the state instead, we have to make sure that it's really kept consistent, which could get messed up if long and short axis are exchanged.
I don't know if any of these are practical problems, but that needs to be investigated if we move the responsibility for the state.

@jklymak
Copy link
Member

jklymak commented Nov 8, 2021

First, I think changing the orientation of a colorbar after creating it is a non-starter, and certainly not something we have done previously. cb.orientation should probably be read-only somehow, but...

For this PR, we have two separate issues that we are looking at:

  1. if the user passes "format" to the instantiation, it could accept the "new" formatting specifier: I think this is fine, but we can't stop accepting the old format specifier

  2. setter and getter for the cb.format property. This seems helpful in that it is getting and setting from the long_axis. However, I wonder if we also want to rename this so that it is clear that we are really doing cb.set_major_formatter? Or trying to discourage operating on the colorbar directly at all. Where I get confused and have to look carefully each time, is how much formatter/locator logic needs to sit at the colorbar level because of the norm. ie. do we even want users doing cb._long_axis.set_major_formatter()?

Given that these are pretty orthogonal, I agree with @timhoffm that this should maybe be made into two PRs, with 1) being a lot easier than 2)!

both StrMethodFormatter and FormatStrFormatter if format is str
@vfdev-5
Copy link
Contributor Author

vfdev-5 commented Nov 8, 2021

@timhoffm @jklymak thanks for the details and the info. I reverted the code and added as proposed in a previous message a way to switch between 2 formatters if input format is str. Updated also the tests. Please let me know if this is sufficient for the PR "1". Thanks!

Copy link
Member

@timhoffm timhoffm left a comment

Choose a reason for hiding this comment

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

Module fixing flake8.

@jklymak jklymak merged commit a3ba7cf into matplotlib:main Nov 9, 2021
@jklymak
Copy link
Member

jklymak commented Nov 9, 2021

Thanks @vfdev-5 ! And thanks for participating in the sprint!

@vfdev-5 vfdev-5 deleted the fix-21378 branch November 9, 2021 10:40
@vfdev-5
Copy link
Contributor Author

vfdev-5 commented Nov 9, 2021

@jklymak I'd like to provide a follow-up PR with setter/getter property for cb.format and maybe other attributes. Should we discuss the scope somewhere in a new issue or I can send a draft PR and we can affine it there ?

@jklymak
Copy link
Member

jklymak commented Nov 9, 2021

I usually prefer a draft PR, even if its just a straw man for discussion, so long as it is not too much work

@timhoffm
Copy link
Member

This should still get a what's new entry.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[ENH]: use new style format strings for colorbar ticks
4 participants