Obter estado do ciclo de cores matplotlib

90

É possível consultar o estado atual do ciclo de cores matplotlib? Em outras palavras, existe uma função get_cycle_stateque se comportará da seguinte maneira?

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

Onde eu espero que o estado seja o índice da próxima cor que será usada em um gráfico. Alternativamente, se ele retornasse a próxima cor ("r" para o ciclo padrão no exemplo acima), também estaria bem.

Mwaskom
fonte
Isso pode ser feito, mesmo sem alterar o estado atual, consulte a resposta abaixo
sancho.s ReinstateMonicaCellio

Respostas:

110

Acessando o iterador do ciclo de cores

Não há método "voltado para o usuário" (também conhecido como "público") para acessar o iterador subjacente, mas você pode acessá-lo por meio de métodos "privados" (por convenção). No entanto, você não pode obter o estado de um iteratorsem alterá-lo.

Configurando o ciclo de cores

À parte rápida: Você pode definir o ciclo de cores / propriedades de várias maneiras (por exemplo, ax.set_color_cyclenas versões <1.5 ou ax.set_prop_cycler> = 1.5). Dê uma olhada no exemplo aqui para a versão 1.5 ou superior , ou o estilo anterior aqui .

Acessando o iterador subjacente

No entanto, embora não haja nenhum método voltado ao público para acessar o iterável, você pode acessá-lo para um determinado objeto de eixos ( ax) por meio da _get_linesinstância da classe auxiliar. ax._get_linesé um toque com um nome confuso, mas é a maquinaria dos bastidores que permite ao plotcomando processar todas as formas estranhas e variadas que plotpodem ser chamadas. Entre outras coisas, é o que controla quais cores atribuir automaticamente. Da mesma forma, é ax._get_patches_for_fillnecessário controlar o ciclo pelas cores de preenchimento padrão e propriedades de patch.

De qualquer forma, o ciclo de cores iterável é ax._get_lines.color_cyclepara linhas e ax._get_patches_for_fill.color_cyclepatches. Em matplotlib> = 1.5, isso mudou para usar a cyclerbiblioteca , e o iterável é chamado em prop_cyclervez de color_cyclee produz um dictde propriedades em vez de apenas uma cor.

Resumindo, você faria algo como:

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']

Você não pode ver o estado de um iterator

