Skip to content

Improve color cycle usage #5674

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions examples/pylab_examples/color_demo.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -11,6 +11,10 @@
4) as a string representing a floating point number
from 0 to 1, corresponding to shades of gray.

5) as a string representing the Nth color in the
currently active color cycle, e.g. "[0]"
represents the first color in the color cycle.

See help(colors) for more info.
"""
import matplotlib.pyplot as plt
Expand All @@ -20,8 +24,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, '[0]')
plt.xlabel('time (s)', color='[0]')
plt.ylabel('voltage (mV)', color='0.5') # grayscale color
plt.title('About as silly as it gets, folks', color='#afeeee')
plt.show()
26 changes: 16 additions & 10 deletions lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -2452,7 +2453,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,
Expand All @@ -2471,8 +2472,9 @@ def pie(self, x, explode=None, labels=None, colors=None,
fraction of the radius with which to offset each wedge.

*colors*: [ *None* | color sequence ]
A sequence of matplotlib color args through which the pie chart
will cycle.
A sequence of matplotlib color args through which the pie
chart will cycle. If `None`, will use the colors in the
default property cycle.

*labels*: [ *None* | len(x) sequence of strings ]
A sequence of strings providing the labels for each wedge
Expand Down Expand Up @@ -2561,7 +2563,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
Expand Down Expand Up @@ -2597,7 +2604,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)
Expand Down Expand Up @@ -3017,8 +3024,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 = six.next(self._get_lines.prop_cycler)['color']
if l0 is None:
ecolor = self._get_lines.get_next_color()
else:
ecolor = l0.get_color()

Expand Down Expand Up @@ -5966,9 +5973,8 @@ def hist(self, x, bins=None, range=None, normed=False, weights=None,

nx = len(x) # number of datasets

if color is None and 'color' in self._get_lines._prop_keys:
color = [six.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:
Expand Down
22 changes: 22 additions & 0 deletions lib/matplotlib/axes/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import warnings
import math
from operator import itemgetter
import re

import numpy as np
from numpy import ma
Expand Down Expand Up @@ -94,6 +95,17 @@ def _process_plot_format(fmt):
linestyle = 'None'
fmt = fmt.replace(' ', '')

# handle the N-th color in the color cycle
matches = re.findall('\[[0-9]+\]', fmt)
if len(matches) == 1:
color = mcolors.colorConverter._get_nth_color(
int(matches[0][1:-1]))
fmt = fmt.replace(matches[0], '')
elif len(matches) != 0:
if color is not None:
raise ValueError(
'Illegal format string "%s"; two color symbols' % fmt)

chars = [c for c in fmt]

for c in chars:
Expand Down Expand Up @@ -160,6 +172,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
Expand All @@ -185,6 +201,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)
Expand Down
51 changes: 51 additions & 0 deletions lib/matplotlib/colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@
- k: black
- w: white

To use the colors that are part of the active color cycle in the
current style, use a string with a positive index in square brackets.
If the index is larger than the color cycle, it is wrapped around.
For example:

- `[0]`: The first color in the cycle
- `[1]`: The second color in the cycle
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably note that negative indexes are not allowed (as evidenced by the regex used).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I should also mention the fact that it wraps around.


Gray shades can be given as a string encoding a float in the 0-1 range, e.g.::

color = '0.75'
Expand Down Expand Up @@ -213,6 +221,18 @@

def is_color_like(c):
'Return *True* if *c* can be converted to *RGB*'
# Special-case the N-th color cycle syntax, because it's 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'^\[[0-9]\]$', c)
if match is not None:
return True
elif isinstance(c, six.text_type):
match = re.match('^\[[0-9]\]$', c)
if match is not None:
return True

try:
colorConverter.to_rgb(c)
return True
Expand Down Expand Up @@ -260,8 +280,35 @@ class ColorConverter(object):
'k': (0.0, 0.0, 0.0),
'w': (1.0, 1.0, 1.0), }

_prop_cycler = None

cache = {}

@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('^\[[0-9]\]$', val)
if match is not None:
return cls._get_nth_color(int(val[1:-1]))

raise ValueError("Not a color cycle color")

def to_rgb(self, arg):
"""
Returns an *RGB* tuple of three floats from 0-1.
Expand Down Expand Up @@ -299,6 +346,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(argl)
except ValueError:
pass
str1 = cnames.get(argl, argl)
if str1.startswith('#'):
color = hex2color(str1)
Expand Down
15 changes: 8 additions & 7 deletions lib/matplotlib/rcsetup.py
Original file line number Diff line number Diff line change
Expand Up @@ -827,7 +827,7 @@ def validate_hist_bins(s):
# line props
'lines.linewidth': [1.0, validate_float], # line width in points
'lines.linestyle': ['-', six.text_type], # solid line
'lines.color': ['b', validate_color], # blue
'lines.color': ['[0]', validate_color], # blue
'lines.marker': ['None', six.text_type], # black
'lines.markeredgewidth': [1.0, validate_float],
'lines.markersize': [6, validate_float], # markersize, in points
Expand All @@ -843,7 +843,7 @@ def validate_hist_bins(s):
## patch props
'patch.linewidth': [None, validate_float_or_None], # line width in points
'patch.edgecolor': ['k', validate_color], # black
'patch.facecolor': ['b', validate_color], # blue
'patch.facecolor': ['[0]', validate_color], # blue
'patch.antialiased': [True, validate_bool], # antialiased (no jaggies)

## Histogram properties
Expand Down Expand Up @@ -992,14 +992,15 @@ def validate_hist_bins(s):
'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'],
deprecate_axes_colorcycle], # cycle of plot
# line colors
# This entry can be either a cycler object or a
# string repr of a cycler-object, which gets eval()'ed
# to create the object.
'axes.prop_cycle': [ccycler('color', 'bgrcmyk'),
validate_cycler],
'axes.prop_cycle': [
ccycler('color',
['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728',
'#9467bd', '#8c564b', '#e377c2', '#7f7f7f',
'#bcbd22', '#17becf']),
validate_cycler],
# If 'data', axes limits are set close to the data.
# If 'round_numbers' axes limits are set to the nearest round numbers.
'axes.autolimit_mode': [
Expand Down
15 changes: 10 additions & 5 deletions lib/matplotlib/sankey.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from matplotlib.externals import six
from matplotlib.externals.six.moves import zip
from matplotlib import rcParams

# Original version by Yannick Copin (ycopin@ipnl.in2p3.fr) 10/2/2010, available
# at:
Expand Down Expand Up @@ -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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a path through this where fc is never defined.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops. Fixed.

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.
Expand Down
10 changes: 2 additions & 8 deletions lib/matplotlib/stackplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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),
Expand Down
4 changes: 2 additions & 2 deletions lib/matplotlib/streamplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion matplotlibrc.template
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,10 @@ backend : %(backend)s
#axes.unicode_minus : True # use unicode for the minus symbol
# rather than hyphen. See
# http://en.wikipedia.org/wiki/Plus_and_minus_signs#Character_codes
#axes.prop_cycle : cycler('color', 'bgrcmyk')
#axes.prop_cycle : cycler('color',
# ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728',
# '#9467bd', '#8c564b', '#e377c2', '#7f7f7f',
# '#bcbd22', '#17becf'])
# color cycle for plot lines
# as list of string colorspecs:
# single letter, long name, or
Expand Down