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.


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.


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.


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.


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


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.


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

0

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


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


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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.