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
base: main
Are you sure you want to change the base?
Conversation
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.
lib/matplotlib/legend.py
Outdated
@@ -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)): |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
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. |
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):
|
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? |
@tuncbkose we have instructions for squashing here: |
Co-authored-by: Tranquilled <[email protected]>
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.
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:
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:
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. |
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
pytest
passes)Release Notes
.. versionadded::
directive in the docstring and documented indoc/users/next_whats_new/
.. versionchanged::
directive in the docstring and documented indoc/api/next_api_changes/
next_whats_new/README.rst
ornext_api_changes/README.rst