diff --git a/examples/pylab_examples/color_demo.py b/examples/pylab_examples/color_demo.py index 1d471b0086a9..f67569ac9f22 100755 --- a/examples/pylab_examples/color_demo.py +++ b/examples/pylab_examples/color_demo.py @@ -1,6 +1,6 @@ #!/usr/bin/env python """ -matplotlib gives you 4 ways to specify colors, +matplotlib gives you 5 ways to specify colors, 1) as a single letter string, ala MATLAB @@ -11,6 +11,9 @@ 4) as a string representing a floating point number from 0 to 1, corresponding to shades of gray. + 5) as a special color "Cn", where n is a number 0-9 specifying the + nth color in the currently active color cycle. + See help(colors) for more info. """ import matplotlib.pyplot as plt @@ -20,8 +23,8 @@ #subplot(111, facecolor='#ababab') t = np.arange(0.0, 2.0, 0.01) s = np.sin(2*np.pi*t) -plt.plot(t, s, 'y') -plt.xlabel('time (s)', color='r') +plt.plot(t, s, 'C1') +plt.xlabel('time (s)', color='C1') plt.ylabel('voltage (mV)', color='0.5') # grayscale color plt.title('About as silly as it gets, folks', color='#afeeee') plt.show() diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 3ea8f26dcd61..0671a1a34385 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -4,6 +4,7 @@ from matplotlib.externals import six from matplotlib.externals.six.moves import reduce, xrange, zip, zip_longest +import itertools import math import warnings @@ -2458,7 +2459,7 @@ def pie(self, x, explode=None, labels=None, colors=None, Call signature:: pie(x, explode=None, labels=None, - colors=('b', 'g', 'r', 'c', 'm', 'y', 'k', 'w'), + colors=None, autopct=None, pctdistance=0.6, shadow=False, labeldistance=1.1, startangle=None, radius=None, counterclock=True, wedgeprops=None, textprops=None, @@ -2478,7 +2479,8 @@ def pie(self, x, explode=None, labels=None, colors=None, *colors*: [ *None* | color sequence ] A sequence of matplotlib color args through which the pie chart - will cycle. + will cycle. If `None`, will use the colors in the currently + active cycle. *labels*: [ *None* | len(x) sequence of strings ] A sequence of strings providing the labels for each wedge @@ -2567,7 +2569,12 @@ def pie(self, x, explode=None, labels=None, colors=None, if len(x) != len(explode): raise ValueError("'explode' must be of length 'x'") if colors is None: - colors = ('b', 'g', 'r', 'c', 'm', 'y', 'k', 'w') + get_next_color = self._get_patches_for_fill.get_next_color + else: + color_cycle = itertools.cycle(colors) + + def get_next_color(): + return six.next(color_cycle) if radius is None: radius = 1 @@ -2603,7 +2610,7 @@ def pie(self, x, explode=None, labels=None, colors=None, w = mpatches.Wedge((x, y), radius, 360. * min(theta1, theta2), 360. * max(theta1, theta2), - facecolor=colors[i % len(colors)], + facecolor=get_next_color(), **wedgeprops) slices.append(w) self.add_patch(w) @@ -3023,8 +3030,8 @@ def xywhere(xs, ys, mask): l0, = self.plot(x, y, fmt, label='_nolegend_', **kwargs) if ecolor is None: - if l0 is None and 'color' in self._get_lines._prop_keys: - ecolor = next(self._get_lines.prop_cycler)['color'] + if l0 is None: + ecolor = self._get_lines.get_next_color() else: ecolor = l0.get_color() @@ -3846,7 +3853,10 @@ def scatter(self, x, y, s=None, c=None, marker='o', cmap=None, norm=None, if facecolors is not None: c = facecolors else: - c = 'b' # The original default + if rcParams['_internal.classic_mode']: + c = 'b' # The original default + else: + c = self._get_patches_for_fill.get_next_color() if edgecolors is None and not rcParams['_internal.classic_mode']: edgecolors = 'face' @@ -6019,9 +6029,8 @@ def _normalize_input(inp, ename='input'): raise ValueError( 'weights should have the same shape as x') - if color is None and 'color' in self._get_lines._prop_keys: - color = [next(self._get_lines.prop_cycler)['color'] - for i in xrange(nx)] + if color is None: + color = [self._get_lines.get_next_color() for i in xrange(nx)] else: color = mcolors.colorConverter.to_rgba_array(color) if len(color) != nx: @@ -7507,6 +7516,12 @@ def violin(self, vpstats, positions=None, vert=True, widths=0.5, perp_lines = self.vlines par_lines = self.hlines + if rcParams['_internal.classic_mode']: + fillcolor = 'y' + edgecolor = 'r' + else: + fillcolor = edgecolor = self._get_lines.get_next_color() + # Render violins bodies = [] for stats, pos, width in zip(vpstats, positions, widths): @@ -7517,7 +7532,7 @@ def violin(self, vpstats, positions=None, vert=True, widths=0.5, bodies += [fill(stats['coords'], -vals + pos, vals + pos, - facecolor='y', + facecolor=fillcolor, alpha=0.3)] means.append(stats['mean']) mins.append(stats['min']) @@ -7527,20 +7542,24 @@ def violin(self, vpstats, positions=None, vert=True, widths=0.5, # Render means if showmeans: - artists['cmeans'] = perp_lines(means, pmins, pmaxes, colors='r') + artists['cmeans'] = perp_lines(means, pmins, pmaxes, + colors=edgecolor) # Render extrema if showextrema: - artists['cmaxes'] = perp_lines(maxes, pmins, pmaxes, colors='r') - artists['cmins'] = perp_lines(mins, pmins, pmaxes, colors='r') - artists['cbars'] = par_lines(positions, mins, maxes, colors='r') + artists['cmaxes'] = perp_lines(maxes, pmins, pmaxes, + colors=edgecolor) + artists['cmins'] = perp_lines(mins, pmins, pmaxes, + colors=edgecolor) + artists['cbars'] = par_lines(positions, mins, maxes, + colors=edgecolor) # Render medians if showmedians: artists['cmedians'] = perp_lines(medians, pmins, pmaxes, - colors='r') + colors=edgecolor) return artists diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index c11bb9c3af33..79084dc377da 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -52,6 +52,7 @@ def _process_plot_format(fmt): * 'ko': black circles * '.b': blue dots * 'r--': red dashed lines + * 'C2--': the third color in the color cycle, dashed lines .. seealso:: @@ -97,7 +98,9 @@ def _process_plot_format(fmt): chars = [c for c in fmt] - for c in chars: + i = 0 + while i < len(chars): + c = chars[i] if c in mlines.lineStyles: if linestyle is not None: raise ValueError( @@ -113,9 +116,14 @@ def _process_plot_format(fmt): raise ValueError( 'Illegal format string "%s"; two color symbols' % fmt) color = c + elif c == 'C' and i < len(chars) - 1: + color_cycle_number = int(chars[i + 1]) + color = mcolors.colorConverter._get_nth_color(color_cycle_number) + i += 1 else: raise ValueError( 'Unrecognized character %c in format string' % c) + i += 1 if linestyle is None and marker is None: linestyle = rcParams['lines.linestyle'] @@ -161,6 +169,10 @@ def set_prop_cycle(self, *args, **kwargs): else: prop_cycler = cycler(*args, **kwargs) + # Make sure the cycler always has at least one color + if 'color' not in prop_cycler.keys: + prop_cycler = prop_cycler * cycler('color', ['k']) + self.prop_cycler = itertools.cycle(prop_cycler) # This should make a copy self._prop_keys = prop_cycler.keys @@ -186,6 +198,12 @@ def __call__(self, *args, **kwargs): ret = self._grab_next_args(*args, **kwargs) return ret + def get_next_color(self): + """ + Return the next color in the cycle. + """ + return six.next(self.prop_cycler)['color'] + def set_lineprops(self, line, **kwargs): assert self.command == 'plot', 'set_lineprops only works with "plot"' line.set(**kwargs) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 86011f06c23f..c3555585838a 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -31,6 +31,12 @@ - k: black - w: white +To use the colors that are part of the active color cycle in the current style, +use `C` followed by a digit. For example: + + - `C0`: The first color in the cycle + - `C1`: The second color in the cycle + Gray shades can be given as a string encoding a float in the 0-1 range, e.g.:: color = '0.75' @@ -67,6 +73,19 @@ def is_color_like(c): 'Return *True* if *c* can be converted to *RGB*' + + # Special-case the N-th color cycle syntax, because its parsing + # needs to be deferred. We may be reading a value from rcParams + # here before the color_cycle rcParam has been parsed. + if isinstance(c, bytes): + match = re.match(b'^C[0-9]$', c) + if match is not None: + return True + elif isinstance(c, six.text_type): + match = re.match('^C[0-9]$', c) + if match is not None: + return True + try: colorConverter.to_rgb(c) return True @@ -114,9 +133,36 @@ class ColorConverter(object): 'k': (0, 0, 0), 'w': (1, 1, 1)} + _prop_cycler = None + cache = {} CN_LOOKUPS = [COLOR_NAMES[k] for k in ['css4', 'xkcd']] + @classmethod + def _get_nth_color(cls, val): + """ + Get the Nth color in the current color cycle. If N is greater + than the number of colors in the cycle, it is wrapped around. + """ + from matplotlib.rcsetup import cycler + from matplotlib import rcParams + + prop_cycler = rcParams['axes.prop_cycle'] + if prop_cycler is None and 'axes.color_cycle' in rcParams: + clist = rcParams['axes.color_cycle'] + prop_cycler = cycler('color', clist) + + colors = prop_cycler._transpose()['color'] + return colors[val % len(colors)] + + @classmethod + def _parse_nth_color(cls, val): + match = re.match('^C[0-9]$', val) + if match is not None: + return cls._get_nth_color(int(val[1])) + + raise ValueError("Not a color cycle color") + def to_rgb(self, arg): """ Returns an *RGB* tuple of three floats from 0-1. @@ -154,6 +200,10 @@ def to_rgb(self, arg): argl = arg.lower() color = self.colors.get(argl, None) if color is None: + try: + argl = self._parse_nth_color(arg) + except ValueError: + pass for cmapping in self.CN_LOOKUPS: str1 = cmapping.get(argl, argl) if str1 != argl: diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 9e46fefcbd3e..6f34c402499e 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -848,7 +848,7 @@ def validate_animation_writer_path(p): # line props 'lines.linewidth': [2.5, validate_float], # line width in points 'lines.linestyle': ['-', six.text_type], # solid line - 'lines.color': ['b', validate_color], # blue + 'lines.color': ['C0', validate_color], # first color in color cycle 'lines.marker': ['None', six.text_type], # black 'lines.markeredgewidth': [1.0, validate_float], 'lines.markersize': [6, validate_float], # markersize, in points @@ -867,7 +867,7 @@ def validate_animation_writer_path(p): ## patch props 'patch.linewidth': [None, validate_float_or_None], # line width in points 'patch.edgecolor': ['k', validate_color], # black - 'patch.facecolor': ['#1f77b4', validate_color], # blue (first color in color cycle) + 'patch.facecolor': ['C0', validate_color], # first color in color cycle 'patch.antialiased': [True, validate_bool], # antialiased (no jaggies) ## Histogram properties @@ -885,7 +885,7 @@ def validate_animation_writer_path(p): 'boxplot.showfliers': [True, validate_bool], 'boxplot.meanline': [False, validate_bool], - 'boxplot.flierprops.color': ['b', validate_color], + 'boxplot.flierprops.color': ['C0', validate_color], 'boxplot.flierprops.marker': ['+', six.text_type], 'boxplot.flierprops.markerfacecolor': ['auto', validate_color_or_auto], 'boxplot.flierprops.markeredgecolor': ['k', validate_color], @@ -893,11 +893,11 @@ def validate_animation_writer_path(p): 'boxplot.flierprops.linestyle': ['none', six.text_type], 'boxplot.flierprops.linewidth': [1.0, validate_float], - 'boxplot.boxprops.color': ['b', validate_color], + 'boxplot.boxprops.color': ['C0', validate_color], 'boxplot.boxprops.linewidth': [1.0, validate_float], 'boxplot.boxprops.linestyle': ['-', six.text_type], - 'boxplot.whiskerprops.color': ['b', validate_color], + 'boxplot.whiskerprops.color': ['C0', validate_color], 'boxplot.whiskerprops.linewidth': [1.0, validate_float], 'boxplot.whiskerprops.linestyle': ['--', six.text_type], @@ -905,7 +905,7 @@ def validate_animation_writer_path(p): 'boxplot.capprops.linewidth': [1.0, validate_float], 'boxplot.capprops.linestyle': ['-', six.text_type], - 'boxplot.medianprops.color': ['r', validate_color], + 'boxplot.medianprops.color': ['C1', validate_color], 'boxplot.medianprops.linewidth': [1.0, validate_float], 'boxplot.medianprops.linestyle': ['-', six.text_type], @@ -1016,7 +1016,10 @@ def validate_animation_writer_path(p): 'axes.formatter.use_mathtext': [False, validate_bool], 'axes.formatter.useoffset': [True, validate_bool], 'axes.unicode_minus': [True, validate_bool], - 'axes.color_cycle': [['b', 'g', 'r', 'c', 'm', 'y', 'k'], + 'axes.color_cycle': [ + ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', + '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', + '#bcbd22', '#17becf'], deprecate_axes_colorcycle], # cycle of plot # line colors # This entry can be either a cycler object or a diff --git a/lib/matplotlib/sankey.py b/lib/matplotlib/sankey.py index 423884723461..41d3e04a308d 100755 --- a/lib/matplotlib/sankey.py +++ b/lib/matplotlib/sankey.py @@ -45,6 +45,7 @@ from matplotlib.transforms import Affine2D from matplotlib import verbose from matplotlib import docstring +from matplotlib import rcParams __author__ = "Kevin L. Davies" __credits__ = ["Yannick Copin"] @@ -770,11 +771,15 @@ def _get_angle(a, r): print("lrpath\n", self._revert(lrpath)) xs, ys = list(zip(*vertices)) self.ax.plot(xs, ys, 'go-') - patch = PathPatch(Path(vertices, codes), - fc=kwargs.pop('fc', kwargs.pop('facecolor', - '#bfd1d4')), # Custom defaults - lw=kwargs.pop('lw', kwargs.pop('linewidth', 0.5)), - **kwargs) + if rcParams['_internal.classic_mode']: + fc = kwargs.pop('fc', kwargs.pop('facecolor', '#bfd1d4')) + lw = kwargs.pop('lw', kwargs.pop('linewidth', 0.5)) + else: + fc = kwargs.pop('fc', kwargs.pop('facecolor', None)) + lw = kwargs.pop('lw', kwargs.pop('linewidth', None)) + if fc is None: + fc = six.next(self.ax._get_patches_for_fill.prop_cycler)['color'] + patch = PathPatch(Path(vertices, codes), fc=fc, lw=lw, **kwargs) self.ax.add_patch(patch) # Add the path labels. diff --git a/lib/matplotlib/stackplot.py b/lib/matplotlib/stackplot.py index 8a34f97c389a..e44885953373 100644 --- a/lib/matplotlib/stackplot.py +++ b/lib/matplotlib/stackplot.py @@ -108,10 +108,7 @@ def stackplot(axes, x, *args, **kwargs): raise ValueError(errstr) # Color between x = 0 and the first array. - if 'color' in axes._get_lines._prop_keys: - color = six.next(axes._get_lines.prop_cycler)['color'] - else: - color = None + color = axes._get_lines.get_next_color() r.append(axes.fill_between(x, first_line, stack[0, :], facecolor=color, label= six.next(labels, None), @@ -120,10 +117,7 @@ def stackplot(axes, x, *args, **kwargs): # Color between array i-1 and array i for i in xrange(len(y) - 1): - if 'color' in axes._get_lines._prop_keys: - color = six.next(axes._get_lines.prop_cycler)['color'] - else: - color = None + color = axes._get_lines.get_next_color() r.append(axes.fill_between(x, stack[i, :], stack[i + 1, :], facecolor=color, label= six.next(labels, None), diff --git a/lib/matplotlib/streamplot.py b/lib/matplotlib/streamplot.py index 64db899104d2..51409fad6828 100644 --- a/lib/matplotlib/streamplot.py +++ b/lib/matplotlib/streamplot.py @@ -82,8 +82,8 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None, if transform is None: transform = axes.transData - if color is None and 'color' in axes._get_lines._prop_keys: - color = six.next(axes._get_lines.prop_cycler)['color'] + if color is None: + color = axes._get_lines.get_next_color() if linewidth is None: linewidth = matplotlib.rcParams['lines.linewidth'] diff --git a/lib/matplotlib/tests/baseline_images/test_cycles/lineprop_cycle_basic.png b/lib/matplotlib/tests/baseline_images/test_cycles/lineprop_cycle_basic.png index e3706c3216f2..2d20423df44d 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_cycles/lineprop_cycle_basic.png and b/lib/matplotlib/tests/baseline_images/test_cycles/lineprop_cycle_basic.png differ diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 1e2fc5b19829..283c17c6ea4d 100755 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -1593,7 +1593,10 @@ def plot_surface(self, X, Y, Z, *args, **kwargs): if 'facecolors' in kwargs: fcolors = kwargs.pop('facecolors') else: - color = np.array(colorConverter.to_rgba(kwargs.pop('color', 'b'))) + color = kwargs.pop('color', None) + if color is None: + color = self._get_lines.get_next_color() + color = np.array(colorConverter.to_rgba(color)) fcolors = None cmap = kwargs.get('cmap', None) @@ -1862,7 +1865,10 @@ def plot_trisurf(self, *args, **kwargs): had_data = self.has_data() # TODO: Support custom face colours - color = np.array(colorConverter.to_rgba(kwargs.pop('color', 'b'))) + color = kwargs.pop('color', None) + if color is None: + color = self._get_lines.get_next_color() + color = np.array(colorConverter.to_rgba(color)) cmap = kwargs.get('cmap', None) norm = kwargs.pop('norm', None) @@ -2211,7 +2217,7 @@ def add_collection3d(self, col, zs=0, zdir='z'): Axes.add_collection(self, col) - def scatter(self, xs, ys, zs=0, zdir='z', s=20, c='b', depthshade=True, + def scatter(self, xs, ys, zs=0, zdir='z', s=20, c=None, depthshade=True, *args, **kwargs): ''' Create a scatter plot. @@ -2264,6 +2270,8 @@ def scatter(self, xs, ys, zs=0, zdir='z', s=20, c='b', depthshade=True, s = np.ma.ravel(s) # This doesn't have to match x, y in size. + if c is None: + c = self._get_lines.get_next_color() cstr = cbook.is_string_like(c) or cbook.is_sequence_of_strings(c) if not cstr: c = np.asanyarray(c) @@ -2342,7 +2350,7 @@ def bar(self, left, height, zs=0, zdir='z', *args, **kwargs): return patches - def bar3d(self, x, y, z, dx, dy, dz, color='b', + def bar3d(self, x, y, z, dx, dy, dz, color=None, zsort='average', *args, **kwargs): ''' Generate a 3D bar, or multiple bars. diff --git a/matplotlibrc.template b/matplotlibrc.template index ed220657175d..1ddb3fcd0dd2 100644 --- a/matplotlibrc.template +++ b/matplotlibrc.template @@ -80,7 +80,7 @@ backend : $TEMPLATE_BACKEND # information on line properties. #lines.linewidth : 2.0 # line width in points #lines.linestyle : - # solid line -#lines.color : blue # has no affect on plot(); see axes.prop_cycle +#lines.color : C0 # has no affect on plot(); see axes.prop_cycle #lines.marker : None # the default marker #lines.markeredgewidth : 1.0 # the line width around the marker symbol #lines.markersize : 6 # markersize, in points @@ -105,7 +105,7 @@ backend : $TEMPLATE_BACKEND #patch.linewidth : None # edge width in points. # If None, use axes.linewidth when patch # is not filled. -#patch.facecolor : 1f77b4 +#patch.facecolor : C0 #patch.edgecolor : black #patch.antialiased : True # render patches in antialiased (no jaggies) @@ -141,14 +141,14 @@ backend : $TEMPLATE_BACKEND #boxplot.capprops.linewidth : 1.0 #boxplot.capprops.linestyle : '-' -#boxplot.medianprops.color : 'b' +#boxplot.medianprops.color : 'C0' #boxplot.medianprops.linewidth : 1.0 #boxplot.medianprops.linestyle : '-' -#boxplot.meanprops.color : 'b' +#boxplot.meanprops.color : 'C1' #boxplot.meanprops.marker : '^' -#boxplot.meanprops.markerfacecolor : 'b' -#boxplot.meanprops.markeredgecolor : 'b' +#boxplot.meanprops.markerfacecolor : 'C1' +#boxplot.meanprops.markeredgecolor : 'C1' #boxplot.meanprops.markersize : 6 #boxplot.meanprops.linestyle : 'none' #boxplot.meanprops.linewidth : 1.0