From 05e4ed9a420441e1227d394bc7e107503d03667a Mon Sep 17 00:00:00 2001 From: Eric Firing Date: Thu, 4 Aug 2016 17:13:42 -1000 Subject: [PATCH 1/3] Use edgecolor rather than linewidth to control edge display. An rcParam['patch.force_edgecolor'] was added so that a default edgecolor can be specified to be used always (as in classic mode), or to be used only when a fillable object is not actually filled. --- lib/matplotlib/collections.py | 98 +++++++++---------- .../mpl-data/stylelib/classic.mplstyle | 1 + lib/matplotlib/patches.py | 78 ++++++++------- lib/matplotlib/rcsetup.py | 13 +-- lib/matplotlib/tests/test_artist.py | 29 +++--- matplotlibrc.template | 7 +- 6 files changed, 116 insertions(+), 110 deletions(-) diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index ee828eab5839..a255feebf669 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -492,14 +492,9 @@ def set_linewidth(self, lw): ACCEPTS: float or sequence of floats """ if lw is None: - if (self._edge_default or - mpl.rcParams['_internal.classic_mode'] or - not self._is_filled): - lw = mpl.rcParams['patch.linewidth'] - if lw is None: - lw = mpl.rcParams['lines.linewidth'] - else: - lw = 0 + lw = mpl.rcParams['patch.linewidth'] + if lw is None: + lw = mpl.rcParams['lines.linewidth'] # get the un-scaled/broadcast lw self._us_lw = self._get_value(lw) @@ -644,6 +639,20 @@ def set_color(self, c): self.set_facecolor(c) self.set_edgecolor(c) + def _set_facecolor(self, c): + if c is None: + c = mpl.rcParams['patch.facecolor'] + + self._is_filled = True + try: + if c.lower() == 'none': + self._is_filled = False + except AttributeError: + pass + self._facecolors = mcolors.to_rgba_array(c, self._alpha) + self.stale = True + + def set_facecolor(self, c): """ Set the facecolor(s) of the collection. *c* can be a @@ -655,17 +664,9 @@ def set_facecolor(self, c): ACCEPTS: matplotlib color spec or sequence of specs """ - self._is_filled = True - try: - if c.lower() == 'none': - self._is_filled = False - except AttributeError: - pass - if c is None: - c = mpl.rcParams['patch.facecolor'] - self._facecolors_original = c - self._facecolors = mcolors.to_rgba_array(c, self._alpha) - self.stale = True + self._original_facecolor = c + self._set_facecolor(c) + def set_facecolors(self, c): """alias for set_facecolor""" @@ -683,6 +684,23 @@ def get_edgecolor(self): return self._edgecolors get_edgecolors = get_edgecolor + def _set_edgecolor(self, c): + self._is_stroked = True + try: + if c.lower() == 'none': + self._is_stroked = False + except AttributeError: + pass + + try: + if c.lower() == 'face': # Special case: lookup in "get" method. + self._edgecolors = 'face' + return + except AttributeError: + pass + self._edgecolors = mcolors.to_rgba_array(c, self._alpha) + self.stale = True + def set_edgecolor(self, c): """ Set the edgecolor(s) of the collection. *c* can be a @@ -696,24 +714,14 @@ def set_edgecolor(self, c): ACCEPTS: matplotlib color spec or sequence of specs """ - self._is_stroked = True - try: - if c.lower() == 'none': - self._is_stroked = False - except AttributeError: - pass - try: - if c.lower() == 'face': - self._edgecolors = 'face' - self._edgecolors_original = 'face' - return - except AttributeError: - pass + self._original_edgecolor = c if c is None: - c = mpl.rcParams['patch.edgecolor'] - self._edgecolors_original = c - self._edgecolors = mcolors.to_rgba_array(c, self._alpha) - self.stale = True + if (mpl.rcParams['patch.force_edgecolor'] or + not self._is_filled or self._edge_default): + c = mpl.rcParams['patch.edgecolor'] + else: + c = 'none' + self._set_edgecolor(c) def set_edgecolors(self, c): """alias for set_edgecolor""" @@ -732,18 +740,8 @@ def set_alpha(self, alpha): except TypeError: raise TypeError('alpha must be a float or None') artist.Artist.set_alpha(self, alpha) - try: - self._facecolors = mcolors.to_rgba_array( - self._facecolors_original, self._alpha) - except (AttributeError, TypeError, IndexError): - pass - try: - if (not isinstance(self._edgecolors_original, six.string_types) - or self._edgecolors_original != str('face')): - self._edgecolors = mcolors.to_rgba_array( - self._edgecolors_original, self._alpha) - except (AttributeError, TypeError, IndexError): - pass + self._set_facecolor(self._original_facecolor) + self._set_edgecolor(self._original_edgecolor) def get_linewidths(self): return self._linewidths @@ -779,9 +777,9 @@ def update_from(self, other): artist.Artist.update_from(self, other) self._antialiaseds = other._antialiaseds - self._edgecolors_original = other._edgecolors_original + self._original_edgecolor = other._original_edgecolor self._edgecolors = other._edgecolors - self._facecolors_original = other._facecolors_original + self._original_facecolor = other._original_facecolor self._facecolors = other._facecolors self._linewidths = other._linewidths self._linestyles = other._linestyles diff --git a/lib/matplotlib/mpl-data/stylelib/classic.mplstyle b/lib/matplotlib/mpl-data/stylelib/classic.mplstyle index 5550eab902d1..23fffeb813dd 100644 --- a/lib/matplotlib/mpl-data/stylelib/classic.mplstyle +++ b/lib/matplotlib/mpl-data/stylelib/classic.mplstyle @@ -29,6 +29,7 @@ markers.fillstyle: full # information on patch properties patch.linewidth : 1.0 # edge width in points patch.facecolor : b +patch.force_edgecolor : True patch.edgecolor : k patch.antialiased : True # render patches in antialiased (no jaggies) diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 7a8ff77c7ba7..df44edfc14bf 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -154,6 +154,18 @@ def get_verts(self): return polygons[0] return [] + def _process_radius(self, radius): + if radius is not None: + return radius + if cbook.is_numlike(self._picker): + _radius = self._picker + else: + if self.get_edgecolor()[3] == 0: + _radius = 0 + else: + _radius = self.get_linewidth() + return _radius + def contains(self, mouseevent, radius=None): """Test whether the mouse event occurred in the patch. @@ -161,11 +173,7 @@ def contains(self, mouseevent, radius=None): """ if six.callable(self._contains): return self._contains(self, mouseevent) - if radius is None: - if cbook.is_numlike(self._picker): - radius = self._picker - else: - radius = self.get_linewidth() + radius = self._process_radius(radius) inside = self.get_path().contains_point( (mouseevent.x, mouseevent.y), self.get_transform(), radius) return inside, {} @@ -175,11 +183,7 @@ def contains_point(self, point, radius=None): Returns *True* if the given point is inside the path (transformed with its transform attribute). """ - if radius is None: - if cbook.is_numlike(self._picker): - radius = self._picker - else: - radius = self.get_linewidth() + radius = self._process_radius(radius) return self.get_path().contains_point(point, self.get_transform(), radius) @@ -281,37 +285,45 @@ def set_aa(self, aa): """alias for set_antialiased""" return self.set_antialiased(aa) + def _set_edgecolor(self, color): + if color is None: + if (mpl.rcParams['patch.force_edgecolor'] or + not self._fill or self._edge_default): + color = mpl.rcParams['patch.edgecolor'] + else: + color = 'none' + self._edgecolor = colors.to_rgba(color, self._alpha) + self.stale = True + def set_edgecolor(self, color): """ Set the patch edge color - ACCEPTS: mpl color spec, or None for default, or 'none' for no color + ACCEPTS: mpl color spec, None, 'none', or 'auto' """ - if color is None: - color = mpl.rcParams['patch.edgecolor'] self._original_edgecolor = color - self._edgecolor = colors.to_rgba(color, self._alpha) - self.stale = True + self._set_edgecolor(color) + def set_ec(self, color): """alias for set_edgecolor""" return self.set_edgecolor(color) + def _set_facecolor(self, color): + alpha = self._alpha if self._fill else 0 + self._facecolor = colors.to_rgba(color, alpha) + self.stale = True + def set_facecolor(self, color): """ Set the patch face color ACCEPTS: mpl color spec, or None for default, or 'none' for no color """ + self._original_facecolor = color # Not strictly needed now. if color is None: color = mpl.rcParams['patch.facecolor'] - # save: otherwise changing _fill may lose alpha information - self._original_facecolor = color - self._facecolor = colors.to_rgba(color, self._alpha) - if not self._fill: - self._facecolor = list(self._facecolor) - self._facecolor[3] = 0 - self.stale = True + self._set_facecolor(color) def set_fc(self, color): """alias for set_facecolor""" @@ -343,10 +355,10 @@ def set_alpha(self, alpha): except TypeError: raise TypeError('alpha must be a float or None') artist.Artist.set_alpha(self, alpha) - # using self._fill and self._alpha - self.set_facecolor(self._original_facecolor) - self.set_edgecolor(self._original_edgecolor) - self.stale = True + self._set_facecolor(self._facecolor) + self._set_edgecolor(self._original_edgecolor) + # stale is already True + def set_linewidth(self, w): """ @@ -355,14 +367,9 @@ def set_linewidth(self, w): ACCEPTS: float or None for default """ if w is None: - if (not self._fill or - self._edge_default or - mpl.rcParams['_internal.classic_mode']): - w = mpl.rcParams['patch.linewidth'] - if w is None: - w = mpl.rcParams['axes.linewidth'] - else: - w = 0 + w = mpl.rcParams['patch.linewidth'] + if w is None: + w = mpl.rcParams['axes.linewidth'] self._linewidth = float(w) # scale the dash pattern by the linewidth @@ -428,7 +435,8 @@ def set_fill(self, b): ACCEPTS: [True | False] """ self._fill = bool(b) - self.set_facecolor(self._original_facecolor) + self._set_facecolor(self._facecolor) + self._set_edgecolor(self._original_edgecolor) self.stale = True def get_fill(self): diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index ec65a13bc273..d8a8bfc26391 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -364,7 +364,7 @@ def validate_color(s): 'return a valid color arg' try: if s.lower() == 'none': - return 'None' + return 'none' except AttributeError: pass @@ -906,7 +906,7 @@ def validate_animation_writer_path(p): 'lines.linewidth': [1.5, validate_float], # line width in points 'lines.linestyle': ['-', six.text_type], # solid line 'lines.color': ['C0', validate_color], # first color in color cycle - 'lines.marker': ['None', six.text_type], # black + 'lines.marker': ['None', six.text_type], # marker name 'lines.markeredgewidth': [1.0, validate_float], 'lines.markersize': [6, validate_float], # markersize, in points 'lines.antialiased': [True, validate_bool], # antialiased (no jaggies) @@ -922,10 +922,11 @@ def validate_animation_writer_path(p): 'markers.fillstyle': ['full', validate_fillstyle], ## patch props - 'patch.linewidth': [None, validate_float_or_None], # line width in points - 'patch.edgecolor': ['k', validate_color], # black - 'patch.facecolor': ['C0', validate_color], # first color in color cycle - 'patch.antialiased': [True, validate_bool], # antialiased (no jaggies) + 'patch.linewidth': [1.0, validate_float], # line width in points + 'patch.edgecolor': ['k', validate_color], + 'patch.force_edgecolor' : [False, validate_bool], + 'patch.facecolor': ['C0', validate_color], # first color in cycle + 'patch.antialiased': [True, validate_bool], # antialiased (no jaggies) ## hatch props 'hatch.linewidth': [1.0, validate_float], diff --git a/lib/matplotlib/tests/test_artist.py b/lib/matplotlib/tests/test_artist.py index 36125c61ed54..06bc212d883e 100644 --- a/lib/matplotlib/tests/test_artist.py +++ b/lib/matplotlib/tests/test_artist.py @@ -182,21 +182,20 @@ def test_remove(): @image_comparison(baseline_images=["default_edges"], remove_text=True, extensions=['png'], style='default') def test_default_edges(): - with mpl.rc_context({'patch.linewidth': None}): - fig, [[ax1, ax2], [ax3, ax4]] = plt.subplots(2, 2) - - ax1.plot(np.arange(10), np.arange(10), 'x', - np.arange(10) + 1, np.arange(10), 'o') - ax2.bar(np.arange(10), np.arange(10)) - ax3.text(0, 0, "BOX", size=24, bbox=dict(boxstyle='sawtooth')) - ax3.set_xlim((-1, 1)) - ax3.set_ylim((-1, 1)) - pp1 = mpatches.PathPatch( - mpath.Path([(0, 0), (1, 0), (1, 1), (0, 0)], - [mpath.Path.MOVETO, mpath.Path.CURVE3, - mpath.Path.CURVE3, mpath.Path.CLOSEPOLY]), - fc="none", transform=ax4.transData) - ax4.add_patch(pp1) + fig, [[ax1, ax2], [ax3, ax4]] = plt.subplots(2, 2) + + ax1.plot(np.arange(10), np.arange(10), 'x', + np.arange(10) + 1, np.arange(10), 'o') + ax2.bar(np.arange(10), np.arange(10)) + ax3.text(0, 0, "BOX", size=24, bbox=dict(boxstyle='sawtooth')) + ax3.set_xlim((-1, 1)) + ax3.set_ylim((-1, 1)) + pp1 = mpatches.PathPatch( + mpath.Path([(0, 0), (1, 0), (1, 1), (0, 0)], + [mpath.Path.MOVETO, mpath.Path.CURVE3, + mpath.Path.CURVE3, mpath.Path.CLOSEPOLY]), + fc="none", transform=ax4.transData) + ax4.add_patch(pp1) @cleanup diff --git a/matplotlibrc.template b/matplotlibrc.template index 3889f75f7c5a..c20c39dc1ba6 100644 --- a/matplotlibrc.template +++ b/matplotlibrc.template @@ -102,11 +102,10 @@ backend : $TEMPLATE_BACKEND # circles. See # http://matplotlib.org/api/artist_api.html#module-matplotlib.patches # information on patch properties -#patch.linewidth : None # edge width in points. - # If None, use axes.linewidth when patch - # is not filled. +#patch.linewidth : 1 # edge width in points. #patch.facecolor : C0 -#patch.edgecolor : black +#patch.edgecolor : black # if forced, or patch is not filled +#patch.force_edgecolor : False # True to always use edgecolor #patch.antialiased : True # render patches in antialiased (no jaggies) ### HATCHES From 4ab8e14a318289d4603b005fa0f31cd2c281408c Mon Sep 17 00:00:00 2001 From: Eric Firing Date: Fri, 5 Aug 2016 10:41:22 -1000 Subject: [PATCH 2/3] fix some test failures --- lib/matplotlib/collections.py | 12 ++++++------ lib/matplotlib/patches.py | 10 ++++------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index a255feebf669..bf25141b3bb6 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -685,6 +685,12 @@ def get_edgecolor(self): get_edgecolors = get_edgecolor def _set_edgecolor(self, c): + if c is None: + if (mpl.rcParams['patch.force_edgecolor'] or + not self._is_filled or self._edge_default): + c = mpl.rcParams['patch.edgecolor'] + else: + c = 'none' self._is_stroked = True try: if c.lower() == 'none': @@ -715,12 +721,6 @@ def set_edgecolor(self, c): ACCEPTS: matplotlib color spec or sequence of specs """ self._original_edgecolor = c - if c is None: - if (mpl.rcParams['patch.force_edgecolor'] or - not self._is_filled or self._edge_default): - c = mpl.rcParams['patch.edgecolor'] - else: - c = 'none' self._set_edgecolor(c) def set_edgecolors(self, c): diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index df44edfc14bf..022821f8bd8f 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -304,12 +304,13 @@ def set_edgecolor(self, color): self._original_edgecolor = color self._set_edgecolor(color) - def set_ec(self, color): """alias for set_edgecolor""" return self.set_edgecolor(color) def _set_facecolor(self, color): + if color is None: + color = mpl.rcParams['patch.facecolor'] alpha = self._alpha if self._fill else 0 self._facecolor = colors.to_rgba(color, alpha) self.stale = True @@ -320,9 +321,7 @@ def set_facecolor(self, color): ACCEPTS: mpl color spec, or None for default, or 'none' for no color """ - self._original_facecolor = color # Not strictly needed now. - if color is None: - color = mpl.rcParams['patch.facecolor'] + self._original_facecolor = color self._set_facecolor(color) def set_fc(self, color): @@ -359,7 +358,6 @@ def set_alpha(self, alpha): self._set_edgecolor(self._original_edgecolor) # stale is already True - def set_linewidth(self, w): """ Set the patch linewidth in points @@ -435,7 +433,7 @@ def set_fill(self, b): ACCEPTS: [True | False] """ self._fill = bool(b) - self._set_facecolor(self._facecolor) + self._set_facecolor(self._original_facecolor) self._set_edgecolor(self._original_edgecolor) self.stale = True From 69af0e7778bd70d439f344b0be1aedfb016a4d23 Mon Sep 17 00:00:00 2001 From: Eric Firing Date: Fri, 5 Aug 2016 11:17:56 -1000 Subject: [PATCH 3/3] set facecolor before edgecolor, which may depend on facecolor --- lib/matplotlib/collections.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index bf25141b3bb6..d0a1b0d56099 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -128,9 +128,10 @@ def __init__(self, # list of unbroadcast/scaled linewidths self._us_lw = [0] self._linewidths = [0] + self._is_filled = True # May be modified by set_facecolor(). - self.set_edgecolor(edgecolors) self.set_facecolor(facecolors) + self.set_edgecolor(edgecolors) self.set_linewidth(linewidths) self.set_linestyle(linestyles) self.set_antialiased(antialiaseds)