-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Color cycle handling #6291
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
Color cycle handling #6291
Changes from all commits
684dbf6
48d5b6b
649722a
3427fa7
992a258
5626c40
17b71ea
58e66c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am +.75 on this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. vs. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, that is the only other plausibly defensible position. My only reservation is that we have pushed back on this suggestion for so long and that in most cases if you are not mapping the color you probably want to be using That said, I have no real protest with this. |
||
|
||
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 | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Limited to 10 I guess, but what if you wanted to use something like Vega20 (not that we have that as a cycle at the moment)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, we should probably document that limitation. The problem with opening it up to more than 10 is that it makes backward compatibility much harder. Should Since more than 10 colors is sort of an edge case, and ill-advised even then, I think it's fine to have this limitation. |
||
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) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sentence above should be updated to say 5 ways.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for noticing. Done.