-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Setting color of legend shadow #24666
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
Conversation
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.
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: |
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. |
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.
see long comment above.
Anyone can dismiss this review.
Moving this to draft, and marking inactive, however, @tuncbkose please let us know if you are still interested in this and we can move back to the queue... |
I am happy to implement whatever that is decided to be preferable. It was (and still is) unclear to me if @tacaswell's last comments were only a suggestion or the agreed way to move forward. If it's the latter, I can probably make the necessary changes soon. |
When we allow for this type variability in parameter values (which is quite reasonable), IMHO we can as well afford an intermediate color step:
Is there some proven interest in rcParams availability? If not, let's start simple and leave this out - we have a number of setting that are not available through rcParams. Rationale: If we start shadow configuration, this has to go into a new 'shadow_props' section immediately. As opposed to the input in parameters, rcParams can be read. While we could overload this with color (every color is truthy, so current (duck-typed) bool checks would continue to work), we cannot transparently change this to a dict. It would also be unwise to have I'm also considering a maybe too clever alternative: Can we turn the existing 'legend.shadow' into a namespace directly, i.e. 'legend.shadow.color' etc. while staying backward-compatible. a) Parsing an rcParams file should be simple: promote 'legend.shadow' to 'legend.shadow.visible' This is quite a bit of effort, but would allow a more canonical rcParams namespacing in the future. I think, we can pull this of, but it's only worth if there is really the need. Hence, the proposal to not touch rcParams for now (well, we have to explicitly raise on colors now; otherwise users can think colors are supported, but we actually duck-type them as bool). Can we have a similar generalization strategy for rcParams?
legend.shadow.visible |
Just from the peanut gallery...
I do think that making some rcParams be dicts (with a specified key/value model) would be good. (See also #11231.) |
Here is a first implementation of One question I have in addition to general review: should rcParams give a specific error message saying |
Thanks for the update @tuncbkose. If you look at the code diff you can see there are some comments from bots. The |
I'm aware of the current formating/styling issues. I just didn't fix them yet, in case more changes are requested. If it would be more convenient for the reviewers, I'll happily fix the failing issues now. |
Gentle ping @tuncbkose - please let us know if you need help addressing the reviews above. Cheers! |
8117387
to
aba3cb1
Compare
Thanks for the ping @melissawm. Sorry that I forgot about this, I should be much more attentive now, if anything further is needed. |
6361aa0
to
8731940
Compare
These errors seem unrelated to me. |
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.
Minor wording suggestions.
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.
One really small change is that the type hint should be updated to reflect dict being allowed.
(Most changes that require such cause CI failures, but this is a particular case that does not)
matplotlib/lib/matplotlib/legend.pyi
Line 81 in adc8c76
shadow: bool | None = ..., |
Should include, likely, | dict[str, Any]
(if all possible values are enumerable and manageable, can be more specific than Any
, but tend towards readability.
Arguably the attribute form could have it as well, but the practical implementation is it only ever gets set to a dictionary during __init__
, and is made to be a bool
before leaving such, so lean towards not changing that.
And one question that I would readily accept the answer of "no" or "not yet" and just be done:
Do we want a set_shadow
method to allow editing of the shadow properties post-hoc? currently the self.shadow
is a bool and self._shadow_props
is a private attribute, so no public way to update it, as far as I can tell.
I go with „not yet“. The signature is not quite obvious to me. Is it a single (dict | bool) parameter (possibly, but a bit awkward for Overall, let‘s wait with this until a request for this is raised. |
Co-authored-by: Tranquilled <Tranquilled@users.noreply.github.com>
@ksunden it seems like your requested change is done. |
Test failures appear to be due to qt problems unrelated to this PR, but rerunning just to be sure. |
Congrats @tuncbkose on getting your first PR merged into Matplotlib - we hope to see you around for a second one! 😄 |
PR Summary
Allowsshadow
legend parameter to be a color.Start making
legend.shadow
configurable.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