From e29e69e059cc1714312e11863bbb200ffa4d742b Mon Sep 17 00:00:00 2001 From: Tranquilled Date: Mon, 5 Oct 2020 18:08:19 -0400 Subject: [PATCH 01/14] Remove unused/deprecated AVConv_ classes --- lib/matplotlib/animation.py | 50 ------------------------------------- 1 file changed, 50 deletions(-) diff --git a/lib/matplotlib/animation.py b/lib/matplotlib/animation.py index cd72921395de..c5d9dc7d2453 100644 --- a/lib/matplotlib/animation.py +++ b/lib/matplotlib/animation.py @@ -584,17 +584,6 @@ def output_args(self): return args + ['-y', self.outfile] - @classmethod - def isAvailable(cls): - return ( - super().isAvailable() - # Ubuntu 12.04 ships a broken ffmpeg binary which we shouldn't use. - # NOTE: when removed, remove the same method in AVConvBase. - and b'LibAv' not in subprocess.run( - [cls.bin_path()], creationflags=subprocess_creation_flags, - stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, - stderr=subprocess.PIPE).stderr) - # Combine FFMpeg options with pipe-based writing @writers.register('ffmpeg') @@ -640,45 +629,6 @@ def _args(self): '-vframes', str(self._frame_counter)] + self.output_args -# Base class of avconv information. AVConv has identical arguments to FFMpeg. -@cbook.deprecated('3.3') -class AVConvBase(FFMpegBase): - """ - Mixin class for avconv output. - - To be useful this must be multiply-inherited from with a - `MovieWriterBase` sub-class. - """ - - _exec_key = 'animation.avconv_path' - _args_key = 'animation.avconv_args' - - # NOTE : should be removed when the same method is removed in FFMpegBase. - isAvailable = classmethod(MovieWriter.isAvailable.__func__) - - -# Combine AVConv options with pipe-based writing -@writers.register('avconv') -class AVConvWriter(AVConvBase, FFMpegWriter): - """ - Pipe-based avconv writer. - - Frames are streamed directly to avconv via a pipe and written in a single - pass. - """ - - -# Combine AVConv options with file-based writing -@writers.register('avconv_file') -class AVConvFileWriter(AVConvBase, FFMpegFileWriter): - """ - File-based avconv writer. - - Frames are written to temporary files on disk and then stitched - together at the end. - """ - - # Base class for animated GIFs with ImageMagick class ImageMagickBase: """ From c49298797689426f68f41c18595ea6b2314877c4 Mon Sep 17 00:00:00 2001 From: Tranquilled Date: Mon, 5 Oct 2020 20:28:52 -0400 Subject: [PATCH 02/14] Remove references to removed classes from docs --- doc/api/animation_api.rst | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/doc/api/animation_api.rst b/doc/api/animation_api.rst index c8edde884046..f9458e75fdfc 100644 --- a/doc/api/animation_api.rst +++ b/doc/api/animation_api.rst @@ -186,7 +186,6 @@ on all systems. FFMpegWriter ImageMagickWriter - AVConvWriter The file-based writers save temporary files for each frame which are stitched into a single file at the end. Although slower, these writers can be easier to @@ -198,7 +197,6 @@ debug. FFMpegFileWriter ImageMagickFileWriter - AVConvFileWriter Fundamentally, a `MovieWriter` provides a way to grab sequential frames from the same underlying `~matplotlib.figure.Figure` object. The base @@ -283,7 +281,6 @@ and mixins :toctree: _as_gen :nosignatures: - AVConvBase FFMpegBase ImageMagickBase @@ -298,6 +295,6 @@ Inheritance Diagrams :private-bases: :parts: 1 -.. inheritance-diagram:: matplotlib.animation.AVConvFileWriter matplotlib.animation.AVConvWriter matplotlib.animation.FFMpegFileWriter matplotlib.animation.FFMpegWriter matplotlib.animation.ImageMagickFileWriter matplotlib.animation.ImageMagickWriter +.. inheritance-diagram:: matplotlib.animation.FFMpegFileWriter matplotlib.animation.FFMpegWriter matplotlib.animation.ImageMagickFileWriter matplotlib.animation.ImageMagickWriter :private-bases: :parts: 1 From 5fcb7d87bcc721a4b76226675c0418527b3c1102 Mon Sep 17 00:00:00 2001 From: Tranquilled Date: Mon, 5 Oct 2020 22:41:33 -0400 Subject: [PATCH 03/14] Add explicit shadowcolor flag to legend; default to previous behavior --- lib/matplotlib/legend.py | 15 +++++++++++++-- lib/matplotlib/mpl-data/stylelib/classic.mplstyle | 3 ++- lib/matplotlib/rcsetup.py | 1 + matplotlibrc.template | 1 + 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index 4553a605c67e..49f4e5d4ddd3 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -211,6 +211,11 @@ def _update_bbox_to_anchor(self, loc_in_canvas): shadow : bool, default: :rc:`legend.shadow` Whether to draw a shadow behind the legend. +shadowcolor : color or None + Takes a valid color string. If ``None``, defaults to the legend's + ``facecolor`` as usual. + Sets shadow color directly if shadow is activated. + framealpha : float, default: :rc:`legend.framealpha` The alpha transparency of the legend's background. If *shadow* is activated and *framealpha* is ``None``, the default value is @@ -322,6 +327,7 @@ def __init__(self, parent, handles, labels, fancybox=None, # True use a fancy box, false use a rounded # box, none use rc shadow=None, + shadowcolor=None, # set shadow color directly title=None, # set a title for the legend title_fontsize=None, # the font size for the title framealpha=None, # set frame alpha @@ -514,7 +520,6 @@ def __init__(self, parent, handles, labels, self._draggable = None # set the text color - color_getters = { # getter function depends on line or patch 'linecolor': ['get_color', 'get_facecolor'], 'markerfacecolor': ['get_markerfacecolor', 'get_facecolor'], @@ -543,6 +548,9 @@ def __init__(self, parent, handles, labels, raise ValueError("Invalid argument for labelcolor : %s" % str(labelcolor)) + # set shadow color + self.shadowcolor = shadowcolor + def _set_artist_props(self, a): """ Set the boilerplate props for artists added to axes. @@ -607,7 +615,10 @@ def draw(self, renderer): self.legendPatch.set_bounds(bbox.x0, bbox.y0, bbox.width, bbox.height) self.legendPatch.set_mutation_scale(fontsize) - if self.shadow: + if self.shadowcolor and self.shadow: + Shadow(self.legendPatch, 2, -2, + color=self.shadowcolor).draw(renderer) + elif self.shadow: Shadow(self.legendPatch, 2, -2).draw(renderer) self.legendPatch.draw(renderer) diff --git a/lib/matplotlib/mpl-data/stylelib/classic.mplstyle b/lib/matplotlib/mpl-data/stylelib/classic.mplstyle index 4f8cab109070..73205e761b68 100644 --- a/lib/matplotlib/mpl-data/stylelib/classic.mplstyle +++ b/lib/matplotlib/mpl-data/stylelib/classic.mplstyle @@ -294,7 +294,8 @@ legend.handletextpad : 0.8 # the space between the legend line and legend tex legend.borderaxespad : 0.5 # the border between the axes and legend edge in fraction of fontsize legend.columnspacing : 2. # the border between the axes and legend edge in fraction of fontsize legend.shadow : False -legend.frameon : True # whether or not to draw a frame around legend +legend.shadowcolor : None # sets shadow color; defaults to legend.facecolor +legend.frameon : True # whether or not to draw a frame around legend legend.framealpha : None # opacity of legend frame legend.scatterpoints : 3 # number of scatter points legend.facecolor : inherit # legend background color (when 'inherit' uses axes.facecolor) diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 17cc0c2b7c6a..d58676728d43 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -1246,6 +1246,7 @@ def _convert_validator_spec(key, conv): # the relative size of legend markers vs. original "legend.markerscale": validate_float, "legend.shadow": validate_bool, + "legend.shadowcolor": validate_color, # whether or not to draw a frame around legend "legend.frameon": validate_bool, # alpha value of the legend frame diff --git a/matplotlibrc.template b/matplotlibrc.template index e6b8c7d5ecfd..0b77d9c401cf 100644 --- a/matplotlibrc.template +++ b/matplotlibrc.template @@ -520,6 +520,7 @@ #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 +#legend.shadowcolor: None # takes a color string, otherwise default behavior #legend.numpoints: 1 # the number of marker points in the legend line #legend.scatterpoints: 1 # number of scatter points #legend.markerscale: 1.0 # the relative size of legend markers vs. original From 2c97d51f3a15066ff8b5b1b302c23c7c3bdf07a2 Mon Sep 17 00:00:00 2001 From: Tranquilled Date: Tue, 6 Oct 2020 00:26:11 -0400 Subject: [PATCH 04/14] Revert "Remove unused/deprecated AVConv_ classes" This reverts commit e29e69e059cc1714312e11863bbb200ffa4d742b. --- lib/matplotlib/animation.py | 50 +++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/lib/matplotlib/animation.py b/lib/matplotlib/animation.py index c5d9dc7d2453..cd72921395de 100644 --- a/lib/matplotlib/animation.py +++ b/lib/matplotlib/animation.py @@ -584,6 +584,17 @@ def output_args(self): return args + ['-y', self.outfile] + @classmethod + def isAvailable(cls): + return ( + super().isAvailable() + # Ubuntu 12.04 ships a broken ffmpeg binary which we shouldn't use. + # NOTE: when removed, remove the same method in AVConvBase. + and b'LibAv' not in subprocess.run( + [cls.bin_path()], creationflags=subprocess_creation_flags, + stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, + stderr=subprocess.PIPE).stderr) + # Combine FFMpeg options with pipe-based writing @writers.register('ffmpeg') @@ -629,6 +640,45 @@ def _args(self): '-vframes', str(self._frame_counter)] + self.output_args +# Base class of avconv information. AVConv has identical arguments to FFMpeg. +@cbook.deprecated('3.3') +class AVConvBase(FFMpegBase): + """ + Mixin class for avconv output. + + To be useful this must be multiply-inherited from with a + `MovieWriterBase` sub-class. + """ + + _exec_key = 'animation.avconv_path' + _args_key = 'animation.avconv_args' + + # NOTE : should be removed when the same method is removed in FFMpegBase. + isAvailable = classmethod(MovieWriter.isAvailable.__func__) + + +# Combine AVConv options with pipe-based writing +@writers.register('avconv') +class AVConvWriter(AVConvBase, FFMpegWriter): + """ + Pipe-based avconv writer. + + Frames are streamed directly to avconv via a pipe and written in a single + pass. + """ + + +# Combine AVConv options with file-based writing +@writers.register('avconv_file') +class AVConvFileWriter(AVConvBase, FFMpegFileWriter): + """ + File-based avconv writer. + + Frames are written to temporary files on disk and then stitched + together at the end. + """ + + # Base class for animated GIFs with ImageMagick class ImageMagickBase: """ From ea20273b0a57d3e95eb0ee8d2e8079bd1c75699b Mon Sep 17 00:00:00 2001 From: Tranquilled Date: Tue, 6 Oct 2020 00:26:49 -0400 Subject: [PATCH 05/14] Revert "Remove references to removed classes from docs" This reverts commit c49298797689426f68f41c18595ea6b2314877c4. --- doc/api/animation_api.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/api/animation_api.rst b/doc/api/animation_api.rst index f9458e75fdfc..c8edde884046 100644 --- a/doc/api/animation_api.rst +++ b/doc/api/animation_api.rst @@ -186,6 +186,7 @@ on all systems. FFMpegWriter ImageMagickWriter + AVConvWriter The file-based writers save temporary files for each frame which are stitched into a single file at the end. Although slower, these writers can be easier to @@ -197,6 +198,7 @@ debug. FFMpegFileWriter ImageMagickFileWriter + AVConvFileWriter Fundamentally, a `MovieWriter` provides a way to grab sequential frames from the same underlying `~matplotlib.figure.Figure` object. The base @@ -281,6 +283,7 @@ and mixins :toctree: _as_gen :nosignatures: + AVConvBase FFMpegBase ImageMagickBase @@ -295,6 +298,6 @@ Inheritance Diagrams :private-bases: :parts: 1 -.. inheritance-diagram:: matplotlib.animation.FFMpegFileWriter matplotlib.animation.FFMpegWriter matplotlib.animation.ImageMagickFileWriter matplotlib.animation.ImageMagickWriter +.. inheritance-diagram:: matplotlib.animation.AVConvFileWriter matplotlib.animation.AVConvWriter matplotlib.animation.FFMpegFileWriter matplotlib.animation.FFMpegWriter matplotlib.animation.ImageMagickFileWriter matplotlib.animation.ImageMagickWriter :private-bases: :parts: 1 From f6a929c2592b0dc1369bf6bdf56232e1c46500a9 Mon Sep 17 00:00:00 2001 From: Tranquilled Date: Mon, 12 Oct 2020 00:15:20 -0400 Subject: [PATCH 06/14] Remove shadowcolor flag; allow color values for shadow flag --- lib/matplotlib/legend.py | 26 ++++++++---------- .../mpl-data/stylelib/classic.mplstyle | 1 - lib/matplotlib/rcsetup.py | 11 ++++++-- lib/matplotlib/tests/test_rcparams.py | 27 +++++++++++++++++++ matplotlibrc.template | 1 - 5 files changed, 47 insertions(+), 19 deletions(-) diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index 49f4e5d4ddd3..8b3d11b38ed9 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -31,6 +31,7 @@ from matplotlib import _api, cbook, docstring, colors from matplotlib.artist import Artist, allow_rasterization from matplotlib.cbook import silent_list +from matplotlib.colors import is_color_like from matplotlib.font_manager import FontProperties from matplotlib.lines import Line2D from matplotlib.patches import (Patch, Rectangle, Shadow, FancyBboxPatch, @@ -208,13 +209,11 @@ 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 : bool or color, default: :rc:`legend.shadow` Whether to draw a shadow behind the legend. - -shadowcolor : color or None - Takes a valid color string. If ``None``, defaults to the legend's - ``facecolor`` as usual. - Sets shadow color directly if shadow is activated. + If value is a color, a shadow of that color will be applied. + If the value is neither boolean nor a valid color, behavior is that of + *True* and the shadow is the default color. framealpha : float, default: :rc:`legend.framealpha` The alpha transparency of the legend's background. @@ -327,7 +326,6 @@ def __init__(self, parent, handles, labels, fancybox=None, # True use a fancy box, false use a rounded # box, none use rc shadow=None, - shadowcolor=None, # set shadow color directly title=None, # set a title for the legend title_fontsize=None, # the font size for the title framealpha=None, # set frame alpha @@ -548,9 +546,6 @@ def __init__(self, parent, handles, labels, raise ValueError("Invalid argument for labelcolor : %s" % str(labelcolor)) - # set shadow color - self.shadowcolor = shadowcolor - def _set_artist_props(self, a): """ Set the boilerplate props for artists added to axes. @@ -615,11 +610,12 @@ def draw(self, renderer): self.legendPatch.set_bounds(bbox.x0, bbox.y0, bbox.width, bbox.height) self.legendPatch.set_mutation_scale(fontsize) - if self.shadowcolor and self.shadow: - Shadow(self.legendPatch, 2, -2, - color=self.shadowcolor).draw(renderer) - elif self.shadow: - Shadow(self.legendPatch, 2, -2).draw(renderer) + if self.shadow: + shadow = self.shadow + if is_color_like(shadow): + Shadow(self.legendPatch, 2, -2, color=shadow).draw(renderer) + else: + Shadow(self.legendPatch, 2, -2).draw(renderer) self.legendPatch.draw(renderer) self._legend_box.draw(renderer) diff --git a/lib/matplotlib/mpl-data/stylelib/classic.mplstyle b/lib/matplotlib/mpl-data/stylelib/classic.mplstyle index 73205e761b68..a8e2fae2e81e 100644 --- a/lib/matplotlib/mpl-data/stylelib/classic.mplstyle +++ b/lib/matplotlib/mpl-data/stylelib/classic.mplstyle @@ -294,7 +294,6 @@ legend.handletextpad : 0.8 # the space between the legend line and legend tex legend.borderaxespad : 0.5 # the border between the axes and legend edge in fraction of fontsize legend.columnspacing : 2. # the border between the axes and legend edge in fraction of fontsize legend.shadow : False -legend.shadowcolor : None # sets shadow color; defaults to legend.facecolor legend.frameon : True # whether or not to draw a frame around legend legend.framealpha : None # opacity of legend frame legend.scatterpoints : 3 # number of scatter points diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index d58676728d43..949b3d5636e0 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -147,6 +147,14 @@ def validate_bool(b): raise ValueError('Could not convert "%s" to bool' % b) +def validate_color_or_bool(c): + """Convert c to color (preferentially) or bool""" + try: + return validate_color(c) + except ValueError: + return validate_bool(c) + + @cbook.deprecated("3.3") def validate_bool_maybe_none(b): """Convert b to ``bool`` or raise, passing through *None*.""" @@ -1245,8 +1253,7 @@ def _convert_validator_spec(key, conv): "legend.title_fontsize": validate_fontsize_None, # the relative size of legend markers vs. original "legend.markerscale": validate_float, - "legend.shadow": validate_bool, - "legend.shadowcolor": validate_color, + "legend.shadow": validate_color_or_bool, # whether or not to draw a frame around legend "legend.frameon": validate_bool, # alpha value of the legend frame diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 97ae564af1fe..9e8534bca36d 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -19,6 +19,7 @@ validate_bool_maybe_none, validate_color, validate_colorlist, + validate_color_or_bool, validate_cycler, validate_float, validate_fontweight, @@ -340,6 +341,32 @@ def generate_validator_testcases(valid): ('(0, 1, "0.5")', ValueError), # last one not a float ), }, + {'validator': validate_color_or_bool, + 'success': (('None', 'none'), + ('none', 'none'), + ('AABBCC', '#AABBCC'), # RGB hex code + ('AABBCC00', '#AABBCC00'), # RGBA hex code + ('tab:blue', 'tab:blue'), # named color + ('C12', 'C12'), # color from cycle + ('(0, 1, 0)', (0.0, 1.0, 0.0)), # RGB tuple + ((0, 1, 0), (0, 1, 0)), # non-string version + ('(0, 1, 0, 1)', (0.0, 1.0, 0.0, 1.0)), # RGBA tuple + ((0, 1, 0, 1), (0, 1, 0, 1)), # non-string version + *((_, True) for _ in + ('t', 'yes', 'on', 'true', 1, True)), + *((_, False) for _ in + ('f', 'n', 'no', 'off', 'false', 0, False)), + ), + 'fail': (('tab:veryblue', ValueError), # invalid name + ('(0, 1)', ValueError), # tuple with length < 3 + ('(0, 1, 0, 1, 0)', ValueError), # tuple with length > 4 + ('(0, 1, none)', ValueError), # cannot cast none to float + ('(0, 1, "0.5")', ValueError), # last one not a float + (2, ValueError), + (-1, ValueError), + ([], ValueError), + ), + }, {'validator': validate_hist_bins, 'success': (('auto', 'auto'), ('fd', 'fd'), diff --git a/matplotlibrc.template b/matplotlibrc.template index 0b77d9c401cf..e6b8c7d5ecfd 100644 --- a/matplotlibrc.template +++ b/matplotlibrc.template @@ -520,7 +520,6 @@ #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 -#legend.shadowcolor: None # takes a color string, otherwise default behavior #legend.numpoints: 1 # the number of marker points in the legend line #legend.scatterpoints: 1 # number of scatter points #legend.markerscale: 1.0 # the relative size of legend markers vs. original From 7c5d798bb8b1bbed73523ac6e0b3f606cf947d93 Mon Sep 17 00:00:00 2001 From: Tranquilled Date: Sat, 31 Oct 2020 15:19:33 -0400 Subject: [PATCH 07/14] Add test, remove stray space --- lib/matplotlib/legend.py | 14 +- .../mpl-data/stylelib/classic.mplstyle | 2 +- .../test_legend/shadow_color.svg | 875 ++++++++++++++++++ lib/matplotlib/tests/test_legend.py | 18 + 4 files changed, 902 insertions(+), 7 deletions(-) create mode 100644 lib/matplotlib/tests/baseline_images/test_legend/shadow_color.svg diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index 8b3d11b38ed9..4db61a7f8860 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -610,12 +610,14 @@ def draw(self, renderer): self.legendPatch.set_bounds(bbox.x0, bbox.y0, bbox.width, bbox.height) self.legendPatch.set_mutation_scale(fontsize) - if self.shadow: - shadow = self.shadow - if is_color_like(shadow): - Shadow(self.legendPatch, 2, -2, color=shadow).draw(renderer) - else: - Shadow(self.legendPatch, 2, -2).draw(renderer) + if is_color_like(self.shadow): + Shadow(self.legendPatch, 2, -2, color=self.shadow).draw(renderer) + elif self.shadow is True: + Shadow(self.legendPatch, 2, -2).draw(renderer) + elif self.shadow is False: + pass + else: + raise ValueError('shadow must be a valid color or bool') self.legendPatch.draw(renderer) self._legend_box.draw(renderer) diff --git a/lib/matplotlib/mpl-data/stylelib/classic.mplstyle b/lib/matplotlib/mpl-data/stylelib/classic.mplstyle index a8e2fae2e81e..4f8cab109070 100644 --- a/lib/matplotlib/mpl-data/stylelib/classic.mplstyle +++ b/lib/matplotlib/mpl-data/stylelib/classic.mplstyle @@ -294,7 +294,7 @@ legend.handletextpad : 0.8 # the space between the legend line and legend tex legend.borderaxespad : 0.5 # the border between the axes and legend edge in fraction of fontsize legend.columnspacing : 2. # the border between the axes and legend edge in fraction of fontsize legend.shadow : False -legend.frameon : True # whether or not to draw a frame around legend +legend.frameon : True # whether or not to draw a frame around legend legend.framealpha : None # opacity of legend frame legend.scatterpoints : 3 # number of scatter points legend.facecolor : inherit # legend background color (when 'inherit' uses axes.facecolor) diff --git a/lib/matplotlib/tests/baseline_images/test_legend/shadow_color.svg b/lib/matplotlib/tests/baseline_images/test_legend/shadow_color.svg new file mode 100644 index 000000000000..ae83624289a5 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_legend/shadow_color.svg @@ -0,0 +1,875 @@ + + + + + + + + 2020-10-31T15:11:46.903485 + image/svg+xml + + + Matplotlib v3.3.2.post1260.dev0+gf6a929c25, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index 0bbd4b1a6496..87fa1982d650 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -508,6 +508,24 @@ def test_shadow_framealpha(): assert leg.get_frame().get_alpha() == 1 +@image_comparison(['shadow_color.svg']) +def test_shadow_color(): + # Test whether a shadow of the appropriate color + # is generated when shadow's value is colorlike + fig, ax = plt.subplots() + ax.plot(range(100), label="test") + legends = { + 'true': plt.legend(loc="upper left", shadow=True), + 'false': plt.legend(loc="center left", shadow=False), + 'default': plt.legend(loc="lower left"), + 'colorstring': plt.legend(loc="upper right", shadow='red'), + 'colortuple': plt.legend(loc="center right", shadow=(0.1, 0.2, 0.5)), + 'colortab': plt.legend(loc="lower right", shadow='tab:cyan') + } + for x in legends: + plt.gca().add_artist(legends[x]) + + def test_legend_title_empty(): # test that if we don't set the legend title, that # it comes back as an empty string, and that it is not From e13ee8dfb4e5074187f259a0808b23c45ed14454 Mon Sep 17 00:00:00 2001 From: Tranquilled Date: Sat, 31 Oct 2020 19:45:07 -0400 Subject: [PATCH 08/14] Change error message and add a test for it --- lib/matplotlib/legend.py | 2 +- lib/matplotlib/tests/test_legend.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index 4db61a7f8860..10fd0b7579c8 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -617,7 +617,7 @@ def draw(self, renderer): elif self.shadow is False: pass else: - raise ValueError('shadow must be a valid color or bool') + raise ValueError('Shadow must be a valid color or bool.') self.legendPatch.draw(renderer) self._legend_box.draw(renderer) diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index 87fa1982d650..80f295df7e16 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -526,6 +526,21 @@ def test_shadow_color(): plt.gca().add_artist(legends[x]) +def test_shadow_bad_type(): + # Ensure an error is thrown if the value of shadow + # is neither colorlike nor bool + # fig, ax = plt.subplots() + # ax.plot(range(100), label="test") + # with pytest.raises(ValueError, match='valid color or bool') as e: + # ax.legend(shadow='Aardvark') + + fig, ax = plt.subplots() + ax.plot(range(100), label="test") + with pytest.raises(ValueError, match="color or bool"): + x = plt.legend(loc="upper left", shadow="aardvark") + fig.canvas.draw() + + def test_legend_title_empty(): # test that if we don't set the legend title, that # it comes back as an empty string, and that it is not From fe09612c8b25d82630e556302e77253e7a200f1c Mon Sep 17 00:00:00 2001 From: Tranquilled Date: Sat, 31 Oct 2020 21:15:39 -0400 Subject: [PATCH 09/14] Return a more accurate error from color/bool validation --- lib/matplotlib/rcsetup.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 949b3d5636e0..b0ceff2d72f6 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -152,7 +152,11 @@ def validate_color_or_bool(c): try: return validate_color(c) except ValueError: + pass + try: return validate_bool(c) + except ValueError: + raise ValueError(f'Could not convert {c} to color or bool.') @cbook.deprecated("3.3") From 3f2a000e77b8bdf75c20b0b9bdc9b115c88ab8ff Mon Sep 17 00:00:00 2001 From: Tranquilled Date: Sat, 31 Oct 2020 22:19:58 -0400 Subject: [PATCH 10/14] Add documentation --- doc/api/next_api_changes/behavior/18668-LL.rst | 7 +++++++ .../2020-10-31-explicit-shadow-color.rst | 13 +++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 doc/api/next_api_changes/behavior/18668-LL.rst create mode 100644 doc/users/next_whats_new/2020-10-31-explicit-shadow-color.rst diff --git a/doc/api/next_api_changes/behavior/18668-LL.rst b/doc/api/next_api_changes/behavior/18668-LL.rst new file mode 100644 index 000000000000..a2bbd164260e --- /dev/null +++ b/doc/api/next_api_changes/behavior/18668-LL.rst @@ -0,0 +1,7 @@ +`shadow` parameter for Legend can be colorlike or bool +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The parameter can now be any colorlike in addition to +a bool. If it is neither, a ValueError is thrown. + +An accessory validation function `validate_color_or_bool' +has also been added, which preferentially returns a color. \ No newline at end of file diff --git a/doc/users/next_whats_new/2020-10-31-explicit-shadow-color.rst b/doc/users/next_whats_new/2020-10-31-explicit-shadow-color.rst new file mode 100644 index 000000000000..6f486caa68f3 --- /dev/null +++ b/doc/users/next_whats_new/2020-10-31-explicit-shadow-color.rst @@ -0,0 +1,13 @@ +Legend shadow colors can be explicitly defined +------------------------- + +The `shadow` parameter for the legend constructor now +accepts both color and bool. If it is a bool, the +behavior is exactly the same as before (if `True`, the +shadow's color is inherited from `facecolor`.) +Any colorlike value sets the shadow to that color, +subject to the same transparency effect that the default +`facecolor` undergoes. + +An accessory validation function `validate_color_or_bool' +has also been added. \ No newline at end of file From 63009717c96a0ec9cf47db1145d3e986f50a6ec0 Mon Sep 17 00:00:00 2001 From: Tranquilled Date: Sat, 31 Oct 2020 23:35:31 -0400 Subject: [PATCH 11/14] Attempt to fix documentation --- doc/api/next_api_changes/behavior/18668-LL.rst | 7 ++++--- .../2020-10-31-explicit-shadow-color.rst | 13 ------------- doc/users/next_whats_new/explicit_shadow_color.rst | 12 ++++++++++++ 3 files changed, 16 insertions(+), 16 deletions(-) delete mode 100644 doc/users/next_whats_new/2020-10-31-explicit-shadow-color.rst create mode 100644 doc/users/next_whats_new/explicit_shadow_color.rst diff --git a/doc/api/next_api_changes/behavior/18668-LL.rst b/doc/api/next_api_changes/behavior/18668-LL.rst index a2bbd164260e..9aba3241712f 100644 --- a/doc/api/next_api_changes/behavior/18668-LL.rst +++ b/doc/api/next_api_changes/behavior/18668-LL.rst @@ -1,7 +1,8 @@ -`shadow` parameter for Legend can be colorlike or bool -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Shadow parameter for Legend can be colorlike or bool +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + The parameter can now be any colorlike in addition to a bool. If it is neither, a ValueError is thrown. -An accessory validation function `validate_color_or_bool' +An accessory validation function `~.rcsetup.validate_color_or_bool` has also been added, which preferentially returns a color. \ No newline at end of file diff --git a/doc/users/next_whats_new/2020-10-31-explicit-shadow-color.rst b/doc/users/next_whats_new/2020-10-31-explicit-shadow-color.rst deleted file mode 100644 index 6f486caa68f3..000000000000 --- a/doc/users/next_whats_new/2020-10-31-explicit-shadow-color.rst +++ /dev/null @@ -1,13 +0,0 @@ -Legend shadow colors can be explicitly defined -------------------------- - -The `shadow` parameter for the legend constructor now -accepts both color and bool. If it is a bool, the -behavior is exactly the same as before (if `True`, the -shadow's color is inherited from `facecolor`.) -Any colorlike value sets the shadow to that color, -subject to the same transparency effect that the default -`facecolor` undergoes. - -An accessory validation function `validate_color_or_bool' -has also been added. \ No newline at end of file diff --git a/doc/users/next_whats_new/explicit_shadow_color.rst b/doc/users/next_whats_new/explicit_shadow_color.rst new file mode 100644 index 000000000000..6b16c12ee418 --- /dev/null +++ b/doc/users/next_whats_new/explicit_shadow_color.rst @@ -0,0 +1,12 @@ +Legend shadow colors can be explicitly defined +---------------------------------------------- + +The shadow parameter for the Legend constructor now +accepts both color and bool. If it is a bool, the +behavior is exactly the same as before. +Any colorlike value sets the shadow to that color, +subject to the same transparency effect that the default +facecolor undergoes. + +An accessory validation function `~.rcsetup.validate_color_or_bool` +has also been added, which preferentially returns a color. \ No newline at end of file From 69d8d6f1e8916671b121b9a5652dc3ff178efaf2 Mon Sep 17 00:00:00 2001 From: Tranquilled Date: Sun, 14 Mar 2021 19:52:42 -0400 Subject: [PATCH 12/14] Remove third option --- lib/matplotlib/legend.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index 10fd0b7579c8..f766105e73a7 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -212,8 +212,6 @@ def _update_bbox_to_anchor(self, loc_in_canvas): shadow : bool or color, default: :rc:`legend.shadow` Whether to draw a shadow behind the legend. If value is a color, a shadow of that color will be applied. - If the value is neither boolean nor a valid color, behavior is that of - *True* and the shadow is the default color. framealpha : float, default: :rc:`legend.framealpha` The alpha transparency of the legend's background. From fca7f2057a6dccf6fdd6b1550e16d632640531cc Mon Sep 17 00:00:00 2001 From: Tranquilled Date: Sun, 14 Mar 2021 21:13:02 -0400 Subject: [PATCH 13/14] Attempt to resolve error --- lib/matplotlib/tests/test_legend.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index 80f295df7e16..b7baa4d04db1 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -529,11 +529,6 @@ def test_shadow_color(): def test_shadow_bad_type(): # Ensure an error is thrown if the value of shadow # is neither colorlike nor bool - # fig, ax = plt.subplots() - # ax.plot(range(100), label="test") - # with pytest.raises(ValueError, match='valid color or bool') as e: - # ax.legend(shadow='Aardvark') - fig, ax = plt.subplots() ax.plot(range(100), label="test") with pytest.raises(ValueError, match="color or bool"): From 4cf426646f9bd91241c68b63c45b4c7e0bb2479e Mon Sep 17 00:00:00 2001 From: Tranquilled Date: Fri, 26 Mar 2021 00:19:23 -0400 Subject: [PATCH 14/14] Remove call to draw canvas --- doc/api/next_api_changes/behavior/18668-LL.rst | 2 +- doc/users/next_whats_new/explicit_shadow_color.rst | 2 +- lib/matplotlib/tests/test_legend.py | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/doc/api/next_api_changes/behavior/18668-LL.rst b/doc/api/next_api_changes/behavior/18668-LL.rst index 9aba3241712f..fa9bd076e126 100644 --- a/doc/api/next_api_changes/behavior/18668-LL.rst +++ b/doc/api/next_api_changes/behavior/18668-LL.rst @@ -1,7 +1,7 @@ Shadow parameter for Legend can be colorlike or bool ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The parameter can now be any colorlike in addition to +The *shadow* parameter can now be any colorlike in addition to a bool. If it is neither, a ValueError is thrown. An accessory validation function `~.rcsetup.validate_color_or_bool` diff --git a/doc/users/next_whats_new/explicit_shadow_color.rst b/doc/users/next_whats_new/explicit_shadow_color.rst index 6b16c12ee418..79cd53ad586e 100644 --- a/doc/users/next_whats_new/explicit_shadow_color.rst +++ b/doc/users/next_whats_new/explicit_shadow_color.rst @@ -1,7 +1,7 @@ Legend shadow colors can be explicitly defined ---------------------------------------------- -The shadow parameter for the Legend constructor now +The *shadow* parameter for the Legend constructor now accepts both color and bool. If it is a bool, the behavior is exactly the same as before. Any colorlike value sets the shadow to that color, diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index 162869322240..2fd516985a88 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -532,8 +532,7 @@ def test_shadow_bad_type(): fig, ax = plt.subplots() ax.plot(range(100), label="test") with pytest.raises(ValueError, match="color or bool"): - x = plt.legend(loc="upper left", shadow="aardvark") - fig.canvas.draw() + ax.legend(loc="upper left", shadow="aardvark") def test_legend_title_empty():