Skip to content

[WIP] Property Cycling #4686

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

Merged
merged 31 commits into from
Aug 29, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
d08c627
Add Cycler as a dependency
WeatherGod Jul 11, 2015
e3c0951
Implemented style cycling using cycler.
WeatherGod Jul 12, 2015
160401b
Fix a typo for cycler dependency
WeatherGod Jul 14, 2015
6b2223d
Initial implementation of property cycling
WeatherGod Jul 14, 2015
0c1e609
Fix some typos and handle some other codes using color cycling
WeatherGod Jul 14, 2015
61d10f9
Make dict access safer
WeatherGod Jul 15, 2015
e356972
Added some tests for baseline features
WeatherGod Jul 15, 2015
68f0afb
Updated the fill test to get the hatching.
WeatherGod Jul 16, 2015
c806726
fix typo
WeatherGod Jul 28, 2015
9965f5f
should have linted
WeatherGod Jul 28, 2015
5fa07f2
Getting cycling to work!
WeatherGod Aug 14, 2015
98fb775
Add deprecation notice for axes.color_cycle
WeatherGod Aug 15, 2015
f443629
Some axes.prop_cycler param improvements.
WeatherGod Aug 15, 2015
648d3ea
Made the linewidth thicker for a test
WeatherGod Aug 15, 2015
2e7b6a5
Updated styles and templates for prop_cycle
WeatherGod Aug 15, 2015
a1b822f
Some documentation and updating an old example
WeatherGod Aug 16, 2015
6df7829
PEP8 fixes
WeatherGod Aug 16, 2015
871d616
Simplify set/getdefaults logic for property cycler
WeatherGod Aug 17, 2015
e7786d0
Added basic validation/normalization for cycler
WeatherGod Aug 17, 2015
153ef67
* aliases support
WeatherGod Aug 17, 2015
8ce4797
Support and test for marker cycling
WeatherGod Aug 17, 2015
f3e8394
* Fix cycler advance bug (and added test)
WeatherGod Aug 17, 2015
6b5a6a5
Added the deprecation notice to api_changes/
WeatherGod Aug 17, 2015
c72a74f
PEP8 fixes
WeatherGod Aug 17, 2015
53f1ff2
Fix test now that we normalize property names
WeatherGod Aug 17, 2015
9a31950
some simplification and doc-fixes
WeatherGod Aug 19, 2015
55bf053
Fix a typo
WeatherGod Aug 20, 2015
44f74e3
Finalized the validating cycler design.
WeatherGod Aug 22, 2015
5a3ce17
reduce() isn't built-in in py3k. go figure...
WeatherGod Aug 23, 2015
9848661
Improve set_prop_cycle(). Docstring fixes.
WeatherGod Aug 24, 2015
63d6321
Realized that I didn't have a hatch validator
WeatherGod Aug 24, 2015
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
17 changes: 17 additions & 0 deletions doc/api/api_changes/2015-08-17-remove_color_cycle.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
`color_cycle` deprecated
````````````````````````

In light of the new property cycling feature,
the Axes method *set_color_cycle* is now deprecated.
Calling this method will replace the current property cycle with
one that cycles just the given colors.

Similarly, the rc parameter *axes.color_cycle* is also deprecated in
lieu of the new *axes.prop_cycle* parameter. Having both parameters in
the same rc file is not recommended as the result cannot be
predicted. For compatibility, setting *axes.color_cycle* will
replace the cycler in *axes.prop_cycle* with a color cycle.
Accessing *axes.color_cycle* will return just the color portion
of the property cycle, if it exists.

