Uzyskaj stan cyklu kolorów matplotlib


89

Czy można sprawdzić aktualny stan cyklu kolorów matplotlib? Innymi słowy, czy istnieje funkcja, get_cycle_statektóra będzie zachowywać się w następujący sposób?

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

Gdzie oczekuję, że stan będzie indeksem następnego koloru, który zostanie użyty na wykresie. Alternatywnie, jeśli zwróci następny kolor („r” jako domyślny cykl w powyższym przykładzie), to też będzie w porządku.


Można to zrobić nawet bez zmiany obecnego stanu, patrz odpowiedź poniżej
sancho.s ReinstateMonicaCellio

Odpowiedzi:


109

Dostęp do iteratora cyklu kolorów

Nie ma metody „widocznej dla użytkownika” (aka „publicznej”), aby uzyskać dostęp do bazowego iteratora, ale można uzyskać do niego dostęp za pomocą metod „prywatnych” (zgodnie z konwencją). Jednak nie można uzyskać stanu iteratorbez zmiany.

Ustawianie cyklu kolorów

Krótko na bok: cykl koloru / właściwości można ustawić na różne sposoby (np. ax.set_color_cycleW wersjach <1.5 lub ax.set_prop_cycler> = 1.5). Spójrz na przykład tutaj dla wersji 1.5 lub nowszej lub do poprzedniego stylu tutaj .

Dostęp do bazowego iteratora

Jednakże, chociaż nie ma publicznej metody dostępu do iterowalnej metody, możesz uzyskać do niej dostęp dla danego obiektu axes ( ax) za pośrednictwem _get_linesinstancji klasy pomocniczej. ax._get_linesto dotyk myląco nazwany, ale to zakulisowa machina, która pozwala plotkomendzie przetwarzać wszystkie dziwne i różnorodne sposoby, które plotmożna wywołać. Między innymi śledzi, jakie kolory mają być przypisane automatycznie. Podobnie można ax._get_patches_for_fillkontrolować przełączanie między domyślnymi kolorami wypełnienia i właściwościami łat.

W każdym razie iterowalny cykl kolorów dotyczy ax._get_lines.color_cyclelinii i ax._get_patches_for_fill.color_cyclełat. Na matplotlib> = 1.5 zmieniło się to, aby używać cyclerbiblioteki , a iterowalna jest wywoływana prop_cyclerzamiast color_cyclei daje dictwłaściwości zamiast tylko koloru.

Podsumowując, zrobiłbyś coś takiego:

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

Nie możesz wyświetlić stanu pliku iterator

