Get matplotlib color cycle state
Asked Answered
U

10

105

Is it possible to query the current state of the matplotlib color cycle? In other words is there a function get_cycle_state that will behave in the following way?

>>> plot(x1, y1)
>>> plot(x2, y2)
>>> state = get_cycle_state()
>>> print state
2

Where I expect the state to be the index of the next color that will be used in a plot. Alternatively, if it returned the next color ("r" for the default cycle in the example above), that would be fine too.

Ulphiah answered 12/12, 2012 at 1:50 Comment(1)
This can be done, even without changing the current state, see answer belowInexcusable
T
130

Accessing the color cycle iterator

There's no "user-facing" (a.k.a. "public") method to access the underlying iterator, but you can access it through "private" (by convention) methods. However, you'd can't get the state of an iterator without changing it.

Setting the color cycle

Quick aside: You can set the color/property cycle in a variety of ways (e.g. ax.set_color_cycle in versions <1.5 or ax.set_prop_cycler in >=1.5). Have a look at the example here for version 1.5 or greater, or the previous style here.

Accessing the underlying iterator

However, while there's no public-facing method to access the iterable, you can access it for a given axes object (ax) through the _get_lines helper class instance. ax._get_lines is a touch confusingly named, but it's the behind-the-scenes machinery that allows the plot command to process all of the odd and varied ways that plot can be called. Among other things, it's what keeps track of what colors to automatically assign. Similarly, there's ax._get_patches_for_fill to control cycling through default fill colors and patch properties.

At any rate, the color cycle iterable is ax._get_lines.color_cycle for lines and ax._get_patches_for_fill.color_cycle for patches. On matplotlib >=1.5, this has changed to use the cycler library, and the iterable is called prop_cycler instead of color_cycle and yields a dict of properties instead of only a color.

All in all, you'd do something like:

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
color_cycle = ax._get_lines.color_cycle
# or ax._get_lines.prop_cycler on version >= 1.5
# Note that prop_cycler cycles over dicts, so you'll want next(cycle)['color']

You can't view the state of an iterator

However, this object is a "bare" iterator. We can easily get the next item (e.g. next_color = next(color_cycle), but that means that the next color after that is what will be plotted. By design, there's no way to get the current state of an iterator without changing it.

In v1.5 or greater, it would be nice to get the cycler object that's used, as we could infer its current state. However, the cycler object itself isn't accessible (publicly or privately) anywhere. Instead, only the itertools.cycle instance created from the cycler object is accessible. Either way, there's no way to get to the underlying state of the color/property cycler.

Match the color of the previously plotted item instead

In your case, it sounds like you're wanting to match the color of something that was just plotted. Instead of trying to determine what the color/property will be, set the color/etc of your new item based on the properties of what's plotted.

For example, in the case you described, I'd do something like this:

import matplotlib.pyplot as plt
import numpy as np

def custom_plot(x, y, **kwargs):
    ax = kwargs.pop('ax', plt.gca())
    base_line, = ax.plot(x, y, **kwargs)
    ax.fill_between(x, 0.9*y, 1.1*y, facecolor=base_line.get_color(), alpha=0.5)

x = np.linspace(0, 1, 10)
custom_plot(x, x)
custom_plot(x, 2*x)
custom_plot(x, -x, color='yellow', lw=3)

plt.show()

enter image description here

It's not the only way, but its cleaner than trying to get the color of the plotted line before-hand, in this case.

Tritium answered 12/12, 2012 at 2:24 Comment(11)
I'm writing some higher-level functions to automate common plotting tasks for my domain of work. Often these entail drawing multiple matplotlib objects, and I'd like to have the several objects share a color in the color cycle, so I don't always have to provide a color argument if I am feeling lazy.Ulphiah
For example, I have a function that takes 2D data with the dimensions corresponding to (observations, time) and draws a timecourse line (representing the mean over observations) and shaded error bands (representing the variance over observations). I'd like those to be the same color.Ulphiah
If you're just wanting to match the color of a particular object, you can always grab its color. e.g. line, = ax.plot(x, y) and then use line.get_color() to get the color of the previously-plotted line.Tritium
Sure, the problem is that sometimes I do want to be able to specify a color for these things, so my function takes a color=None argument, but passing None to the color argument for maplotlob raises an exception. So I need to be able to know what color I'm going to use before I plot the first thing.Ulphiah
If you don't mind using **kwargs, it makes things much cleaner, in this case. Also, if you decide later that you want to specify another property (e.g. linewidth) that you didn't originally explicitly code in, it allows everything to be passed on to plot. I like to add the ability to explicitly specify an axes object to plot on when I write similar functions, so I usually do something along the lines of ax = kwargs.pop('ax', plt.gca()).Tritium
It seems ax._get_lines.color_cycle no longer exists in 1.5?Cypsela
@Cypsela - Yeah, they've changed the cycle mechanism to use Tom's new cycler library. It allows much more flexible style cycling. Offhand, I don't remember what the equivalent is, and I'm traveling without a laptop. If you find it, feel free to edit the question. I'll try to update it later, if I don't forget.Tritium
I think the (near) equivalent is an iterable ax._get_lines.prop_cycler you can have a construct something like if 'color' in ax._get_lines._prop_keys: followed by next(ax._get_lines.prop_cycler)['color'] to do the equivalent of what is suggested with color_cycle in the answer, I believe. I'm not sure whether you can get an iterable of just the colors, I'd need to explore more.Roundel
@Cypsela Sure (and thanks) - but I just felt it would be better sitting in Joe's already very good and accepted answer. If he doesn't get round to putting it in by Monday - I'll stick a proper answer up there instead of the dreaded "answer-in-comments"Roundel
for matplotlib <1.5, to get the next color from the iteratore use built-in function next() on the iterator ax._get_lines.color_cycle --> next_color = next(ax._get_lines.color_cycle).Communalism
@JoeKington, Could you update for Matplotlib 3.8 which removes those?Engelhart
H
45

Here's a way that works in 1.5 which will hopefully be future-proof as it doesn't rely on methods prepended with underscores:

colors = plt.rcParams["axes.prop_cycle"].by_key()["color"]

This will give you a list of the colors defined in order for the present style.

Hinkel answered 23/7, 2016 at 6:47 Comment(3)
Works in 2.0.2.Landbert
This is what I required, thank you. Works in matplotlib 3.4.3.Ucayali
Works in matplotlib 3.5.2Italy
C
20

Note: In the latest versions of matplotlib (>= 1.5) _get_lines has changed. You now need to use next(ax._get_lines.prop_cycler)['color'] in Python 2 or 3 (or ax._get_lines.prop_cycler.next()['color'] in Python 2) to get the next color from the color cycle.

Wherever possible use the more direct approach shown in the lower part of @joe-kington's answer. As _get_lines is not API-facing it might change again in a not backward compatible manner in the future.

Canticle answered 9/11, 2015 at 11:56 Comment(1)
How do you avoid that querying the next element of the color cycler changes the cycler's state?Antiscorbutic
T
6

Sure, this will do it.

#rainbow

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0,2*np.pi)
ax= plt.subplot(1,1,1)
ax.plot(np.sin(x))
ax.plot(np.cos(x))

