🌐 AI搜索 & 代理 主页
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions doc/release/next_whats_new/legend_line_width.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
``legend.linewidth`` rcParam and parameter
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

A new rcParam ``legend.linewidth`` has been added to control the line width of
the legend's box edges. When set to ``None`` (the default), it inherits the
value from ``patch.linewidth``. This allows for independent control of the
legend frame line width without affecting other elements.

The `.Legend` constructor also accepts a new *linewidth* parameter to set the
legend frame line width directly, overriding the rcParam value.

.. plot::
:include-source: true
:alt: A line plot with a legend showing a thick border around the legend box.

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.

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax.plot([1, 2, 3], label='data')
ax.legend(linewidth=2.0) # Thick legend box edge
plt.show()
10 changes: 10 additions & 0 deletions lib/matplotlib/legend.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,12 @@ def _update_bbox_to_anchor(self, loc_in_canvas):
The legend's background patch edge color.
If ``"inherit"``, use :rc:`axes.edgecolor`.

linewidth : float or None, default: :rc:`legend.linewidth`
The legend's background patch edge linewidth.
If ``None``, use :rc:`patch.linewidth`.

.. versionadded:: 3.11

mode : {"expand", None}
If *mode* is set to ``"expand"`` the legend will be horizontally
expanded to fill the Axes area (or *bbox_to_anchor* if defines
Expand Down Expand Up @@ -385,6 +391,7 @@ def __init__(
framealpha=None, # set frame alpha
edgecolor=None, # frame patch edgecolor
facecolor=None, # frame patch facecolor
linewidth=None, # frame patch linewidth

bbox_to_anchor=None, # bbox to which the legend will be anchored
bbox_transform=None, # transform for the bbox
Expand Down Expand Up @@ -526,9 +533,12 @@ def __init__(

fancybox = mpl._val_or_rc(fancybox, "legend.fancybox")

linewidth = mpl._val_or_rc(linewidth, "legend.linewidth")

self.legendPatch = FancyBboxPatch(
xy=(0, 0), width=1, height=1,
facecolor=facecolor, edgecolor=edgecolor,
linewidth=linewidth,
# If shadow is used, default to alpha=1 (#8943).
alpha=(framealpha if framealpha is not None
else 1 if shadow
Expand Down
1 change: 1 addition & 0 deletions lib/matplotlib/legend.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class Legend(Artist):
framealpha: float | None = ...,
edgecolor: Literal["inherit"] | ColorType | None = ...,
facecolor: Literal["inherit"] | ColorType | None = ...,
linewidth: float | None = ...,
bbox_to_anchor: BboxBase
| tuple[float, float]
| tuple[float, float, float, float]
Expand Down
1 change: 1 addition & 0 deletions lib/matplotlib/mpl-data/matplotlibrc
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,7 @@
#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?

#legend.fancybox: True # if True, use a rounded box for the
# legend background, else a rectangle
#legend.shadow: False # if True, give background a shadow effect
Expand Down
2 changes: 2 additions & 0 deletions lib/matplotlib/rcsetup.py
Original file line number Diff line number Diff line change
Expand Up @@ -1184,6 +1184,8 @@ def _convert_validator_spec(key, conv):
"legend.frameon": validate_bool,
# alpha value of the legend frame
"legend.framealpha": validate_float_or_None,
# linewidth of legend frame
"legend.linewidth": validate_float_or_None,

## the following dimensions are in fraction of the font size
"legend.borderpad": validate_float, # units are fontsize
Expand Down
31 changes: 31 additions & 0 deletions lib/matplotlib/tests/test_legend.py
Original file line number Diff line number Diff line change
Expand Up @@ -1669,6 +1669,37 @@ def test_boxplot_legend_labels():
assert all(x.get_label().startswith("_") for x in bp4['medians'][1:])


def test_legend_linewidth():
"""Test legend.linewidth parameter and rcParam."""
fig, ax = plt.subplots()
ax.plot([1, 2, 3], label='data')

# Test direct parameter
leg = ax.legend(linewidth=2.5)
assert leg.legendPatch.get_linewidth() == 2.5

# Test rcParam
with mpl.rc_context({'legend.linewidth': 3.0}):
fig, ax = plt.subplots()
ax.plot([1, 2, 3], label='data')
leg = ax.legend()
assert leg.legendPatch.get_linewidth() == 3.0

# Test None default (should inherit from patch.linewidth)
with mpl.rc_context({'legend.linewidth': None, 'patch.linewidth': 1.5}):
fig, ax = plt.subplots()
ax.plot([1, 2, 3], label='data')
leg = ax.legend()
assert leg.legendPatch.get_linewidth() == 1.5

# Test that direct parameter overrides rcParam
with mpl.rc_context({'legend.linewidth': 1.0}):
fig, ax = plt.subplots()
ax.plot([1, 2, 3], label='data')
leg = ax.legend(linewidth=4.0)
assert leg.legendPatch.get_linewidth() == 4.0


def test_patchcollection_legend():
# Test that PatchCollection labels show up in legend and preserve visual
# properties (issue #23998)
Expand Down
Loading