No entanto, este objeto é um "vazio" iterator. Podemos facilmente obter o próximo item (por exemplo next_color = next(color_cycle), mas isso significa que a próxima cor depois disso é a que será plotada. Por design, não há como obter o estado atual de um iterador sem alterá-lo.

Em v1.5ou superior, seria bom pegar o cyclerobjeto que está sendo usado, pois poderíamos inferir seu estado atual. No entanto, o cyclerpróprio objeto não está acessível (pública ou privada) em qualquer lugar. Em vez disso, apenas a itertools.cycleinstância criada a partir do cyclerobjeto está acessível. De qualquer maneira, não há como chegar ao estado subjacente do ciclador de cor / propriedade.

Em vez disso, combine a cor do item traçado anteriormente

No seu caso, parece que você deseja combinar a cor de algo que acabou de ser plotado. Em vez de tentar determinar qual será a cor / propriedade, defina a cor / etc do seu novo item com base nas propriedades do que está plotado.

Por exemplo, no caso que você descreveu, eu faria algo assim:

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()

insira a descrição da imagem aqui

Não é a única maneira, mas é mais limpa do que tentar obter a cor da linha traçada de antemão, neste caso.

Joe Kington
fonte
Estou escrevendo algumas funções de nível superior para automatizar tarefas comuns de plotagem para meu domínio de trabalho. Freqüentemente, isso envolve o desenho de vários objetos matplotlib, e eu gostaria que os vários objetos compartilhassem uma cor no ciclo de cores, então nem sempre tenho que fornecer um argumento de cor se estou com preguiça.
mwaskom
12
Se você deseja apenas combinar a cor de um objeto em particular, pode sempre selecionar sua cor. por exemplo, line, = ax.plot(x, y)e use line.get_color()para obter a cor da linha traçada anteriormente.
Joe Kington
3
Parece que ax._get_lines.color_cyclenão existe mais no 1.5?
endolith
6
Acho que o equivalente (próximo) é um iterável, ax._get_lines.prop_cyclervocê pode ter uma construção algo como if 'color' in ax._get_lines._prop_keys:seguido por next(ax._get_lines.prop_cycler)['color']para fazer o equivalente do que é sugerido com color_cyclea resposta, eu acredito. Não tenho certeza se você pode obter um iterável apenas das cores, preciso explorar mais.
J Richard Snape
1
@endolith Claro (e obrigado) - mas eu simplesmente senti que seria melhor sentar-se na resposta já muito boa e aceita de Joe. Se ele não conseguir colocá-lo na segunda-feira - vou colocar uma resposta adequada lá em vez das temidas "respostas em comentários"
J Richard Snape
26

Esta é uma maneira que funciona no 1.5 e que será preparada para o futuro, pois não depende de métodos prefixados com sublinhados:

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

Isso lhe dará uma lista das cores definidas para o estilo atual.

Mike DePalatis
fonte
1
Funciona em 2.0.2.
KevinG
16

Nota: nas versões mais recentes de matplotlib (> = 1.5) _get_linesmudou. Agora você precisa usar next(ax._get_lines.prop_cycler)['color']no Python 2 ou 3 (ou ax._get_lines.prop_cycler.next()['color']no Python 2 ) para obter a próxima cor do ciclo de cores.

Sempre que possível, use a abordagem mais direta mostrada na parte inferior da resposta de @joe-kington. Como _get_linesnão é voltado para API, ele pode mudar novamente de uma maneira incompatível com versões anteriores no futuro.

E eu
fonte
Como evitar que a consulta ao próximo elemento do ciclador de cores mude o estado do ciclador?
kazemakase
6

Claro, isso vai bastar.

#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,

Dá:

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

Aqui está a função itertools que matplotlib usa itertools.cycle

Edit: Obrigado pelo comentário, parece que não é possível copiar um iterador. Uma ideia seria descartar um ciclo completo e controlar qual valor você está usando, deixe-me voltar a isso.

Edit2: Tudo bem, isso lhe dará a próxima cor e fará um novo iterador que se comporta como se o próximo não tivesse sido chamado. Isso não preserva a ordem de coloração, apenas o próximo valor de cor, deixo isso para você.

Isso dá a seguinte saída, observe que a inclinação no gráfico corresponde ao índice, por exemplo, primeiro g é o gráfico mais abaixo e assim por diante.

#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

Girar cor

arynaq
fonte
Obrigado! Só para esclarecer, não parece que isso retorna uma cópia, mas sim uma referência ao ciclo real. Portanto, chamar rainbow.next () na verdade mudará a aparência do próximo gráfico também.
mwaskom
5

Eu só quero acrescentar o que @Andi disse acima. Como color_cycleestá obsoleto no matplotlib 1.5, você deve usar prop_cycler, no entanto, a solução de Andi ( ax._get_lines.prop_cycler.next()['color']) retornou este erro para mim:

AttributeError: o objeto 'itertools.cycle' não tem o atributo 'próximo'

O código que funcionou para mim foi next(ax._get_lines.prop_cycler):, que na verdade não está muito longe da resposta original de @joe-kington.

Pessoalmente, tive esse problema ao fazer um eixo twinx (), que zera o ciclador de cores. Eu precisava de uma maneira de fazer o ciclo de cores corretamente porque eu estava usando style.use('ggplot'). Pode haver uma maneira mais fácil / melhor de fazer isso, então sinta-se à vontade para me corrigir.

Carson
fonte
por favor, formate sua postagem como uma resposta , não como um material de discussão (caso contrário, é um comentário), seu ponto principal é que next(ax._get_lines.prop_cycler)é uma alternativa possível.
UmNyobe
@Carson Como você evita que a chamada next(ax._get_lines.prop_cycler)realmente mude o estado do reciclador de cores?
kazemakase
Isso é realmente o que eu estava tentando fazer - mudar o estado do prop_cycler. Se você leu a resposta aceita para essa pergunta, verá que não há como acessar o estado de prop_cyclersem alterar seu estado, porque é iterável.
Carson
O erro que você aponta é uma diferença Python 2 / Python 3. Obrigado por apontar isso, editei minha resposta de acordo.
Andi
1

Como o matplotlib usa itertools.cycle, podemos examinar todo o ciclo de cores e, em seguida, restaurar o iterador ao seu estado anterior:

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

Isso deve retornar a lista sem alterar o estado do iterador.

Use-o com matplotlib> = 1,5:

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

ou com matplotlib <1,5:

>>> list_from_cycle(ax._get_lines.color_cycle)
['r', 'g', 'b']
Freidrichen
fonte
0

A maneira mais simples possível que eu poderia encontrar sem fazer todo o loop pelo ciclador é ax1.lines[-1].get_color().

Akim AKBA5439
fonte
-1

No matplotlib versão 2.2.3, há um get_next_color()método na _get_linespropriedade:

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

get_next_color() retorna uma string de cor html e avança o iterador do ciclo de cores.

Jkokorian
fonte
-1

Como acessar o ciclo de cores (e estilo completo)?

O estado atual é armazenado em ax._get_lines.prop_cycler. Não há métodos embutidos para expor a "lista base" de um genérico itertools.cyclee, em particular, ax._get_lines.prop_cycler(veja abaixo).

Eu postei aqui algumas funções para obter informações sobre a itertools.cycle. Pode-se então usar

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

para obter a cor atual sem alterar o estado do ciclo .


TL; DR

Onde o ciclo de cores (e estilo completo) é armazenado?

O ciclo de estilo é armazenado em dois lugares diferentes, um para o padrão e outro para os eixos atuais (assumindo import matplotlib.pyplot as plte axé um manipulador de eixo):

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

Observe que eles têm classes diferentes. O padrão é uma "configuração de ciclo base" e não sabe sobre nenhum estado atual para nenhum eixo, enquanto a corrente sabe sobre o ciclo a seguir e seu estado atual:

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'>

O ciclo padrão pode ter várias chaves (propriedades) para fazer o ciclo, e pode-se obter apenas as cores:

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']

Pode-se até mudar o cycler a ser usado para um dado axes, após defini-lo custom_prop_cycler, com

ax.set_prop_cycle(custom_prop_cycler)

Mas não há métodos internos para expor a "lista de base" para um genérico itertools.cyclee, em particular, para ax._get_lines.prop_cycler.

sancho.s ReinstateMonicaCellio
fonte