rainbow = ax._get_lines.color_cycle
print rainbow
for i, color in enumerate(rainbow):
    if i<10:
        print color,

Gives:

<itertools.cycle object at 0x034CB288>
r c m y k b g r c m

Here is the itertools function that matplotlib uses itertools.cycle

Edit: Thanks for the comment, it seems that it is not possible to copy an iterator. An idea would be to dump a full cycle and keep track of which value you are using, let me get back on that.

Edit2: Allright, this will give you the next color and make a new iterator that behaves as if next was not called. This does not preserve the order of coloring, just the next color value, I leave that to you.

This gives the following output, notice that steepness in the plot corresponds to index, eg first g is the bottomest graph and so on.

#rainbow

import matplotlib.pyplot as plt
import numpy as np
import collections
import itertools

x = np.linspace(0,2*np.pi)
ax= plt.subplot(1,1,1)


def create_rainbow():
    rainbow = [ax._get_lines.color_cycle.next()]
    while True:
        nextval = ax._get_lines.color_cycle.next()
        if nextval not in rainbow:
            rainbow.append(nextval)
        else:
            return rainbow

def next_color(axis_handle=ax):
    rainbow = create_rainbow()
    double_rainbow = collections.deque(rainbow)
    nextval = ax._get_lines.color_cycle.next()
    double_rainbow.rotate(-1)
    return nextval, itertools.cycle(double_rainbow)


for i in range(1,10):
    nextval, ax._get_lines.color_cycle = next_color(ax)
    print "Next color is: ", nextval
    ax.plot(i*(x))


plt.savefig("SO_rotate_color.png")
plt.show()

Console

Next color is:  g
Next color is:  c
Next color is:  y
Next color is:  b
Next color is:  r
Next color is:  m
Next color is:  k
Next color is:  g
Next color is:  c

Rotate color

Titanate answered 12/12, 2012 at 2:20 Comment(1)
Thanks! Just to clarify, it doesn't look like that returns a copy, but rather a reference to the actual cycle. So, calling rainbow.next() is actually going to change what the next plot will look like, too.Ulphiah
P
5

I just want to add onto what @Andi said above. Since color_cycle is deprecated in matplotlib 1.5, you have to use prop_cycler, however, Andi's solution (ax._get_lines.prop_cycler.next()['color']) returned this error for me:

AttributeError: 'itertools.cycle' object has no attribute 'next'

The code that worked for me was: next(ax._get_lines.prop_cycler), which actually isn't far off from @joe-kington's original response.

