Skip to content

Commit a3bfb09

Browse files
Add shadow coloring for legends and associated tests
Co-authored-by: Tranquilled <Tranquilled@users.noreply.github.com>
1 parent 5df9e3c commit a3bfb09

File tree

6 files changed

+62
-3
lines changed

6 files changed

+62
-3
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Colorful legend shadows
2+
-------------------------
3+
The *shadow* parameter of legends now accepts dicts in addition to booleans.
4+
If it is neither, a ValueError is thrown.
5+
Dictionaries can contain any keywords for `.patches.Patch`.
6+
Configuration is currently not supported via :rc:`legend.shadow`.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Configurable legend shadows
2+
-------------------------
3+
The *shadow* parameter of legends now accepts dicts in addition to booleans.
4+
Dictionaries can contain any keywords for `.patches.Patch`.
5+
Configuration is currently not supported via :rc:`legend.shadow`.

lib/matplotlib/legend.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from matplotlib import _api, _docstring, colors, offsetbox
3232
from matplotlib.artist import Artist, allow_rasterization
3333
from matplotlib.cbook import silent_list
34+
from matplotlib.colors import is_color_like
3435
from matplotlib.font_manager import FontProperties
3536
from matplotlib.lines import Line2D
3637
from matplotlib.patches import (Patch, Rectangle, Shadow, FancyBboxPatch,
@@ -216,8 +217,10 @@ def _update_bbox_to_anchor(self, loc_in_canvas):
216217
Whether round edges should be enabled around the `.FancyBboxPatch` which
217218
makes up the legend's background.
218219
219-
shadow : bool, default: :rc:`legend.shadow`
220+
shadow : None, bool or dict, default: :rc:`legend.shadow`
220221
Whether to draw a shadow behind the legend.
222+
The shadow can be configured using `.Patch` keywords.
223+
Currently customization in :rc:`legend.shadow` not supported.
221224
222225
framealpha : float, default: :rc:`legend.framealpha`
223226
The alpha transparency of the legend's background.
@@ -480,6 +483,22 @@ def val_or_rc(val, rc_name):
480483
self._mode = mode
481484
self.set_bbox_to_anchor(bbox_to_anchor, bbox_transform)
482485

486+
# Figure out if self.shadow is valid
487+
# If shadow was None, rcParams loads False
488+
# So it shouldn't be None here
489+
490+
if isinstance(self.shadow, dict):
491+
self._shadow_props = self.shadow
492+
self.shadow = True
493+
elif self.shadow in (0, 1, True, False):
494+
self._shadow_props = {}
495+
self.shadow = bool(self.shadow)
496+
else:
497+
raise ValueError(
498+
'Legend shadow must be a dict or bool, not '
499+
f'{self.shadow!r} of type {type(self.shadow)}.'
500+
)
501+
483502
# We use FancyBboxPatch to draw a legend frame. The location
484503
# and size of the box will be updated during the drawing time.
485504

@@ -662,8 +681,11 @@ def draw(self, renderer):
662681
self.legendPatch.set_bounds(bbox.bounds)
663682
self.legendPatch.set_mutation_scale(fontsize)
664683

684+
# self.shadow is validated in __init__
685+
# So by here it is a bool and self._shadow_props contains any configs
686+
665687
if self.shadow:
666-
Shadow(self.legendPatch, 2, -2).draw(renderer)
688+
Shadow(self.legendPatch, 2, -2, **self._shadow_props).draw(renderer)
667689

668690
self.legendPatch.draw(renderer)
669691
self._legend_box.draw(renderer)

lib/matplotlib/rcsetup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1059,7 +1059,7 @@ def _convert_validator_spec(key, conv):
10591059
"legend.labelcolor": _validate_color_or_linecolor,
10601060
# the relative size of legend markers vs. original
10611061
"legend.markerscale": validate_float,
1062-
"legend.shadow": validate_bool,
1062+
"legend.shadow": validate_bool, # using dict in rcParams not yet implemented
10631063
# whether or not to draw a frame around legend
10641064
"legend.frameon": validate_bool,
10651065
# alpha value of the legend frame

lib/matplotlib/tests/test_legend.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,32 @@ def test_empty_bar_chart_with_legend():
532532
plt.legend()
533533

534534

535+
@image_comparison(['shadow_argument_types.png'])
536+
def test_shadow_argument_types():
537+
# Test that different arguments for shadow work as expected
538+
fig, ax = plt.subplots()
539+
ax.plot([1, 2, 3], label='test')
540+
541+
legs = (ax.legend(loc='upper left', shadow=True), # True
542+
ax.legend(loc='upper right', shadow=False), # False
543+
ax.legend(loc='center left', shadow={'color': 'red'}), # string
544+
ax.legend(loc='center right', shadow={'color': (0.1, 0.2, 0.5)}), # tuple
545+
ax.legend(loc='lower left', shadow={'color': 'tab:cyan'}) # tab
546+
)
547+
for l in legs:
548+
ax.add_artist(l)
549+
ax.legend(loc='lower right') # default
550+
551+
552+
def test_shadow_invalid_argument():
553+
# Test if invalid argument to legend shadow
554+
# (i.e. not [color|bool]) raises ValueError
555+
fig, ax = plt.subplots()
556+
ax.plot([1, 2, 3], label='test')
557+
with pytest.raises(ValueError, match="dict or bool"):
558+
ax.legend(loc="upper left", shadow="aardvark") # Bad argument
559+
560+
535561
def test_shadow_framealpha():
536562
# Test if framealpha is activated when shadow is True
537563
# and framealpha is not explicitly passed'''

0 commit comments

Comments
 (0)