8000 Add shadow coloring for legends and associated tests · matplotlib/matplotlib@2009a34 · GitHub
[go: up one dir, main page]

Skip to content

Commit 2009a34

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

File tree

7 files changed

+103
-3
lines changed

7 files changed

+103
-3
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Colorful legend shadows
2+
-------------------------
3+
The *shadow* parameter of legends now accepts colors in addition to booleans.
4+
If it is neither, a ValueError is thrown.
5+
Current implementation prefers colors over booleans,
6+
thus *'y', '1'*, and *'0'* are now interpreted as colors instead of bools.
7+
An accessory validation function `.rcsetup.validate_color_or_bool`
8+
has also been added.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Colorful legend shadows
2+
-------------------------
3+
The *shadow* parameter of legends now accepts colors in addition to booleans.
4+
Current implementation prefers colors over booleans,
5+
thus *'y', '1'*, and *'0'* are interpreted as colors.
6+
An accessory validation function `.rcsetup.validate_color_or_bool`
7+
has also been added.

lib/matplotlib/legend.py

Lines changed: 19 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,9 @@ 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 : color or bool, default: :rc:`legend.shadow`
220221
Whether to draw a shadow behind the legend.
222+
If a color is given, a shadow of that color will be applied.
221223
222224
framealpha : float, default: :rc:`legend.framealpha`
223225
The alpha transparency of the legend's background.
@@ -480,6 +482,16 @@ def val_or_rc(val, rc_name):
480482
self._mode = mode
481483
self.set_bbox_to_anchor(bbox_to_anchor, bbox_transform)
482484

485+
# Figure out if self.shadow is valid
486+
487+
if not (is_color_like(self.shadow) or
488+
self.shadow in (0, 1, True, False)
489+
):
490+
raise ValueError(
491+
'Legend shadow must be a valid color or bool, not '
492+
f'{self.shadow!r} of type {type(self.shadow)}.'
493+
)
494+
483495
# We use FancyBboxPatch to draw a legend frame. The location
484496
# and size of the box will be updated during the drawing time.
485497

@@ -662,7 +674,12 @@ def draw(self, renderer):
662674
self.legendPatch.set_bounds(bbox.bounds)
663675
self.legendPatch.set_mutation_scale(fontsize)
664676

665-
if self.shadow:
677+
# self.shadow is validated in __init__
678+
# So by here it is either a color or a bool
679+
680+
if is_color_like(self.shadow):
681+
Shadow(self.legendPatch, 2, -2, color=self.shadow).draw(renderer)
682+
elif self.shadow:
666683
Shadow(self.legendPatch, 2, -2).draw(renderer)
667684

668685
self.legendPatch.draw(renderer)

lib/matplotlib/rcsetup.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,18 @@ def _validate_color_or_linecolor(s):
307307
raise ValueError(f'{s!r} does not look like a color arg')
308308

309309

310+
def validate_color_or_bool(c):
311+
"""Confirm c is a color or a bool."""
312+
try:
313+
return validate_color(c)
314+
except ValueError:
315+
pass
316+
try:
317+
return validate_bool(c)
318+
except ValueError:
319+
raise ValueError(f'Could not convert "{c!r}" to color or bool')
320+
321+
310322
def validate_color(s):
311323
"""Return a valid color arg."""
312324
if isinstance(s, str):
@@ -1059,7 +1071,7 @@ def _convert_validator_spec(key, conv):
10591071
"legend.labelcolor": _validate_color_or_linecolor,
10601072
# the relative size of legend markers vs. original
10611073
"legend.markerscale": validate_float,
1062-
"legend.shadow": validate_bool,
1074+
"legend.shadow": validate_color_or_bool,
10631075
# whether or not to draw a frame around legend
10641076
"legend.frameon": validate_bool,
10651077
# alpha value of the legend frame
Loading

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='red'), # string
544+
ax.legend(loc='center right', shadow=(0.1, 0.2, 0.5)), # tuple
545+
ax.legend(loc='lower left', shadow='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="color 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'''

lib/matplotlib/tests/test_rcparams.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
validate_bool,
1919
validate_color,
2020
validate_colorlist,
21+
validate_color_or_bool,
2122
_validate_color_or_linecolor,
2223
validate_cycler,
2324
validate_float,
@@ -343,6 +344,35 @@ def generate_validator_testcases(valid):
343344
('(0, 1, "0.5")', ValueError), # last one not a float
344345
),
345346
},
347+
{'validator': validate_color_or_bool,
348+
'success': (('None', 'none'),
349+
('none', 'none'),
350+
('AABBCC', '#AABBCC'), # RGB hex code
351+
('AABBCC00', '#AABBCC00'), # RGBA hex code
352+
('tab:blue', 'tab:blue'), # named color
353+
('C12', 'C12'), # color from cycle
354+
('(0, 1, 0)', (0.0, 1.0, 0.0)), # RGB tuple
355+
((0, 1, 0), (0, 1, 0)), # non-string version
356+
('(0, 1, 0, 1)', (0.0, 1.0, 0.0, 1.0)), # RGBA tuple
357+
((0, 1, 0, 1), (0, 1, 0, 1)), # non-string version
358+
*((_, True) for _ in
359+
('t', 'yes', 'on', 'true', 1, True)),
360+
*((_, False) for _ in
361+
('f', 'n', 'no', 'off', 'false', 0, False)),
362+
# These last three are currently individually validated
363+
# both as colors and as bools. `validate_color_or_bool`
364+
# checks for color first, so they won't appear as boolean.
365+
('y', 'y'),
366+
('1', '1'),
367+
('0', '0')
368+
),
369+
'fail': (('tab:veryblue', ValueError), # invalid name
370+
('(0, 1)', ValueError), # tuple with length < 3
371+
('(0, 1, 0, 1, 0)', ValueError), # tuple with length > 4
372+
('(0, 1, none)', ValueError), # cannot cast none to float
373+
('(0, 1, "0.5")', ValueError), # last one not a float
374+
),
375+
},
346376
{'validator': _validate_color_or_linecolor,
347377
'success': (('linecolor', 'linecolor'),
348378
('markerfacecolor', 'markerfacecolor'),

0 commit comments

Comments
 (0)
0