-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
[Features] Allow setting legend title alignment #23140
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
I noted that there were some doubts in the original issue, but cannot really say much about those. However, I believe that this warrants a "New feature" release note with an example of how to use it. A suggestion is to have three subplots and use a legend with different alignment in each. If you also could copy the result of that here, it would help the review process (always easier to see what the results are). I also believe that there should be some tests for this, but I am not really sure about how to best test it. One way is to use something like:
which then strucks me that we probably would like to have a |
Here is an example to show the behavior of import numpy as np
import matplotlib.pyplot as plt
_, axes = plt.subplots(1, 3, figsize=(15, 4))
x = np.arange(0, 10, 1)
for ax, title_alignment in zip(axes, ['left', 'center', 'right']):
ax.plot(x, x, label="Line 1")
ax.plot(x, x[::-1], label="Line 2")
ax.legend(title=f"Long title with title_align='{title_alignment}'", title_align=title_alignment) About the test case, I'm going through the testing document. I can add it later. I think a |
I'm mildly against adding this directly to legend - legend already has a crazy number of options. As stated in the original issue, I think adding this as an optional argument to |
That's reasonable, also most users would likely change the alignment after they visually examine the figure. I think @oscargus might be right about adding a leg = ax.legend(title="title")
leg.set_title("title", align="left") However, in APIs like leg = ax.legend(title="title")
leg.set_title(leg.get_title(), align="left") |
I do not have a strong opinion. On one hand, one can set everything when creating the legend, on the other hand many other classes have dedicated methods for setting almost anything. For sure one can add a Edit: I think I missed the point of the last edit, but anyway, fine with me. I think a user release note may be worthwhile stating that one can now modify the legend title alignment and how. Ideally with an example as you had above. |
I feel |
Is alignment a font property? If so, do we even need this? (This is not a rhetorical question, I do not know.) If not, maybe the comparison is (One benefit of having many |
Note that (But I guess that ideally not both |
For me the proper API parallel is ax.set_title (https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.set_title.html), or ax.set_xlabel. There is no axes.set_title_align, etc. The fact that users can specify the legend title directly on legend is fine, but more fine-tuned control should get its own method. |
lib/matplotlib/legend.py
Outdated
Set the legend title. Alignment can be set with *align* parameter. | ||
Fontproperties can be optionally set with *prop* parameter. |
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.
It would be better if you could re-write these as numpydoc
.
I am 👍🏻 on the feature, but am a bit confused by the name as it looks like what is moving in the legend handles / text not the title. I think I see how we got to this name, but I'm not sure looking at those examples I would guess If you have variable length labels in the legend what does that do? |
Personally, I feel the |
Unfortunately, the topic is more difficult: ExplanationTechnically, we currently do not have a knob for title alignment. What we have here are two boxes If the title box is shorter than the handle box, using
However, if the title is longer, the shifting happens for the handles.
What we needThe packer functionality is not enough: The title alignment must not automatically affect the handle alignment. Either we need a per-element alignment option in the packer #12388 (comment); or we have to come up with other mechanisms for the legend alignment than using the packer. |
Isn't the solution really to not call it |
I'm not convinced the long title "problem" is really a problem? I guess you are complaining that the bottom part of the legend moves, but that seems consistent with what a VPacker does, and what the user has asked for. |
That said, I suspect that a global alignment is not necessarily what people want and thus not a good solution. For example, I'd prefer the handles to be left-aligned and the title to be centered. While you could effectively achieve this by selecting a global "center" or " left" align depending on whether your title is longer than the handle, that'd be some awkward hacking on the user side to compensate a bad API. |
I think I know what you are saying, but you are just suggesting one of two ways to deal with long titles if align='centre' (basically left aligning them if the title is too long) whereas the current implementation uses the other way (moving the legend to the right and centred under the title). If we somehow change the behaviour, and someone else prefers the other way, then they now have to do the awkward hacking. I also don't think we should be too interested in super flexible long-title behaviour - if someone has a long title, and they are bothered by how we decide to centre it or not, they can always shorten it by adding carriage returns (and probably should) Given that we already have a knob, I don't think there is anything a-priori wrong with how it behaves, and its pretty easy to describe, I don't think we need to create a new packer behaviour. |
lib/matplotlib/legend.py
Outdated
|
||
align: str, default: 'center' | ||
One of {'center', 'left', 'right'} | ||
control the alignment of title |
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.
This should explain the long-title behaviour so it is not a surprise.
IMHO the knob is not really good because it's a knob tuning both title and entry alignment at the same time. Independent knobs would be better, but they are more complicated to align. I'm not particulary fond of this soIution, but it's viable if we change the API. The alignment is a global alignment of the legend (box). Therefore:
|
lib/matplotlib/legend.py
Outdated
|
||
prop : `.font_manager.FontProperties` or `str` or `pathlib.Path` | ||
The fontproperties of the legend title, | ||
:rc:`text.Text.set_fontproperties`. |
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.
That's not the name of an rcParam.
lib/matplotlib/tests/test_legend.py
Outdated
for align in ['center', 'left', 'right']: | ||
leg.set_alignment(align) | ||
assert leg.get_children()[0].align == align | ||
assert leg.get_alignment() == align |
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.
This is redundant: you are running this again for each of the parametrizations. I suggest to put the test of set_alignment
this in a separate test function instead:
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.
This is almost good to go. Only some wording improvements.
One flake8 complaint left:
|
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.
This is good to go. Thanks @Mr-Milk for keeping this up and having patience with our careful but lengthy design process!
I suggest that either the second reviewer or the author squashes the commits.
Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
I rebased and squashed this. To check that I did it correctly I created a synthetic merge of 74d53d2 into main:
|
74d53d2
to
b9cdf3e
Compare
I second @timhoffm 's comment, thank you for sticking with us and getting what looks like a very useful feature in! |
Congratulations on your first merged Matplotlib PR @Mr-Milk 🎉 Hopefully we will hear from you again!] |
PR Summary
Attempt to solve issue #12388
Closes #12388 (just so that it is automatically closed when merged).
Changes:
alignment
keyword argument to theLegend
class.set_alignment
andget_alignment
methods forLegend
class.PR Checklist
Tests and Styling
pytest
passes).flake8-docstrings
and runflake8 --docstring-convention=all
).Documentation
doc/users/next_whats_new/
(follow instructions in README.rst there).doc/api/next_api_changes/
(follow instructions in README.rst there).