Ottieni lo stato del ciclo del colore matplotlib


89

È possibile interrogare lo stato corrente del ciclo dei colori matplotlib? In altre parole, esiste una funzione get_cycle_stateche si comporterà nel modo seguente?

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

Dove mi aspetto che lo stato sia l'indice del colore successivo che verrà utilizzato in una trama. In alternativa, se restituisse il colore successivo ("r" per il ciclo predefinito nell'esempio sopra), andrebbe bene anche quello.


Questo può essere fatto, anche senza cambiare lo stato corrente, vedi la risposta sotto
sancho.s ReinstateMonicaCellio

Risposte:


109

Accesso all'iteratore del ciclo del colore

Non esiste un metodo "rivolto all'utente" (noto anche come "pubblico") per accedere all'iteratore sottostante, ma è possibile accedervi tramite metodi "privati" (per convenzione). Tuttavia, non potresti ottenere lo stato di un iteratorsenza cambiarlo.

Impostazione del ciclo del colore

Spiegazione rapida: puoi impostare il ciclo colore / proprietà in vari modi (ad esempio ax.set_color_cyclenelle versioni <1.5 o ax.set_prop_cyclerin> = 1.5). Dai un'occhiata all'esempio qui per la versione 1.5 o successiva , o allo stile precedente qui .

Accesso all'iteratore sottostante

Tuttavia, sebbene non esista un metodo rivolto al pubblico per accedere all'iterabile, è possibile accedervi per un determinato oggetto assi ( ax) tramite l' _get_linesistanza della classe helper. ax._get_linesè un tocco chiamato in modo confuso, ma è il meccanismo dietro le quinte che consente al plotcomando di elaborare tutti i modi dispari e vari che plotpossono essere chiamati. Tra le altre cose, è ciò che tiene traccia dei colori da assegnare automaticamente. Allo stesso modo, c'è ax._get_patches_for_fillda controllare il ciclo tra i colori di riempimento predefiniti e le proprietà delle patch.

In ogni caso, il ciclo di colori iterabile è ax._get_lines.color_cycleper le linee e ax._get_patches_for_fill.color_cycleper le patch. Su matplotlib> = 1.5, questo è cambiato per usare la cyclerlibreria , e l'iterabile viene chiamato prop_cyclerinvece di color_cyclee restituisce una dictdelle proprietà invece del solo un colore.

Tutto sommato, faresti qualcosa del tipo:

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

Non puoi visualizzare lo stato di un file iterator

Tuttavia, questo oggetto è un "nudo" iterator. Possiamo facilmente ottenere l'elemento successivo (ad esempio next_color = next(color_cycle), ma ciò significa che il colore successivo è quello che verrà stampato. In base alla progettazione, non c'è modo di ottenere lo stato corrente di un iteratore senza cambiarlo.

In v1.5o superiore, sarebbe bello ottenere l' cycleroggetto utilizzato, poiché potremmo dedurre il suo stato corrente. Tuttavia, l' cycleroggetto stesso non è accessibile (pubblicamente o privatamente) da nessuna parte. È invece accessibile solo l' itertools.cycleistanza creata cyclerdall'oggetto. In ogni caso, non c'è modo di arrivare allo stato sottostante del ciclatore colore / proprietà.

Abbina invece il colore dell'elemento tracciato in precedenza

Nel tuo caso, sembra che tu voglia abbinare il colore di qualcosa che è stato appena tracciato. Invece di cercare di determinare quale sarà il colore / proprietà, imposta il colore / ecc. Del tuo nuovo elemento in base alle proprietà di ciò che è stampato.

Ad esempio, nel caso che hai descritto, farei qualcosa del genere:

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

inserisci qui la descrizione dell'immagine

Non è l'unico modo, ma in questo caso è più pulito che cercare di ottenere il colore della linea tracciata in anticipo.


Sto scrivendo alcune funzioni di livello superiore per automatizzare le attività di plottaggio comuni per il mio dominio di lavoro. Spesso questi comportano il disegno di più oggetti matplotlib e mi piacerebbe che i diversi oggetti condividessero un colore nel ciclo dei colori, quindi non devo sempre fornire un argomento sul colore se mi sento pigro.
mwaskom

12
Se vuoi solo abbinare il colore di un particolare oggetto, puoi sempre afferrarne il colore. ad es. line, = ax.plot(x, y)e quindi utilizzare line.get_color()per ottenere il colore della linea tracciata in precedenza.
Joe Kington

3
Sembra ax._get_lines.color_cyclenon esista più nella 1.5?
endolith

6
Penso che l'equivalente (vicino) sia un iterabile ax._get_lines.prop_cyclerche puoi avere un costrutto simile a if 'color' in ax._get_lines._prop_keys:seguito da next(ax._get_lines.prop_cycler)['color']per fare l'equivalente di ciò che è suggerito color_cyclenella risposta, credo. Non sono sicuro che sia possibile ottenere un iterabile solo dei colori, avrei bisogno di esplorare di più.
J Richard Snape

1
@endolith Certo (e grazie), ma ho sentito che sarebbe stato meglio sedersi nella risposta già molto buona e accettata di Joe. Se non riesce a metterlo entro lunedì, inserirò una risposta adeguata lassù invece della temuta "risposta nei commenti"
J Richard Snape

25

Ecco un modo che funziona in 1.5 che si spera sia a prova di futuro in quanto non si basa su metodi preceduti da trattini bassi:

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

Questo ti darà un elenco dei colori definiti in ordine per lo stile attuale.


1
Funziona in 2.0.2.
KevinG

