From 65fc1825c5b8e6fb5e29d8fa6a9050d8e4e171be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tun=C3=A7=20Ba=C5=9Far=20K=C3=B6se?= Date: Thu, 8 Dec 2022 15:57:40 +0200 Subject: [PATCH] Add shadow coloring for legends and associated tests Co-authored-by: Tranquilled --- .../next_whats_new/legend_shadow_colors.rst | 17 ++++++++++ lib/matplotlib/legend.py | 25 ++++++++++++-- lib/matplotlib/legend.pyi | 2 +- lib/matplotlib/rcsetup.py | 1 + .../test_legend/shadow_argument_types.png | Bin 0 -> 17186 bytes lib/matplotlib/tests/test_legend.py | 32 ++++++++++++++++++ 6 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 doc/users/next_whats_new/legend_shadow_colors.rst create mode 100644 lib/matplotlib/tests/baseline_images/test_legend/shadow_argument_types.png diff --git a/doc/users/next_whats_new/legend_shadow_colors.rst b/doc/users/next_whats_new/legend_shadow_colors.rst new file mode 100644 index 000000000000..6d501dcf07fe --- /dev/null +++ b/doc/users/next_whats_new/legend_shadow_colors.rst @@ -0,0 +1,17 @@ +Configurable legend shadows +--------------------------- +The *shadow* parameter of legends now accepts dicts in addition to booleans. +Dictionaries can contain any keywords for `.patches.Patch`. +For example, this allows one to set the color and/or the transparency of a legend shadow: + +.. code-block:: python + + ax.legend(loc='center left', shadow={'color': 'red', 'alpha': 0.5}) + +and to control the shadow location: + +.. code-block:: python + + ax.legend(loc='center left', shadow={"ox":20, "oy":-20}) + +Configuration is currently not supported via :rc:`legend.shadow`. diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index b0e50d67f9b7..90be3344e828 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -178,8 +178,10 @@ def _update_bbox_to_anchor(self, loc_in_canvas): Whether round edges should be enabled around the `.FancyBboxPatch` which makes up the legend's background. -shadow : bool, default: :rc:`legend.shadow` +shadow : None, bool or dict, default: :rc:`legend.shadow` Whether to draw a shadow behind the legend. + The shadow can be configured using `.Patch` keywords. + Customization via :rc:`legend.shadow` is currently not supported. framealpha : float, default: :rc:`legend.framealpha` The alpha transparency of the legend's background. @@ -558,6 +560,22 @@ def val_or_rc(val, rc_name): self._mode = mode self.set_bbox_to_anchor(bbox_to_anchor, bbox_transform) + # Figure out if self.shadow is valid + # If shadow was None, rcParams loads False + # So it shouldn't be None here + + self._shadow_props = {'ox': 2, 'oy': -2} # default location offsets + if isinstance(self.shadow, dict): + self._shadow_props.update(self.shadow) + self.shadow = True + elif self.shadow in (0, 1, True, False): + self.shadow = bool(self.shadow) + else: + raise ValueError( + 'Legend shadow must be a dict or bool, not ' + f'{self.shadow!r} of type {type(self.shadow)}.' + ) + # We use FancyBboxPatch to draw a legend frame. The location # and size of the box will be updated during the drawing time. @@ -743,8 +761,11 @@ def draw(self, renderer): self.legendPatch.set_bounds(bbox.bounds) self.legendPatch.set_mutation_scale(fontsize) + # self.shadow is validated in __init__ + # So by here it is a bool and self._shadow_props contains any configs + if self.shadow: - Shadow(self.legendPatch, 2, -2).draw(renderer) + Shadow(self.legendPatch, **self._shadow_props).draw(renderer) self.legendPatch.draw(renderer) self._legend_box.draw(renderer) diff --git a/lib/matplotlib/legend.pyi b/lib/matplotlib/legend.pyi index 77ef273766c2..a0b464b90b43 100644 --- a/lib/matplotlib/legend.pyi +++ b/lib/matplotlib/legend.pyi @@ -78,7 +78,7 @@ class Legend(Artist): ncols: int = ..., mode: Literal["expand"] | None = ..., fancybox: bool | None = ..., - shadow: bool | None = ..., + shadow: bool | dict[str, Any] | None = ..., title: str | None = ..., title_fontsize: float | None = ..., framealpha: float | None = ..., diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 663ff4b70536..f75ad21acc59 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -1078,6 +1078,7 @@ def _convert_validator_spec(key, conv): "legend.labelcolor": _validate_color_or_linecolor, # the relative size of legend markers vs. original "legend.markerscale": validate_float, + # using dict in rcParams not yet supported, so make sure it is bool "legend.shadow": validate_bool, # whether or not to draw a frame around legend "legend.frameon": validate_bool, diff --git a/lib/matplotlib/tests/baseline_images/test_legend/shadow_argument_types.png b/lib/matplotlib/tests/baseline_images/test_legend/shadow_argument_types.png new file mode 100644 index 0000000000000000000000000000000000000000..c38699467d55eee90477f993842c2e8efdc23e30 GIT binary patch literal 17186 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 f{_}s&u