🌐 AI搜索 & 代理 主页
Skip to content

Conversation

@manit2004
Copy link
Contributor

@manit2004 manit2004 commented Nov 22, 2025

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:

  • Added a new legend.linewidth rcParam (default: None)
  • When None, it inherits from axes.linewidth for backward compatibility
  • Added a linewidth parameter to Legend.__init__() for per-legend control
  • The parameter is passed directly to the FancyBboxPatch that draws the legend frame

This approach is consistent with how other legend properties like edgecolor and facecolor work, making it intuitive for users already familiar with matplotlib's legend customization.

Minimum self-contained example:

import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np

x = np.linspace(0, 2*np.pi, 100)

fig, axes = plt.subplots(1, 3, figsize=(12, 4))

# Default behavior (inherits axes.linewidth)
axes[0].plot(x, np.sin(x), label='sin(x)')
axes[0].legend()
axes[0].set_title('Default (axes.linewidth=0.8)')

# Set via rcParams
mpl.rcParams['legend.linewidth'] = 3.0
axes[1].plot(x, np.cos(x), label='cos(x)')
axes[1].legend()
axes[1].set_title('Via rcParams (linewidth=3.0)')
mpl.rcParams['legend.linewidth'] = None  # Reset

# Set via parameter
axes[2].plot(x, np.sin(2*x), label='sin(2x)')
axes[2].legend(linewidth=5.0, edgecolor='red')
axes[2].set_title('Via parameter (linewidth=5.0)')

plt.tight_layout()
plt.show()

PR checklist

Copy link
Member

@rcomer rcomer left a 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
Copy link
Member

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::

Copy link
Member

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.

The legend's background patch edge linewidth.
If ``None``, use :rc:`axes.linewidth`.
.. versionadded:: 3.10
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
.. versionadded:: 3.10
.. versionadded:: 3.11

v3.10 is already released.

#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
Copy link
Member

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.

# legend properties
"legend.fancybox": validate_bool,
"legend.loc": _validate_legend_loc,
"legend.linewidth": validate_float_or_None, # linewidth of legend frame
Copy link
Member

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 rcomer linked an issue Nov 23, 2025 that may be closed by this pull request
@rcomer rcomer moved this to Waiting for author in First Time Contributors Nov 23, 2025
Copy link
Member

@rcomer rcomer left a 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!

Comment on lines 537 to 538
if linewidth is None:
linewidth = mpl.rcParams["patch.linewidth"]
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
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.

@manit2004
Copy link
Contributor Author

@rcomer I have implemented your suggestions and also added a test to verify the feature implemented.

Added test_legend_linewidth() to verify the new parameter's behavior across four scenarios:

  1. Direct parameter usage - Confirms ax.legend(linewidth=2.5) correctly sets the legend frame linewidth
  2. rcParam configuration - Validates legend.linewidth rcParam is properly applied
  3. Default inheritance - Ensures None correctly inherits from patch.linewidth (backward compatibility)
  4. Parameter precedence - Verifies direct parameter overrides rcParam value

@manit2004
Copy link
Contributor Author

@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?

Copy link
Member

@rcomer rcomer left a 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.

@rcomer rcomer added this to the v3.11.0 milestone Nov 23, 2025
@manit2004
Copy link
Contributor Author

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
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor Author

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?

@timhoffm timhoffm merged commit 7bf7e47 into matplotlib:main Nov 29, 2025
39 checks passed
has2k1 pushed a commit to has2k1/matplotlib that referenced this pull request Dec 11, 2025
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Waiting for author

Development

Successfully merging this pull request may close these issues.

[ENH]: Add rcParams for the width of the legend's box edge

3 participants