16

Nota: nelle ultime versioni di matplotlib (> = 1.5) _get_linesè cambiato. Ora devi usarlo next(ax._get_lines.prop_cycler)['color']in Python 2 o 3 (o ax._get_lines.prop_cycler.next()['color']in Python 2 ) per ottenere il colore successivo dal ciclo dei colori.

Ove possibile, usa l'approccio più diretto mostrato nella parte inferiore della risposta di @ joe-kington. Poiché _get_linesnon è rivolto verso l'API, potrebbe cambiare di nuovo in modo non compatibile con le versioni precedenti in futuro.


Come si evita che l'interrogazione dell'elemento successivo del ciclatore di colore cambi lo stato del ciclatore?
kazemakase

6

Certo, questo lo farà.

#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

Ecco la funzione itertools che matplotlib usa itertools.cycle

Modifica: grazie per il commento, sembra che non sia possibile copiare un iteratore. Un'idea sarebbe quella di scaricare un ciclo completo e tenere traccia del valore che stai utilizzando, permettimi di tornare su questo.

Edit2: Va bene, questo ti darà il colore successivo e creerà un nuovo iteratore che si comporta come se il prossimo non fosse stato chiamato. Questo non preserva l'ordine di colorazione, solo il valore del colore successivo, lo lascio a te.

Ciò fornisce il seguente output, si noti che la pendenza nel grafico corrisponde all'indice, ad esempio il primo g è il grafico più basso e così via.

#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

Ruota il colore


Grazie! Giusto per chiarire, non sembra che restituisca una copia, ma piuttosto un riferimento al ciclo effettivo. Quindi, chiamare rainbow.next () cambierà anche l'aspetto della trama successiva.
mwaskom

5

Voglio solo aggiungere qualcosa a quello che ha detto @Andi sopra. Poiché color_cycleè deprecato in matplotlib 1.5, devi usare prop_cycler, tuttavia, la soluzione di Andi ( ax._get_lines.prop_cycler.next()['color']) ha restituito questo errore per me:

AttributeError: l'oggetto "itertools.cycle" non ha attributo "next"

Il codice che ha funzionato per me era next(ax._get_lines.prop_cycler):, che in realtà non è molto lontano dalla risposta originale di @ joe-kington.

Personalmente, ho riscontrato questo problema durante la creazione di un asse twinx (), che ripristina il ciclatore di colori. Avevo bisogno di un modo per far scorrere i colori correttamente perché stavo usando style.use('ggplot'). Potrebbe esserci un modo più semplice / migliore per farlo, quindi sentiti libero di correggermi.


per favore formatta il tuo post come risposta , non come materiale di discussione (altrimenti è un commento), il tuo punto principale è che next(ax._get_lines.prop_cycler)è una possibile alternativa.
UmNyobe

@Carson Come eviti che la chiamata next(ax._get_lines.prop_cycler)cambi effettivamente lo stato del color cycler?
kazemakase

Questo è in realtà quello che stavo cercando di fare: cambiare lo stato del file prop_cycler. Se leggi la risposta accettata a questa domanda, vedrai che non c'è modo di accedere allo stato di prop_cyclersenza modificarne lo stato perché è un iterabile.
Carson

L'errore che hai segnalato è una differenza Python 2 / Python 3. Grazie per averlo sottolineato, ho modificato la mia risposta di conseguenza.
Andi

1

Poiché matplotlib utilizza itertools.cycle, possiamo effettivamente esaminare l'intero ciclo del colore e quindi ripristinare l'iteratore al suo stato precedente:

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

Questo dovrebbe restituire l'elenco senza modificare lo stato dell'iteratore.

Usalo con matplotlib> = 1.5:

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

o con matplotlib <1.5:

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

0

Il modo più semplice possibile che ho potuto trovare senza fare l'intero giro attraverso il ciclatore è ax1.lines[-1].get_color().


-1

Nella versione 2.2.3 di matplotlib c'è un get_next_color()metodo sulla _get_linesproprietà:

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

get_next_color() restituisce una stringa di colore html e fa avanzare l'iteratore del ciclo del colore.


-1

Come accedere al ciclo colore (e stile completo)?

Lo stato corrente viene memorizzato in ax._get_lines.prop_cycler. Non ci sono metodi incorporati per esporre la "lista di base" per un generico itertools.cycle, e in particolare per ax._get_lines.prop_cycler(vedi sotto).

Ho pubblicato qui alcune funzioni per ottenere informazioni su un file itertools.cycle. Si potrebbe quindi utilizzare

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

per ottenere il colore corrente senza modificare lo stato del ciclo .


TL; DR

Dove viene memorizzato il ciclo del colore (e dello stile completo)?

Il ciclo di stile è memorizzato in due posizioni diverse, una per l' impostazione predefinita e una per gli assi correnti (assumendo import matplotlib.pyplot as plte axsia un gestore di assi):

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

Nota che hanno classi diverse. L'impostazione predefinita è una "impostazione del ciclo di base" e non conosce lo stato corrente di alcun asse, mentre la corrente conosce il ciclo da seguire e il suo stato corrente:

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

Il ciclo predefinito può avere diverse chiavi (proprietà) da ciclare e si possono ottenere solo i colori:

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

Si potrebbe anche cambiare il cyclerda usare per un dato axes, dopo averlo definito custom_prop_cycler, con

ax.set_prop_cycle(custom_prop_cycler)

Ma non ci sono metodi incorporati per esporre la "lista di base" per un generico itertools.cycle, e in particolare per ax._get_lines.prop_cycler.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.