diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 2cc84c0475af..b40ad57575cc 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -54,11 +54,11 @@ """ from __future__ import (absolute_import, division, print_function, unicode_literals) - +import re from matplotlib.externals import six from matplotlib.externals.six.moves import zip import warnings -import re + import numpy as np from numpy import ma import matplotlib.cbook as cbook @@ -194,7 +194,6 @@ def to_rgb(self, arg): raise ValueError( 'to_rgb: arg "%s" is unhashable even inside a tuple' % (str(arg),)) - try: if cbook.is_string_like(arg): argl = arg.lower() @@ -202,6 +201,10 @@ def to_rgb(self, arg): if color is None: try: argl = self._parse_nth_color(arg) + # in this case we do not want to cache in case + # the rcparam changes, recurse with the actual color + # value + return self.to_rgb(argl) except ValueError: pass for cmapping in self.CN_LOOKUPS: @@ -215,7 +218,7 @@ def to_rgb(self, arg): if fl < 0 or fl > 1: raise ValueError( 'gray (string) must be in range 0-1') - color = (fl,)*3 + color = (fl,) * 3 elif cbook.iterable(arg): if len(arg) > 4 or len(arg) < 3: raise ValueError( @@ -228,7 +231,6 @@ def to_rgb(self, arg): else: raise ValueError( 'cannot convert argument to rgb sequence') - self.cache[arg] = color except (KeyError, ValueError, TypeError) as exc: diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 8b4e23547393..d06f054a8409 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -22,6 +22,8 @@ import operator import os import warnings +import re + try: import collections.abc as abc except ImportError: @@ -30,10 +32,11 @@ from matplotlib.fontconfig_pattern import parse_fontconfig_pattern from matplotlib.colors import is_color_like + # Don't let the original cycler collide with our validating cycler from cycler import Cycler, cycler as ccycler -#interactive_bk = ['gtk', 'gtkagg', 'gtkcairo', 'qt4agg', +# interactive_bk = ['gtk', 'gtkagg', 'gtkcairo', 'qt4agg', # 'tkagg', 'wx', 'wxagg', 'cocoaagg', 'webagg'] # The capitalized forms are needed for ipython at present; this may # change for later versions. @@ -175,6 +178,7 @@ def validate_string_or_None(s): except ValueError: raise ValueError('Could not convert "%s" to string' % s) + def validate_axisbelow(s): try: return validate_bool(s) @@ -340,6 +344,22 @@ def validate_color_or_auto(s): return validate_color(s) +def validate_color_for_prop_cycle(s): + # Special-case the N-th color cycle syntax, this obviously can not + # go in the color cycle. + if isinstance(s, bytes): + match = re.match(b'^C[0-9]$', s) + if match is not None: + raise ValueError('Can not put cycle reference ({cn!r}) in ' + 'prop_cycler'.format(cn=s)) + elif isinstance(s, six.text_type): + match = re.match('^C[0-9]$', s) + if match is not None: + raise ValueError('Can not put cycle reference ({cn!r}) in ' + 'prop_cycler'.format(cn=s)) + return validate_color(s) + + def validate_color(s): 'return a valid color arg' try: @@ -649,7 +669,7 @@ def validate_hatch(s): characters: ``\\ / | - + * . x o O``. """ - if not isinstance(s, six.text_type): + if not isinstance(s, six.string_types): raise ValueError("Hatch pattern must be a string") unique_chars = set(s) unknown = (unique_chars - @@ -661,7 +681,8 @@ def validate_hatch(s): _prop_validators = { - 'color': validate_colorlist, + 'color': _listify_validator(validate_color_for_prop_cycle, + allow_stringlist=True), 'linewidth': validate_floatlist, 'linestyle': validate_stringlist, 'facecolor': validate_colorlist, @@ -818,6 +839,9 @@ def validate_cycler(s): norm_prop = _prop_aliases.get(prop, prop) cycler_inst.change_key(prop, norm_prop) + for key, vals in cycler_inst.by_key().items(): + _prop_validators[key](vals) + return cycler_inst diff --git a/lib/matplotlib/tests/baseline_images/test_cycles/property_collision_plot.png b/lib/matplotlib/tests/baseline_images/test_cycles/property_collision_plot.png index 2c962c843f64..82b4ddac5101 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_cycles/property_collision_plot.png and b/lib/matplotlib/tests/baseline_images/test_cycles/property_collision_plot.png differ diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index 44a818d76e4b..085d4d476e7d 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -17,6 +17,8 @@ from numpy.testing.utils import assert_array_equal, assert_array_almost_equal from nose.plugins.skip import SkipTest +from cycler import cycler +import matplotlib import matplotlib.colors as mcolors import matplotlib.cm as cm import matplotlib.cbook as cbook @@ -596,6 +598,23 @@ def test_pandas_iterable(): assert_sequence_equal(cm1.colors, cm2.colors) +@cleanup +def test_cn(): + matplotlib.rcParams['axes.prop_cycle'] = cycler('color', + ['blue', 'r']) + x11_blue = mcolors.rgb2hex(mcolors.colorConverter.to_rgb('C0')) + assert x11_blue == '#0000ff' + red = mcolors.rgb2hex(mcolors.colorConverter.to_rgb('C1')) + assert red == '#ff0000' + + matplotlib.rcParams['axes.prop_cycle'] = cycler('color', + ['XKCDblue', 'r']) + XKCD_blue = mcolors.rgb2hex(mcolors.colorConverter.to_rgb('C0')) + assert XKCD_blue == '#0343df' + red = mcolors.rgb2hex(mcolors.colorConverter.to_rgb('C1')) + assert red == '#ff0000' + + if __name__ == '__main__': import nose nose.runmodule(argv=['-s', '--with-doctest'], exit=False) diff --git a/lib/matplotlib/tests/test_cycles.py b/lib/matplotlib/tests/test_cycles.py index 67ce08b881dc..06b60caa81ca 100644 --- a/lib/matplotlib/tests/test_cycles.py +++ b/lib/matplotlib/tests/test_cycles.py @@ -129,7 +129,7 @@ def test_property_collision_plot(): fig, ax = plt.subplots() ax.set_prop_cycle('linewidth', [2, 4]) for c in range(1, 4): - ax.plot(np.arange(10), c * np.arange(10), lw=0.1) + ax.plot(np.arange(10), c * np.arange(10), lw=0.1, color='k') ax.plot(np.arange(10), 4 * np.arange(10)) ax.plot(np.arange(10), 5 * np.arange(10)) diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index b7a965062a31..d37adfc2b00d 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -348,6 +348,7 @@ def test_validators(): ('cycler(c=[1, 2, 3])', ValueError), # invalid values ("cycler(lw=['a', 'b', 'c'])", ValueError), # invalid values (cycler('waka', [1, 3, 5]), ValueError), # not a property + (cycler('color', ['C1', 'r', 'g']), ValueError) # no CN ) }, {'validator': validate_hatch,