The Wayback Machine - https://web.archive.org/web/20230105214739/https://github.com/matplotlib/matplotlib/pull/24666
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

Setting color of legend shadow #24666

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open

Conversation

tuncbkose
Copy link

@tuncbkose tuncbkose commented Dec 8, 2022

PR Summary

Allows shadow legend parameter to be a color.
As requested by #24663, revival of #18668, based on previous work by @Tranquilled.

PR Checklist

Documentation and Tests

  • Has pytest style unit tests (and pytest passes)
  • Documentation is sphinx and numpydoc compliant (the docs should build without error).
  • [?] New plotting related features are documented with examples.
    • The change is relatively small, but I can add examples if requested.

Release Notes

  • New features are marked with a .. versionadded:: directive in the docstring and documented in doc/users/next_whats_new/
  • API changes are marked with a .. versionchanged:: directive in the docstring and documented in doc/api/next_api_changes/
  • Release notes conform with instructions in next_whats_new/README.rst or next_api_changes/README.rst

Copy link

@github-actions github-actions bot left a comment

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.

@tuncbkose tuncbkose marked this pull request as ready for review Dec 8, 2022
lib/matplotlib/legend.py Outdated Show resolved Hide resolved
@@ -480,6 +482,11 @@ def val_or_rc(val, rc_name):
self._mode = mode
self.set_bbox_to_anchor(bbox_to_anchor, bbox_transform)

# Figure out if self.shadow is valid

if not (is_color_like(self.shadow) or isinstance(self.shadow, bool)):
Copy link
Member

@tacaswell tacaswell Dec 8, 2022

Choose a reason for hiding this comment

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

I'm a little wary of going to strict type checking, there is a strong expectations that 0 and 1 will work basically anyplace True / False will.

Copy link
Author

@tuncbkose tuncbkose Dec 8, 2022

Choose a reason for hiding this comment

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

I am under the impression that 0 and 1 are all one might expect to work, as opposed to options like "true", "false" as in validate.bool in rcsetup.py.
Do you think it is sufficient to change isinstance to shadow in (0, 1, True, False)?

Copy link
Member

@timhoffm timhoffm Dec 9, 2022

Choose a reason for hiding this comment

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

@tacaswell I'm somewhat annoyed by the 0, 1 topic.

In many cases (also here before the PR), we do implicit bool-casting via if shadow:. That means users can pass 0 or [] or whatever they like. I don't think we give a guarantee that these will work in the future.

In my personal opinion using 0, 1 as a bool substitute is an anti-pattern that reduces readability. (e.g. here it could also be the size of the shadow). While I don't want to dictate how people are using python. I don't think we need to jump extra hoops to support unreasonable behavior. Anyway, what I definitively don't want is cluttering our code internally with 0, 1 checks.

We should

  • either stay as permissive as before (i.e. do not do any check here), which is somewhat problematic as mistyped color strings are interpreted as True
  • or do the strict type checking

If you insist that 0, 1 is really worth supporting, we should have a general discussion on that in the dev call. We'd need to document what exactly we want to accept in such cases in the dev guidelines. And we should come up with supporting functionality like is_bool_like().

On a more general note:

Are we sure that the shadow=[color] extension is the right API? It is ok if we are quite sure there won't be additional parameters in the future. However, if we think there will be other parameters like size or blur, we should go for another API. One way would be to bool or dict so that you can do shadow={'color': 'red', 'size'=5}, which is probably better than introducing additional parameters.

Copy link
Contributor

@anntzer anntzer Dec 14, 2022

Choose a reason for hiding this comment

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

I would suggest the correct way to check for T/F is perhaps something like np.ndim(x) == 0 and (self.shadow == False or self.shadow == True)? (where the np.ndim check is only to avoid problems with array-equality returning bool arrays that cannot be used in a bool scalar context). This way you take advantage of the fact that True == 1 and False == 0 (a fact that also makes supporting T/F but not 0/1 a bit weird; note that the same argument does not apply to other truthy or falsy values).

lib/matplotlib/legend.py Outdated Show resolved Hide resolved
@oscargus oscargus added this to the v3.7.0 milestone Dec 8, 2022
lib/matplotlib/rcsetup.py Outdated Show resolved Hide resolved
@oscargus
Copy link
Contributor

oscargus commented Dec 14, 2022