Jednak przedmiot ten jest „goły” iterator. Możemy łatwo uzyskać następny element (np. next_color = next(color_cycle)Ale to oznacza, że ​​następny kolor po nim jest tym, co zostanie naniesione. Zgodnie z projektem nie ma możliwości uzyskania aktualnego stanu iteratora bez zmiany go.

W v1.5lub większym, byłoby miło uzyskać cyclerużywany obiekt, ponieważ moglibyśmy wywnioskować jego aktualny stan. Jednak cyclersam obiekt nie jest nigdzie dostępny (publicznie ani prywatnie). Zamiast tego dostępna jest tylko itertools.cycleinstancja utworzona z cyclerobiektu. Tak czy inaczej, nie ma sposobu, aby dostać się do podstawowego stanu cyklera kolorów / właściwości.

Zamiast tego dopasuj kolor wydrukowanego wcześniej przedmiotu

W twoim przypadku brzmi to tak, jakbyś chciał dopasować kolor do czegoś, co właśnie zostało wykreślone. Zamiast próbować określić, jaki będzie kolor / właściwość, ustaw kolor / itp. Nowego elementu na podstawie właściwości wykreślonego elementu.

Na przykład w przypadku, który opisałeś, zrobiłbym coś takiego:

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

wprowadź opis obrazu tutaj

To nie jedyny sposób, ale jest czystszy niż próba wcześniejszego uzyskania koloru kreślonej linii, w tym przypadku.


Piszę kilka funkcji wyższego poziomu, aby zautomatyzować typowe zadania związane z kreśleniem w mojej dziedzinie pracy. Często wiąże się to z rysowaniem wielu obiektów matplotlib i chciałbym, aby kilka obiektów miało wspólny kolor w cyklu kolorów, więc nie zawsze muszę podawać argument dotyczący koloru, jeśli czuję się leniwy.
mwaskom

12
Jeśli chcesz tylko dopasować kolor konkretnego obiektu, zawsze możesz złapać jego kolor. np. line, = ax.plot(x, y)a następnie użyj, line.get_color()aby uzyskać kolor narysowanej wcześniej linii.
Joe Kington,

3
Wydaje się, że ax._get_lines.color_cyclenie istnieje już w wersji 1.5?
endolit

6
Myślę, że (bliski) odpowiednik jest iterowalny, po ax._get_lines.prop_cyclerktórym można mieć konstrukcję podobną do tej, if 'color' in ax._get_lines._prop_keys:po której następuje next(ax._get_lines.prop_cycler)['color']odpowiednik tego, co jest sugerowane color_cyclew odpowiedzi. Nie jestem pewien, czy można uzyskać iterowalne tylko kolory, musiałbym zbadać więcej.
J Richard Snape

1
@endolith Jasne (i dzięki) - ale po prostu czułem, że lepiej byłoby posłuchać już bardzo dobrej i zaakceptowanej odpowiedzi Joe. Jeśli nie zdąży umieścić go do poniedziałku - umieszczę tam właściwą odpowiedź zamiast przerażającego „odpowiedzi w komentarzach”
J Richard Snape

25

Oto sposób, który działa w 1.5, który, miejmy nadzieję, będzie przyszłościowy, ponieważ nie opiera się na metodach poprzedzonych podkreśleniami:

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

Spowoduje to wyświetlenie listy kolorów zdefiniowanych w kolejności dla obecnego stylu.


1
Działa w 2.0.2.
KevinG,

16

Uwaga: w najnowszych wersjach matplotlib (> = 1.5) _get_linesuległa zmianie. Teraz musisz użyć next(ax._get_lines.prop_cycler)['color']w Pythonie 2 lub 3 (lub ax._get_lines.prop_cycler.next()['color']w Pythonie 2 ), aby uzyskać następny kolor z cyklu kolorów.

Tam, gdzie to możliwe, stosuj bardziej bezpośrednie podejście pokazane w dolnej części odpowiedzi @ joe-kington. Ponieważ _get_linesnie jest dostępny dla interfejsu API, może się zmienić w przyszłości w sposób niezgodny wstecz.


Jak uniknąć tego, że zapytanie o następny element kolorowego cyklera zmienia jego stan?
kazemakase

6

Jasne, to wystarczy.

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

Daje:

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

Oto funkcja itertools, której używa matplotlib itertools.cycle

Edycja: Dzięki za komentarz, wygląda na to, że nie można skopiować iteratora. Pomysł polegałby na zrzuceniu pełnego cyklu i śledzeniu używanej wartości, wrócę do tego.

Edit2: W porządku, to da ci następny kolor i stworzy nowy iterator, który zachowuje się tak, jakby next nie został wywołany. To nie zachowuje kolejności kolorowania, tylko kolejną wartość koloru, zostawiam to Tobie.

Daje to następujący wynik, zauważ, że stromość na wykresie odpowiada indeksowi, np. Pierwsze g jest najniższym wykresem i tak dalej.

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

Konsola

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

Obróć kolor


Dzięki! Dla wyjaśnienia, wygląda na to, że nie zwraca kopii, ale raczej odniesienie do rzeczywistego cyklu. Zatem wywołanie rainbow.next () w rzeczywistości zmieni również wygląd następnej fabuły.
mwaskom

5

Chcę tylko dodać to, co @Andi powiedział powyżej. Ponieważ color_cyclejest przestarzałe w matplotlib 1.5, musisz jednak użyć prop_cyclerrozwiązania Andi ( ax._get_lines.prop_cycler.next()['color']) zwróciło mi ten błąd:

AttributeError: obiekt „itertools.cycle” nie ma atrybutu „next”

Kod, który działał dla mnie, to: next(ax._get_lines.prop_cycler) , to:, który właściwie nie jest daleko od oryginalnej odpowiedzi @ joe-kington.

Osobiście napotkałem ten problem podczas tworzenia osi twinx (), która resetuje cykler kolorów. Potrzebowałem sposobu, aby poprawnie zmienić cykl kolorów, ponieważ używałem style.use('ggplot'). Może istnieć łatwiejszy / lepszy sposób, aby to zrobić, więc nie krępuj się mnie poprawić.


proszę sformatować swój post jako odpowiedź , a nie materiał do dyskusji (w przeciwnym razie jest to komentarz), a Twoim głównym celem jest next(ax._get_lines.prop_cycler)to, że jest to możliwa alternatywa.
UmNyobe,

@Carson Jak uniknąć sytuacji, w której wywołanie next(ax._get_lines.prop_cycler)faktycznie zmienia stan cyklera kolorów?
kazemakase

Właśnie to próbowałem zrobić - zmienić stan pliku prop_cycler. Jeśli przeczytasz zaakceptowaną odpowiedź na to pytanie, zobaczysz, że nie ma możliwości uzyskania dostępu do stanu prop_cyclerbez zmiany jego stanu, ponieważ jest iterowalny.
Carson,

Wskazany błąd to różnica między Python 2 a Python 3. Dzięki za zwrócenie uwagi na to, odpowiednio zredagowałem odpowiedź.
Andi,

1

Ponieważ matplotlib używa itertools.cycle, możemy faktycznie przejrzeć cały cykl kolorów, a następnie przywrócić iterator do poprzedniego stanu:

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

Powinno to zwrócić listę bez zmiany stanu iteratora.

Użyj go z matplotlib> = 1.5:

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

lub z matplotlib <1.5:

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

0

Najprostszym możliwym sposobem, jaki mogłem znaleźć bez wykonywania całej pętli przez cykler, jest ax1.lines[-1].get_color().


-1

W matplotlib w wersji 2.2.3 get_next_color()na _get_lineswłaściwości istnieje metoda :

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

get_next_color() zwraca ciąg koloru html i przesuwa iterator cyklu kolorów.


-1

Jak uzyskać dostęp do cyklu kolorów (i całego stylu)?

Bieżący stan jest przechowywany w ax._get_lines.prop_cycler. Nie ma wbudowanych metod ujawniania „listy bazowej” dla generycznego itertools.cycle, aw szczególności dla ax._get_lines.prop_cycler(patrz poniżej).

Opublikowałem tutaj kilka funkcji, aby uzyskać informacje na temat pliku itertools.cycle. Można by wtedy użyć

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

aby uzyskać aktualny kolor bez zmiany stanu cyklu .


TL; DR

Gdzie przechowywany jest cykl kolorów (i całego stylu)?

Cykl stylu jest przechowywany w dwóch różnych miejscach, jednym dla domyślnych , a drugim dla bieżących osi (przy założeniu import matplotlib.pyplot as plti axjest operatorem osi):

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

Zauważ, że mają one różne klasy. Domyślnie jest to „ustawienie cyklu podstawowego” i nie ma informacji o żadnym bieżącym stanie dla żadnej osi, podczas gdy prąd wie o cyklu, który ma nastąpić i jego aktualnym stanie:

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

Domyślny cykl może mieć kilka klawiszy (właściwości) do przełączania i można uzyskać tylko kolory:

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

Można nawet zmienić cyclerużycie dla danego axes, po zdefiniowaniu tego za custom_prop_cyclerpomocą

ax.set_prop_cycle(custom_prop_cycler)

Ale nie ma wbudowanych metod ujawniania „listy bazowej” dla generycznego itertools.cycle, aw szczególności dla ax._get_lines.prop_cycler.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.