diff --git a/doc/release/next_whats_new/legend_line_width.rst b/doc/release/next_whats_new/legend_line_width.rst new file mode 100644 index 000000000000..d8cfd57640a8 --- /dev/null +++ b/doc/release/next_whats_new/legend_line_width.rst @@ -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. + + 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() diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index 8564c18c5118..8f43f89de5f9 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -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 @@ -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 @@ -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 diff --git a/lib/matplotlib/legend.pyi b/lib/matplotlib/legend.pyi index c03471fc54d1..e17738c76161 100644 --- a/lib/matplotlib/legend.pyi +++ b/lib/matplotlib/legend.pyi @@ -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] diff --git a/lib/matplotlib/mpl-data/matplotlibrc b/lib/matplotlib/mpl-data/matplotlibrc index 83e567a414c9..b1eed6abfd3c 100644 --- a/lib/matplotlib/mpl-data/matplotlibrc +++ b/lib/matplotlib/mpl-data/matplotlibrc @@ -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 #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 diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 80d25659888e..a088274b3439 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -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 diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index a99192c4d571..5f83b25b90a5 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -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)