Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 2009a34

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

File tree

Expand file treeCollapse file tree

7 files changed

+103
-3
lines changed
Filter options
Expand file treeCollapse file tree

7 files changed

+103
-3
lines changed
+8Lines 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.
+7Lines 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

Copy file name to clipboardExpand all lines: lib/matplotlib/legend.py
+19-2Lines 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

Copy file name to clipboardExpand all lines: lib/matplotlib/rcsetup.py
+13-1Lines 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

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_legend.py
+26Lines 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

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_rcparams.py
+30Lines 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)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.