diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py
index 31242ccaecd2..8a4d665bcaf7 100644
--- a/lib/matplotlib/__init__.py
+++ b/lib/matplotlib/__init__.py
@@ -1397,6 +1397,7 @@ def tk_window_focus():
'matplotlib.tests.test_colors',
'matplotlib.tests.test_compare_images',
'matplotlib.tests.test_contour',
+ 'matplotlib.tests.test_cycle',
'matplotlib.tests.test_dates',
'matplotlib.tests.test_delaunay',
'matplotlib.tests.test_figure',
diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py
index 3461cf349653..b1153301c9ca 100644
--- a/lib/matplotlib/axes/_axes.py
+++ b/lib/matplotlib/axes/_axes.py
@@ -2876,7 +2876,7 @@ def xywhere(xs, ys, mask):
if ecolor is None:
if l0 is None:
- ecolor = six.next(self._get_lines.color_cycle)
+ ecolor = self._get_lines.cycle.get_next_color()
else:
ecolor = l0.get_color()
@@ -5678,7 +5678,7 @@ def hist(self, x, bins=10, range=None, normed=False, weights=None,
nx = len(x) # number of datasets
if color is None:
- color = [six.next(self._get_lines.color_cycle)
+ color = [self._get_lines.cycle.get_next_color()
for i in xrange(nx)]
else:
color = mcolors.colorConverter.to_rgba_array(color)
diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py
index 1e4f52d377cd..8fd6c28197f4 100644
--- a/lib/matplotlib/axes/_base.py
+++ b/lib/matplotlib/axes/_base.py
@@ -29,6 +29,8 @@
import matplotlib.font_manager as font_manager
import matplotlib.text as mtext
import matplotlib.image as mimage
+import matplotlib.cycle as mcycle
+
from matplotlib.artist import allow_rasterization
from matplotlib.cbook import iterable
@@ -137,20 +139,14 @@ class _process_plot_var_args(object):
def __init__(self, axes, command='plot'):
self.axes = axes
self.command = command
- self.set_color_cycle()
+ self.cycle = mcycle.Cycle()
def __getstate__(self):
- # note: it is not possible to pickle a itertools.cycle instance
- return {'axes': self.axes, 'command': self.command}
+ return {'axes': self.axes, 'command': self.command,
+ 'cycle': self.cycle}
def __setstate__(self, state):
self.__dict__ = state.copy()
- self.set_color_cycle()
-
- def set_color_cycle(self, clist=None):
- if clist is None:
- clist = rcParams['axes.color_cycle']
- self.color_cycle = itertools.cycle(clist)
def __call__(self, *args, **kwargs):
@@ -229,12 +225,8 @@ def _xy_from_xy(self, x, y):
return x, y
def _makeline(self, x, y, kw, kwargs):
- kw = kw.copy() # Don't modify the original kw.
+ kw = self.cycle.next(kw.copy())
kwargs = kwargs.copy()
- if kw.get('color', None) is None and kwargs.get('color', None) is None:
- kwargs['color'] = kw['color'] = six.next(self.color_cycle)
- # (can't use setdefault because it always evaluates
- # its second argument)
seg = mlines.Line2D(x, y,
**kw
)
@@ -245,7 +237,7 @@ def _makefill(self, x, y, kw, kwargs):
try:
facecolor = kw['color']
except KeyError:
- facecolor = six.next(self.color_cycle)
+ facecolor = self.cycle.get_next_color()
seg = mpatches.Polygon(np.hstack((x[:, np.newaxis],
y[:, np.newaxis])),
facecolor=facecolor,
@@ -971,14 +963,47 @@ def clear(self):
"""clear the axes"""
self.cla()
+ def set_cycle(self, style, slist):
+ """
+ Set the cycle for a line attribute for any future plot commands
+ on this Axes
+
+ *style* is a key to a dictionary for cycles in the cycle class
+ *slist* is a list of mpl style specifiers
+ """
+ self._get_lines.cycle.set_cycle(style, slist)
+
+ def clear_all_cycle(self):
+ """
+ Clear all the current line attribute cycles
+ """
+ self._get_lines.cycle.clear_all_cycle()
+
+ def clear_cycle(self, style):
+ """
+ Clear a cycle for a line attribute specified by style
+
+ *style* is a key to a dictionary for cycles in the cycle class
+ """
+ self._get_lines.cycle.clear_cycle(style)
+
def set_color_cycle(self, clist):
"""
Set the color cycle for any future plot commands on this Axes.
*clist* is a list of mpl color specifiers.
"""
- self._get_lines.set_color_cycle(clist)
- self._get_patches_for_fill.set_color_cycle(clist)
+ self._get_lines.cycle.set_color_cycle(clist)
+ self._get_patches_for_fill.cycle.set_color_cycle(clist)
+
+ def set_line_cycle(self, llist):
+ """
+ Set the line style cycle for any future plot commands on this
+ Axes.
+
+ *llist* is a list of mpl line style specifiers.
+ """
+ self._get_lines.cycle.set_line_cycle(llist)
def ishold(self):
"""return the HOLD status of the axes"""
diff --git a/lib/matplotlib/cycle.py b/lib/matplotlib/cycle.py
new file mode 100644
index 000000000000..a13024a40a7b
--- /dev/null
+++ b/lib/matplotlib/cycle.py
@@ -0,0 +1,143 @@
+import itertools
+import six
+from matplotlib import rcParams
+
+
+class Cycle(object):
+
+ def __init__(self):
+ """
+ Set the initial cycle of styles to be used by the lines of the graph
+ """
+ self._styles = {'color': None,
+ 'linestyle': None,
+ 'linewidth': None,
+ 'marker': None,
+ 'markersize': None,
+ 'markeredgewidth': None,
+ 'markeredgecolor': None,
+ 'markerfacecolor': None,
+ 'antialiased': None,
+ 'dash_capstyle': None,
+ 'solid_capstyle': None,
+ 'dash_joinstyle': None,
+ 'solid_joinstyle': None,
+ }
+ self._styles_list = {}
+ self.set_color_cycle()
+ self.set_line_cycle()
+
+ def __getstate__(self):
+ return {'_styles_list': self._styles_list}
+
+ def __setstate__(self, state):
+ self.__init__()
+ self.__dict__.update(state)
+ for style in self._styles_list.keys():
+ self.set_cycle(style, self._styles_list[style])
+
+ def next(self, args=None):
+ """
+ Returns the next set of line attributes for a line on the graph to use
+ *args* is an optional dictionary of style arguments
+ Styles that already exist in *args* will not be cycled through
+ """
+ if args is None:
+ args = {}
+ else:
+ args = args.copy()
+ for style in self._styles.keys():
+ if self._styles[style] is not None and style not in args:
+ args[style] = six.next(self._styles[style])
+ return args
+
+ def set_cycle(self, style, slist):
+ """
+ Set a cycle for a line attribute specified by style, the cycle to be
+ used to is specified by slist
+
+ *style* is a key to the _style dictionary
+ *slist* is a list of mpl style specifiers
+ """
+ if self._validate(style, slist):
+ self._styles_list[style] = slist
+ self._styles[style] = itertools.cycle(slist)
+
+ def _validate(self, style, slist):
+ """
+ Ensures that the style given is a valid attribute to by cycled over
+ If the style is a valid cycle, ensure that the list of specifiers
+ given are valid specifiers for that style
+
+ *style* is a key to the _style dictionary
+ *plist* is a list of mpl style specifiers
+ """
+ if type(slist) not in (list, tuple) or slist == []:
+ msg = "'slist' must be of type [list | tuple ] and non empty"
+ raise ValueError(msg)
+ if style not in self._styles.keys():
+ msg = "set cycle value error, %s is not a valid style" % style
+ raise ValueError(msg)
+ param = 'lines.' + style
+ for val in slist:
+ try:
+ rcParams.validate[param](val)
+ except ValueError:
+ msg = "Set cycle value error, Style %s: %s" % (style, str(val))
+ raise ValueError(msg)
+
+ return True
+
+ def clear_cycle(self, style):
+ """
+ Clears(resets) a cycle for a line attribute specified by style
+
+ *style* is a key to the _style dictionary
+ """
+ if style not in self._styles.keys():
+ msg = "clear cycle value error, %s is not a valid style" % style
+ raise ValueError(msg)
+ self._styles[style] = None
+
+ def clear_all_cycle(self):
+ """
+ Clears (resets) all cycles for the lines on a plot
+ """
+ for style in self._styles.keys():
+ self._styles[style] = None
+
+ def set_line_cycle(self, llist=None):
+ """
+ Sets a line style cycle to be used for the lines on the graph,
+ if none are specified the default line style cycle will be used
+ """
+ if llist is None:
+ llist = rcParams['axes.line_cycle']
+ self.set_cycle('linestyle', llist)
+
+ def set_color_cycle(self, clist=None):
+ """
+ Sets a color cycle to be used for the lines on the graph, if none are
+ specified the default color cycle will be used
+ """
+ if clist is None:
+ clist = rcParams['axes.color_cycle']
+ self.set_cycle('color', clist)
+
+ def get_next_color(self):
+ """
+ Return the next color or defaults to rcParams if none
+ """
+ try:
+ return six.next(self._styles['color'])
+ except TypeError:
+ return rcParams['lines.color']
+
+ def get_next_linestyle(self):
+ """
+ Return the next linestyle or defaults to rcParams if none
+ """
+ try:
+ return six.next(self._styles['linestyle'])
+ except TypeError:
+ return rcParams['lines.linestyle']
diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py
index 1fdf121b32d8..73a71baacd3f 100644
--- a/lib/matplotlib/rcsetup.py
+++ b/lib/matplotlib/rcsetup.py
@@ -235,6 +235,23 @@ def __call__(self, s):
raise ValueError('Could not convert all entries to ints')
+def validate_line(s):
+ 'return a valid arg'
+ if s in (None, 'none', 'None', ' ', ''):
+ return 'None'
+ if s in ('-', '--', '-.', ':'):
+ return s
+ raise ValueError('%s is not a valid line arg' % s)
+
+
+def validate_markercolor(s):
+ 'return a valid marker color arg'
+ if s == 'auto':
+ return s
+ else:
+ return validate_color(s)
+
+
def validate_color(s):
'return a valid color arg'
try:
@@ -278,6 +295,18 @@ def validate_colorlist(s):
msg = "'s' must be of type [ string | list | tuple ]"
raise ValueError(msg)
+
+def validate_linelist(s):
+ 'return a list of colorspecs'
+ if isinstance(s, six.string_types):
+ return [validate_line(l.strip()) for l in s.split(',')]
+ elif type(s) in (list, tuple) and s != []:
+ return [validate_line(l) for l in s]
+ else:
+ msg = "'s' must be of type [ string | list | tuple ] and non empty"
+ raise ValueError(msg)
+
+
def validate_stringlist(s):
'return a list'
if isinstance(s, six.string_types):
@@ -524,11 +553,13 @@ def __call__(self, s):
# line props
'lines.linewidth': [1.0, validate_float], # line width in points
- 'lines.linestyle': ['-', six.text_type], # solid line
+ 'lines.linestyle': ['-', validate_line], # solid line
'lines.color': ['b', validate_color], # blue
'lines.marker': ['None', six.text_type], # black
'lines.markeredgewidth': [0.5, validate_float],
'lines.markersize': [6, validate_float], # markersize, in points
+ 'lines.markeredgecolor': ['auto', validate_markercolor],
+ 'lines.markerfacecolor': ['auto', validate_markercolor],
'lines.antialiased': [True, validate_bool], # antialised (no jaggies)
'lines.dash_joinstyle': ['round', validate_joinstyle],
'lines.solid_joinstyle': ['round', validate_joinstyle],
@@ -636,7 +667,7 @@ def __call__(self, s):
'axes.unicode_minus': [True, validate_bool],
'axes.color_cycle': [['b', 'g', 'r', 'c', 'm', 'y', 'k'],
validate_colorlist], # cycle of plot
- # line colors
+ 'axes.line_cycle': [['-'], validate_linelist],
'axes.xmargin': [0, ValidateInterval(0, 1,
closedmin=True,
closedmax=True)], # margin added to xaxis
diff --git a/lib/matplotlib/stackplot.py b/lib/matplotlib/stackplot.py
index b42ca98702b4..b04765fb62bb 100644
--- a/lib/matplotlib/stackplot.py
+++ b/lib/matplotlib/stackplot.py
@@ -103,13 +103,13 @@ def stackplot(axes, x, *args, **kwargs):
# Color between x = 0 and the first array.
r.append(axes.fill_between(x, first_line, stack[0, :],
- facecolor=six.next(axes._get_lines.color_cycle),
+ facecolor=axes._get_lines.cycle.get_next_color(),
label= six.next(labels, None),
**kwargs))
# Color between array i-1 and array i
for i in xrange(len(y) - 1):
- color = six.next(axes._get_lines.color_cycle)
+ color = axes._get_lines.cycle.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 3708afa647db..e2f5f441005d 100644
--- a/lib/matplotlib/streamplot.py
+++ b/lib/matplotlib/streamplot.py
@@ -80,7 +80,7 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None,
transform = axes.transData
if color is None:
- color = six.next(axes._get_lines.color_cycle)
+ color = axes._get_lines.cycle.get_next_color()
if linewidth is None:
linewidth = matplotlib.rcParams['lines.linewidth']
diff --git a/lib/matplotlib/tests/baseline_images/test_cycle/clear_all_cycle.pdf b/lib/matplotlib/tests/baseline_images/test_cycle/clear_all_cycle.pdf
new file mode 100644
index 000000000000..e46c94a24909
Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_cycle/clear_all_cycle.pdf differ
diff --git a/lib/matplotlib/tests/baseline_images/test_cycle/clear_all_cycle.png b/lib/matplotlib/tests/baseline_images/test_cycle/clear_all_cycle.png
new file mode 100644
index 000000000000..8adf22818098
Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_cycle/clear_all_cycle.png differ
diff --git a/lib/matplotlib/tests/baseline_images/test_cycle/clear_all_cycle.svg b/lib/matplotlib/tests/baseline_images/test_cycle/clear_all_cycle.svg
new file mode 100644
index 000000000000..aa00ce020e46
--- /dev/null
+++ b/lib/matplotlib/tests/baseline_images/test_cycle/clear_all_cycle.svg
@@ -0,0 +1,660 @@
+
+
+
+
diff --git a/lib/matplotlib/tests/baseline_images/test_cycle/clear_one_cycle.pdf b/lib/matplotlib/tests/baseline_images/test_cycle/clear_one_cycle.pdf
new file mode 100644
index 000000000000..caaf171ee6d5
Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_cycle/clear_one_cycle.pdf differ
diff --git a/lib/matplotlib/tests/baseline_images/test_cycle/clear_one_cycle.png b/lib/matplotlib/tests/baseline_images/test_cycle/clear_one_cycle.png
new file mode 100644
index 000000000000..db2f0dc465a5
Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_cycle/clear_one_cycle.png differ
diff --git a/lib/matplotlib/tests/baseline_images/test_cycle/clear_one_cycle.svg b/lib/matplotlib/tests/baseline_images/test_cycle/clear_one_cycle.svg
new file mode 100644
index 000000000000..5c13c9ca3035
--- /dev/null
+++ b/lib/matplotlib/tests/baseline_images/test_cycle/clear_one_cycle.svg
@@ -0,0 +1,660 @@
+
+
+
+
diff --git a/lib/matplotlib/tests/baseline_images/test_cycle/set_cycles.pdf b/lib/matplotlib/tests/baseline_images/test_cycle/set_cycles.pdf
new file mode 100644
index 000000000000..5bc5d696b325
Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_cycle/set_cycles.pdf differ
diff --git a/lib/matplotlib/tests/baseline_images/test_cycle/set_cycles.png b/lib/matplotlib/tests/baseline_images/test_cycle/set_cycles.png
new file mode 100644
index 000000000000..a1c797e2c209
Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_cycle/set_cycles.png differ
diff --git a/lib/matplotlib/tests/baseline_images/test_cycle/set_cycles.svg b/lib/matplotlib/tests/baseline_images/test_cycle/set_cycles.svg
new file mode 100644
index 000000000000..b7acae5b27f8
--- /dev/null
+++ b/lib/matplotlib/tests/baseline_images/test_cycle/set_cycles.svg
@@ -0,0 +1,902 @@
+
+
+
+
diff --git a/lib/matplotlib/tests/test_cycle.py b/lib/matplotlib/tests/test_cycle.py
new file mode 100644
index 000000000000..1ab76831e935
--- /dev/null
+++ b/lib/matplotlib/tests/test_cycle.py
@@ -0,0 +1,52 @@
+import numpy as np
+import matplotlib
+from matplotlib.testing.decorators import image_comparison
+import matplotlib.pyplot as plt
+
+
+@image_comparison(baseline_images=['set_cycles'])
+def test_set_cycles():
+ x = np.linspace(0, 2 * np.pi)
+ offsets = np.linspace(0, 2 * np.pi, 4, endpoint=False)
+ yy = np.transpose([np.sin(x + phi) for phi in offsets])
+ fig, ax1 = plt.subplots(nrows=1)
+ ax1.set_color_cycle(['c', 'm', 'y', 'k'])
+ ax1.set_cycle('linestyle', ['--', '-.', ':'])
+ ax1.set_cycle('linewidth', [3, 1])
+ ax1.set_cycle('marker', ['>', '<', 'o'])
+ ax1.set_cycle('markersize', [5, 10])
+ ax1.set_cycle('markerfacecolor', ['black'])
+ ax1.set_cycle('markeredgecolor', ['blue'])
+ ax1.set_cycle('markeredgewidth', [1])
+ ax1.set_cycle('antialiased', [True, False])
+ ax1.set_cycle('dash_joinstyle', ['miter', 'round', 'bevel'])
+ ax1.set_cycle('solid_joinstyle', ['miter', 'round', 'bevel'])
+ ax1.set_cycle('dash_capstyle', ['butt', 'round', 'projecting'])
+ ax1.set_cycle('solid_capstyle', ['butt', 'round', 'projecting'])
+ ax1.plot(yy)
+
+
+@image_comparison(baseline_images=['clear_one_cycle'])
+def test_clear_one_cycle():
+ x = np.linspace(0, 2 * np.pi)
+ offsets = np.linspace(0, 2 * np.pi, 4, endpoint=False)
+ yy = np.transpose([np.sin(x + phi) for phi in offsets])
+ fig, ax1 = plt.subplots(nrows=1)
+ ax1.set_color_cycle(['c', 'm', 'y', 'k'])
+ ax1.set_cycle('linestyle', ['--', '-.', ':'])
+ ax1.set_cycle('linewidth', [3, 1])
+ ax1.clear_cycle('linestyle')
+ ax1.plot(yy)
+
+
+@image_comparison(baseline_images=['clear_all_cycle'])
+def test_clear_all_cycle():
+ x = np.linspace(0, 2 * np.pi)
+ offsets = np.linspace(0, 2 * np.pi, 4, endpoint=False)
+ yy = np.transpose([np.sin(x + phi) for phi in offsets])
+ fig, ax1 = plt.subplots(nrows=1)
+ ax1.set_color_cycle(['c', 'm', 'y', 'k'])
+ ax1.set_cycle('linestyle', ['--', '-.', ':'])
+ ax1.set_cycle('linewidth', [3, 1])
+ ax1.clear_all_cycle()
+ ax1.plot(yy)