Timeline for removal has not been set.
16 changes: 16 additions & 0 deletions doc/users/whats_new/rcparams.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
Added ``axes.prop_cycle`` key to rcParams
`````````````````````````````````````````
This is a more generic form of the now-deprecated ``axes.color_cycle`` param.
Now, we can cycle more than just colors, but also linestyles, hatches,
and just about any other artist property. Cycler notation is used for
defining proprty cycles. Adding cyclers together will be like you are
`zip()`-ing together two or more property cycles together::

axes.prop_cycle: cycler('color', 'rgb') + cycler('lw', [1, 2, 3])

You can even multiply cyclers, which is like using `itertools.product()`
on two or more property cycles. Remember to use parentheses if writing
a multi-line `prop_cycle` parameter.

..plot:: mpl_examples/color/color_cycle_demo.py

Added ``errorbar.capsize`` key to rcParams
``````````````````````````````````````````
Controls the length of end caps on error bars. If set to zero, errorbars
Expand Down
19 changes: 11 additions & 8 deletions examples/color/color_cycle_demo.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
"""
Demo of custom color-cycle settings to control colors for multi-line plots.
Demo of custom property-cycle settings to control colors and such
for multi-line plots.

This example demonstrates two different APIs:

1. Setting the default rc-parameter specifying the color cycle.
This affects all subsequent plots.
2. Setting the color cycle for a specific axes. This only affects a single
axes.
1. Setting the default rc-parameter specifying the property cycle.
This affects all subsequent axes (but not axes already created).
2. Setting the property cycle for a specific axes. This only
affects a single axes.
"""
from cycler import cycler
import numpy as np
import matplotlib.pyplot as plt

Expand All @@ -17,13 +19,14 @@
yy = np.transpose([np.sin(x + phi) for phi in offsets])

plt.rc('lines', linewidth=4)
plt.rc('axes', prop_cycle=(cycler('color', ['r', 'g', 'b', 'y']) +
cycler('linestyle', ['-', '--', ':', '-.'])))
fig, (ax0, ax1) = plt.subplots(nrows=2)

plt.rc('axes', color_cycle=['r', 'g', 'b', 'y'])
ax0.plot(yy)
ax0.set_title('Set default color cycle to rgby')

ax1.set_color_cycle(['c', 'm', 'y', 'k'])
ax1.set_prop_cycle(cycler('color', ['c', 'm', 'y', 'k']) +
cycler('lw', [1, 2, 3, 4]))
ax1.plot(yy)
ax1.set_title('Set axes color cycle to cmyk')

Expand Down
6 changes: 5 additions & 1 deletion lib/matplotlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@
from matplotlib.cbook import is_string_like, mplDeprecation
from matplotlib.compat import subprocess
from matplotlib.rcsetup import (defaultParams,
validate_backend)
validate_backend,
cycler)

import numpy
from matplotlib.externals.six.moves.urllib.request import urlopen
Expand Down Expand Up @@ -826,6 +827,8 @@ def matplotlib_fname():
'svg.embed_char_paths': ('svg.fonttype',
lambda x: "path" if x else "none", None),
'savefig.extension': ('savefig.format', lambda x: x, None),
'axes.color_cycle': ('axes.prop_cycle', lambda x: cycler('color', x),
lambda x: [c.get('color', None) for c in x]),
}

_deprecated_ignore_map = {
Expand Down Expand Up @@ -1452,6 +1455,7 @@ def tk_window_focus():
'matplotlib.tests.test_transforms',
'matplotlib.tests.test_triangulation',
'matplotlib.tests.test_widgets',
'matplotlib.tests.test_cycles',
'matplotlib.sphinxext.tests.test_tinypages',
'mpl_toolkits.tests.test_mplot3d',
'mpl_toolkits.tests.test_axes_grid1',
Expand Down
20 changes: 15 additions & 5 deletions lib/matplotlib/artist.py
Original file line number Diff line number Diff line change
Expand Up @@ -900,13 +900,20 @@ def properties(self):

def set(self, **kwargs):
"""
A tkstyle set command, pass *kwargs* to set properties
A property batch setter. Pass *kwargs* to set properties.
Will handle property name collisions (e.g., if both
'color' and 'facecolor' are specified, the property
with higher priority gets set last).

"""
ret = []
for k, v in six.iteritems(kwargs):
for k, v in sorted(kwargs.items(), reverse=True):
k = k.lower()
funcName = "set_%s" % k
func = getattr(self, funcName)
func = getattr(self, funcName, None)
if func is None:
raise TypeError('There is no %s property "%s"' %
(self.__class__.__name__, k))
ret.extend([func(v)])
return ret

Expand Down Expand Up @@ -1406,14 +1413,17 @@ def setp(obj, *args, **kwargs):
funcvals = []
for i in range(0, len(args) - 1, 2):
funcvals.append((args[i], args[i + 1]))
funcvals.extend(kwargs.items())
funcvals.extend(sorted(kwargs.items(), reverse=True))

ret = []
for o in objs:
for s, val in funcvals:
s = s.lower()
funcName = "set_%s" % s
func = getattr(o, funcName)
func = getattr(o, funcName, None)
if func is None:
raise TypeError('There is no %s property "%s"' %
(o.__class__.__name__, s))
ret.extend([func(val)])
return [x for x in cbook.flatten(ret)]

Expand Down
14 changes: 7 additions & 7 deletions lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1265,9 +1265,9 @@ def plot(self, *args, **kwargs):

Return value is a list of lines that were added.

By default, each line is assigned a different color specified by a
'color cycle'. To change this behavior, you can edit the
axes.color_cycle rcParam.
By default, each line is assigned a different style specified by a
'style cycle'. To change this behavior, you can edit the
axes.prop_cycle rcParam.

The following format string characters are accepted to control
the line style or marker:
Expand Down Expand Up @@ -2935,8 +2935,8 @@ def xywhere(xs, ys, mask):
l0, = self.plot(x, y, fmt, **kwargs)
Copy link
Member

Choose a reason for hiding this comment

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

Under what condition will plot return None ?!?

Copy link
Member

Choose a reason for hiding this comment

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

Never mind, I understand this logic now.


if ecolor is None:
if l0 is None:
ecolor = six.next(self._get_lines.color_cycle)
if l0 is None and 'color' in self._get_lines._prop_keys:
ecolor = six.next(self._get_lines.prop_cycler)['color']
else:
ecolor = l0.get_color()

Expand Down Expand Up @@ -5829,8 +5829,8 @@ 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)
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)]
else:
color = mcolors.colorConverter.to_rgba_array(color)
Expand Down
Loading