Personally, I ran into this problem when making a twinx() axis, which reset the color cycler. I needed a way to make the colors cycle correctly because I was using style.use('ggplot'). There might be an easier/better way to do this, so feel free to correct me.

Pascia answered 15/12, 2015 at 17:20 Comment(4)
please format your post as an answer, not as a discussion material (otherwise it is a comment), your main point being that next(ax._get_lines.prop_cycler) is a possible alternative.Schnapp
@Pascia How do you avoid that the call next(ax._get_lines.prop_cycler) actually changes the state of the color cycler?Antiscorbutic
That's actually what I was attempting to do -- change the state of the prop_cycler. If you read the accepted answer to this question, you'll see that there's no way to access the state of prop_cycler without changing its state because it's an iterable.Pascia
The error you point out is a Python 2 / Python 3 difference. Thanks for pointing this out, I edited my answer accordingly.Canticle
A
1

Since matplotlib uses itertools.cycle we can actually look through the entire color cycle and then restore the iterator to its previous state:

def list_from_cycle(cycle):
    first = next(cycle)
    result = [first]
    for current in cycle:
        if current == first:
            break
        result.append(current)

    # Reset iterator state:
    for current in cycle:
        if current == result[-1]:
            break
    return result

This should return the list without changing the state of the iterator.

Use it with matplotlib >= 1.5:

>>> list_from_cycle(ax._get_lines.prop_cycler)
[{'color': 'r'}, {'color': 'g'}, {'color': 'b'}]

or with matplotlib < 1.5:

>>> list_from_cycle(ax._get_lines.color_cycle)
['r', 'g', 'b']
Arbitrage answered 22/9, 2016 at 8:38 Comment(0)
V
1

In matplotlib version 2.2.3 there is a get_next_color() method on the _get_lines property:

import from matplotlib import pyplot as plt
fig, ax = plt.subplots()
next_color = ax._get_lines.get_next_color()

get_next_color() returns an html color string, and advances the color cycle iterator.

Voltameter answered 10/12, 2018 at 13:52 Comment(0)
F
1

The simplest way possible I could find without doing the whole loop through the cycler is ax1.lines[-1].get_color().

Fascination answered 26/12, 2019 at 20:30 Comment(0)
I
0

How to access the color (and complete style) cycle?

The current state is stored in ax._get_lines.prop_cycler. There are no built-in methods to expose the "base list" for a generic itertools.cycle, and in particular for ax._get_lines.prop_cycler (see below).

I have posted here a few functions to get info on a itertools.cycle. One could then use

style_cycle = ax._get_lines.prop_cycler
curr_style = get_cycle_state(style_cycle)  # <-- my (non-builtin) function
curr_color = curr_style['color']

to get the current color without changing the state of the cycle.


TL;DR

Where is the color (and complete style) cycle stored?

The style cycle is stored in two different places, one for the default, and one for the current axes (assuming import matplotlib.pyplot as plt and ax is an axis handler):

default_prop_cycler = plt.rcParams['axes.prop_cycle']
current_prop_cycle = ax._get_lines.prop_cycler

Note these have different classes. The default is a "base cycle setting" and it does not know about any current state for any axes, while the current knows about the cycle to follow and its current state:

print('type(default_prop_cycler) =', type(default_prop_cycler))
print('type(current_prop_cycle) =', type(current_prop_cycle))

[]: type(default_prop_cycler) = <class 'cycler.Cycler'>
[]: type(current_prop_cycle) = <class 'itertools.cycle'>

The default cycle may have several keys (properties) to cycle, and one can get only the colors:

print('default_prop_cycler.keys =', default_prop_cycler.keys)
default_prop_cycler2 = plt.rcParams['axes.prop_cycle'].by_key()
print(default_prop_cycler2)
print('colors =', default_prop_cycler2['color'])

[]: default_prop_cycler.keys = {'color', 'linestyle'}
[]: {'color': ['r', 'g', 'b', 'y'], 'linestyle': ['-', '--', ':', '-.']}
[]: colors = ['r', 'g', 'b', 'y']

One could even change the cycler to use for a given axes, after defining that custom_prop_cycler, with

ax.set_prop_cycle(custom_prop_cycler)

But there are no built-in methods to expose the "base list" for a generic itertools.cycle, and in particular for ax._get_lines.prop_cycler.

Inexcusable answered 22/12, 2019 at 8:32 Comment(0)
L
-1

minimal working example

I struggelt with this quite a few times already. This is a minimal working example for Andis answer. enter image description here

code

import numpy as np
import matplotlib.pyplot as plt


xs = np.arange(10)


fig, ax = plt.subplots()

for ii in range(3):
    color = next(ax._get_lines.prop_cycler)['color']
    lbl = 'line {:d}, color {:}'.format(ii, color)
    ys = np.random.rand(len(xs))
    ax.plot(xs, ys, color=color, label=lbl)
ax.legend()
Latinity answered 4/3, 2021 at 9:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.