Failure seems unrelated.

I am wondering if one should replace one of the legends (or possibly add one in the middle) that uses the rcParams?

Regarding the 0/1 issue, I'll add that to the agenda for the dev call tomorrow.

@oscargus
Copy link
Contributor

oscargus commented Dec 14, 2022

Oh, and if you know how to rebase and squash, please do that.

It would also make sense to edit the commit message and add the line (after two empty lines):

Co-authored-by: Tranquilled (or proper name)  <whatever the email is>

@tuncbkose
Copy link
Author

tuncbkose commented Dec 16, 2022

I don't know how to rebase and squash, but I can probably figure it out. And add the coauthor message too.

Are there any updates on the 0/1 issue that warrants changing the code here?

@rcomer
Copy link
Member

rcomer commented Dec 16, 2022

@tuncbkose we have instructions for squashing here:
https://matplotlib.org/devdocs/devel/development_workflow.html#rewriting-commit-history

@tuncbkose tuncbkose requested a review from tacaswell Dec 23, 2022
@rcomer rcomer linked an issue Dec 23, 2022 that may be closed by this pull request
@tacaswell
Copy link
Member

tacaswell commented Jan 5, 2023

Thank you for your work on this @tuncbkose however I'm going to push this to 3.8. There are a couple of outstanding questions that will take too long to sort out to sneak this in for 3.7.

  • Should we make the input bool or dict as Shadow will take all of the Patch keyword arguments. As @timhoffm points out if we start making it possible to thread the color through it is likely there will soon be a demand for threading through the rest of the parameters. This then leads to the question of how to handle the rcparams as we do not currently have any structured values (there is no validate_dict) so either we would have to add that (which would be a lot of work) or accept that there are values you can pass at run time that can not be set in rcparams. Neither are great options.
  • Another option would be to add a shadow_props kwarg. The upside of this is we side-step all of the rcparams issues (at least for a bit), but the major down side is now we have coupled kwargs (where the value of one kwarg affects if we even pay attention to the other). It would not be the first place we have this, but they are messy to document and use (but sometimes you need them for "Do What I Mean").
  • @timhoffm and I need to sort out what to do about the {0, 1} cases (this is the second or third one to come up recently). On one hand I agree with Tim that the using 0 and 1 literals is place of True and False is not good, but on the other hand I am still very wary of ripping out the duck-typing (but not ideas I would endorse) with no warning.

While this seems like a very small change (and the actually implementation is straight forward), there are a bunch of follow-on consequences. Some of the more gnarly parts of the Matplotlib API are due to adding features that were relatively small but eventually began to interact in surprising ways!

I think my favorite path forward is:

  • make the shadow kwarg to be None | bool | dict
  • if setting the color via rcparams is a hard requirement add at legend.shadow_props.color entry (so we have space to add more if needed)

I think something like:

if shadow is None:
    self.shadow = rcparams['legend.shadow']
    self._shadow_props = {}
elif instance(shadow, dict):
    self._shadow_props = shadow
    self.shadow = bool(shadow)
else:
    self.shadow = bool(shadow)
    self._shadow_props = {}

# maybe
for k in ['color']:
    self._shadow_props.setdefault(k, rcparams[f'legend.shadow_props.{k}'])

...

if self.shadow:
    Shadow(self.legendPatch, 2, -2, **self._shadow_props).draw(renderer)

This:

  • side-steps the "how much duck type preservation" by preserving the existing behavior at the cost of deciding the ambiguity of shadow={} should mean in terms of "bool like" not "props like"
  • lets the user control the shadow color at the call sight
  • also takes care of any future "but what about the " issues now
  • gives an (optional) path to setting a subset of the properties via rcparams independently of setting if the shadow is shown by default or not

The biggest down side is a further growth of the number of rcparams, but I think that is marginally less bad than the existing rcparams getting more complex.

@tacaswell
Copy link
Member

tacaswell commented Jan 5, 2023

My last comment is the summary of a phone discussion with @QuLogic and @ksunden .

@tacaswell tacaswell modified the milestones: v3.7.0, v3.8.0 Jan 5, 2023
Copy link
Member

@tacaswell tacaswell left a comment

see long comment above.

Anyone can dismiss this review.

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

Successfully merging this pull request may close these issues.

[ENH]: Set color of legend shadow
6 participants