-
-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Add legend.linewidth parameter to control legend box edge linewidth #30780
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.
Thanks for your work on this @manit2004. As well as my specific comments below, I think this should default to the patch linewidth. As you noted in your summary, that is what is currently used so having it as the default will give us backwards compatibility. I think that will clear up the image test failures.
This should also get a test to verify the behaviour.
| value from ``axes.linewidth``. This allows for independent control of the | ||
| legend frame line width without affecting other elements. | ||
|
|
||
| .. versionadded:: 3.10 |
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.
The versionadded directive is not needed in the release notes. All these files will later be combined to make a single page for the release. Like this
|
|
||
| The `.Legend` constructor also accepts a new *linewidth* parameter to set the | ||
| legend frame line width directly, overriding the rcParam value:: | ||
|
|
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.
Using the plot directive here will mean we get an output image with the example. See here for an example of how that’s done.
lib/matplotlib/legend.py
Outdated
| The legend's background patch edge linewidth. | ||
| If ``None``, use :rc:`axes.linewidth`. | ||
| .. versionadded:: 3.10 |
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.
| .. versionadded:: 3.10 | |
| .. versionadded:: 3.11 |
v3.10 is already released.
lib/matplotlib/mpl-data/matplotlibrc
Outdated
| #legend.handletextpad: 0.8 # the space between the legend line and legend text | ||
| #legend.borderaxespad: 0.5 # the border between the axes and legend edge | ||
| #legend.columnspacing: 2.0 # column separation | ||
| #legend.linewidth: None # line width of the legend frame, None means inherit from axes.linewidth |
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 move this up with edgecolor, etc. Although it is technically a size, it comes under the same style consideration as color and alpha. Also it is measured in points, not fraction of font size.
lib/matplotlib/rcsetup.py
Outdated
| # legend properties | ||
| "legend.fancybox": validate_bool, | ||
| "legend.loc": _validate_legend_loc, | ||
| "legend.linewidth": validate_float_or_None, # linewidth of legend frame |
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.
Move this down with framealpha.
rcomer
left a comment
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.
Thanks for the quick update @manit2004. Just a couple more minor comments but otherwise this looks great!
lib/matplotlib/legend.py
Outdated
| if linewidth is None: | ||
| linewidth = mpl.rcParams["patch.linewidth"] |
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.
| if linewidth is None: | |
| linewidth = mpl.rcParams["patch.linewidth"] |
I think we can leave this as None, and the FancyBboxPatch will take care of swapping it for the rcParam.
|
@rcomer I have implemented your suggestions and also added a test to verify the feature implemented. Added
|
|
@rcomer I have renamed the .rst file and also removed those two lines as you pointed out. Is there anything else that I should do or is it ready to merge? |
rcomer
left a comment
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.
Thanks @manit2004 this looks good to me. For code changes we require two approving reviews, so now we just need to wait for someone else to take a look.
|
Thanks @rcomer for guiding me through one of my first contributions in a big project like matplotlib. Waiting for the reviews. |
| #legend.framealpha: 0.8 # legend patch transparency | ||
| #legend.facecolor: inherit # inherit from axes.facecolor; or color spec | ||
| #legend.edgecolor: 0.8 # background patch boundary color | ||
| #legend.linewidth: None # line width of the legend frame, None means inherit from patch.linewidth |
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 seems we have a mixture already in that we sometimes use "inherit" and sometimes None. We should do an analysis of the cases and decide which one to use in the future.
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 see the point about consistency. I followed the edgecolor pattern which uses "inherit". Happy to adjust based on the team's preferred convention.
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.
Unfortunately, we are inconsistent already.
rcParams using inherit:
- legend.facecolor: inherit # inherit from axes.facecolor; or color spec
- legend.edgecolor
- xtick.labelcolor
- ytick.labelcolor
rcParams using None:
- grid.major.color: None # If None defaults to grid.color
- grid.major.linestyle: None # If None defaults to grid.linestyle
- grid.major.linewidth: None # If None defaults to grid.linewidth
- grid.major.alpha: None # If None defaults to grid.alpha
- grid.minor.color: None # If None defaults to grid.color
- grid.minor.linestyle: None # If None defaults to grid.linestyle
- grid.minor.linewidth: None # If None defaults to grid.linewidth
- grid.minor.alpha: None # If None defaults to grid.alpha
- legend.labelcolor: None # None: use text.color
- contour.linewidth: None # {float, None} Size of the contour line widths. If set to None, it falls back to
line.linewidth.
rcParams using auto
- axes.titlecolor: auto # color of the axes title, auto falls back to text.color as default value
- savefig.facecolor: auto # figure face color when saving
Summary
"auto" is out. The decision is between None and inherit. There are arguments for both.
I'm ok with sticking with None for now.
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.
Thanks for clarifying. Is there anything needed from my side to move this PR forward being merged?
…atplotlib#30780) * Added legend.linewidth parameter to control legend box edge linewidth * Added legend.linewidth parameter to control legend box edge linewidth * linting issue and mypy subtest issue fixed * suggestions implemented * tests added * suggestion taken and .rst file renamed * Fix linting error --------- Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
PR summary
Why is this change necessary?
Users can customize many aspects of legend appearance through rcParams (facecolor, edgecolor, framealpha, shadow, etc.), but there was no way to control the linewidth of the legend's box edges. This created an inconsistency in the customization API and required workarounds when users wanted legends with thicker or thinner frame lines to match their plot aesthetics.
What problem does it solve?
This solves the problem of not being able to independently control legend frame linewidth. Previously, the legend frame linewidth was implicitly tied to the default patch linewidth, making it impossible to have, for example, thin axes borders but thick legend borders, or vice versa.
What is the reasoning for this implementation?
The implementation follows matplotlib's existing pattern for legend customization:
legend.linewidthrcParam (default:None)None, it inherits fromaxes.linewidthfor backward compatibilitylinewidthparameter toLegend.__init__()for per-legend controlFancyBboxPatchthat draws the legend frameThis approach is consistent with how other legend properties like
edgecolorandfacecolorwork, making it intuitive for users already familiar with matplotlib's legend customization.Minimum self-contained example:
PR checklist
128/131 legend tests pass; 3 image comparison tests have minor visual differences (RMS < 3.0) that may require baseline updates
This is a styling parameter not a new plotting feature.
Check doc/api/next_api_changes/development/30767-MS.rst