Erhalten Sie den Matplotlib-Farbzyklusstatus

88

Ist es möglich, den aktuellen Status des Matplotlib-Farbzyklus abzufragen? Mit anderen Worten, gibt es eine Funktion get_cycle_state, die sich wie folgt verhält?

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

Wo ich erwarte, dass der Zustand der Index der nächsten Farbe ist, die in einem Diagramm verwendet wird. Alternativ wäre es auch in Ordnung, wenn die nächste Farbe zurückgegeben würde ("r" für den Standardzyklus im obigen Beispiel).

mwaskom
quelle
Dies kann auch ohne Änderung des aktuellen Status erfolgen, siehe Antwort unten
sancho.s ReinstateMonicaCellio

Antworten:

105

Zugriff auf den Farbzyklus-Iterator

Es gibt keine "benutzerbezogene" (auch "öffentliche") Methode, um auf den zugrunde liegenden Iterator zuzugreifen, aber Sie können über "private" (gemäß Konvention) Methoden darauf zugreifen. Sie können den Status eines jedoch nicht erhalten, iteratorohne ihn zu ändern.

Einstellen des Farbzyklus

Schnell beiseite: Sie können den Farb- / Eigenschaftszyklus auf verschiedene Arten einstellen (z. B. ax.set_color_cyclein Versionen <1.5 oder ax.set_prop_cyclerin> = 1.5). Schauen Sie sich das Beispiel hier für Version 1.5 oder höher oder den vorherigen Stil hier an .

Zugriff auf den zugrunde liegenden Iterator

Es gibt zwar keine öffentlich zugängliche Methode für den Zugriff auf die iterable Datei, Sie können jedoch für ein bestimmtes Achsenobjekt ( ax) über die Hilfsklasseninstanz darauf zugreifen _get_lines. ax._get_linesist eine verwirrend benannte Berührung, aber es ist die Maschinerie hinter den Kulissen, die es dem plotBefehl ermöglicht, alle seltsamen und unterschiedlichen Arten zu verarbeiten, plotdie aufgerufen werden können. Unter anderem wird verfolgt, welche Farben automatisch zugewiesen werden sollen. Ebenso können Sie das ax._get_patches_for_fillDurchlaufen der Standardfüllfarben und Patch-Eigenschaften steuern.

In jedem Fall gilt der iterierbare Farbzyklus ax._get_lines.color_cyclefür Linien und ax._get_patches_for_fill.color_cyclePatches. Bei matplotlib> = 1.5 wurde dies geändert, um die cyclerBibliothek zu verwenden , und die iterable wird prop_cycleranstelle von color_cycleund ruft eine dictvon Eigenschaften anstelle von nur einer Farbe auf.

Alles in allem würden Sie so etwas tun wie:

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

Sie können den Status eines nicht anzeigen iterator

Dieses Objekt ist jedoch "nackt" iterator. Wir können leicht das nächste Element erhalten (z. B. next_color = next(color_cycle)aber das bedeutet, dass die nächste Farbe danach geplottet wird. Konstruktionsbedingt gibt es keine Möglichkeit, den aktuellen Status eines Iterators abzurufen, ohne ihn zu ändern.

In v1.5oder höher wäre es schön, das verwendete cyclerObjekt zu erhalten , da wir auf seinen aktuellen Zustand schließen könnten. Das cyclerObjekt selbst ist jedoch nirgendwo (öffentlich oder privat) zugänglich. Stattdessen kann nur auf die itertools.cycleaus dem cyclerObjekt erstellte Instanz zugegriffen werden. In beiden Fällen gibt es keine Möglichkeit, zum zugrunde liegenden Status des Farb- / Eigenschaftszyklers zu gelangen.

Passen Sie stattdessen die Farbe des zuvor gezeichneten Elements an

In Ihrem Fall klingt es so, als ob Sie die Farbe von etwas anpassen möchten, das gerade gezeichnet wurde. Anstatt zu versuchen, die Farbe / Eigenschaft zu bestimmen, legen Sie die Farbe / usw. Ihres neuen Elements basierend auf den Eigenschaften der gezeichneten Elemente fest.

In dem von Ihnen beschriebenen Fall würde ich beispielsweise Folgendes tun:

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

Geben Sie hier die Bildbeschreibung ein

Dies ist nicht der einzige Weg, aber in diesem Fall sauberer als der Versuch, die Farbe der gezeichneten Linie vorher zu ermitteln.

Joe Kington
quelle
Ich schreibe einige übergeordnete Funktionen, um allgemeine Plotaufgaben für meinen Arbeitsbereich zu automatisieren. Oft müssen mehrere Matplotlib-Objekte gezeichnet werden, und ich möchte, dass die verschiedenen Objekte im Farbzyklus eine Farbe gemeinsam haben, sodass ich nicht immer ein Farbargument angeben muss, wenn ich mich faul fühle.
Mwaskom
12
Wenn Sie nur die Farbe eines bestimmten Objekts anpassen möchten, können Sie jederzeit dessen Farbe abrufen. zB line, = ax.plot(x, y)und dann verwenden line.get_color(), um die Farbe der zuvor gezeichneten Linie zu erhalten.
Joe Kington
3
Es scheint ax._get_lines.color_cyclenicht mehr in 1.5 zu existieren?
Endolith
6
Ich denke, das (nahe) Äquivalent ist eine Iterierbarkeit. ax._get_lines.prop_cyclerSie können ein Konstrukt haben, if 'color' in ax._get_lines._prop_keys:gefolgt von next(ax._get_lines.prop_cycler)['color']dem Äquivalent dessen, was color_cyclein der Antwort vorgeschlagen wird, glaube ich. Ich bin mir nicht sicher, ob Sie nur die Farben iterieren können. Ich müsste mehr erforschen.
J Richard Snape
1
@endolith Sicher (und danke) - aber ich hatte nur das Gefühl, es wäre besser, in Joes bereits sehr guter und akzeptierter Antwort zu sitzen. Wenn er es nicht schafft, es bis Montag einzureichen - ich werde dort oben eine richtige Antwort anstelle der gefürchteten "Antwort in Kommentaren"
festhalten
25

Hier ist ein Weg, der in 1.5 funktioniert und hoffentlich zukunftssicher ist, da er nicht auf Methoden beruht, denen Unterstriche vorangestellt sind:

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

Dadurch erhalten Sie eine Liste der Farben, die für den aktuellen Stil definiert wurden.

Mike DePalatis
quelle
1
Funktioniert in 2.0.2.
KevinG
16

Hinweis: In den neuesten Versionen von matplotlib (> = 1.5) _get_lineswurde geändert. Sie müssen jetzt next(ax._get_lines.prop_cycler)['color']in Python 2 oder 3 (oder ax._get_lines.prop_cycler.next()['color']in Python 2 ) verwenden, um die nächste Farbe aus dem Farbzyklus zu erhalten.

Verwenden Sie nach Möglichkeit den direkteren Ansatz, der im unteren Teil der Antwort von @ joe-kington gezeigt wird. Da _get_lineses sich nicht um eine API handelt, kann es sich in Zukunft möglicherweise nicht mehr abwärtskompatibel ändern.

Und ich
quelle
Wie vermeiden Sie, dass das Abfragen des nächsten Elements des Farbzyklers den Status des Zyklers ändert?
Kazemakase
6

Sicher, das wird es tun.

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

Gibt:

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

Hier ist die Funktion , die itertools matplotlib Anwendungen itertools.cycle

Bearbeiten: Danke für den Kommentar, es scheint, dass es nicht möglich ist, einen Iterator zu kopieren. Eine Idee wäre, einen vollständigen Zyklus zu sichern und zu verfolgen, welchen Wert Sie verwenden. Lassen Sie mich darauf zurückkommen.

Edit2: In Ordnung, dies gibt Ihnen die nächste Farbe und erstellt einen neuen Iterator, der sich so verhält, als ob next nicht aufgerufen wurde. Dies behält nicht die Reihenfolge der Färbung bei, nur den nächsten Farbwert, das überlasse ich Ihnen.

Dies ergibt die folgende Ausgabe. Beachten Sie, dass die Steilheit im Diagramm dem Index entspricht, z. B. ist das erste g das unterste Diagramm und so weiter.

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

Konsole

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

Farbe drehen

Arynaq
quelle
Vielen Dank! Zur Verdeutlichung sieht es nicht so aus, als würde eine Kopie zurückgegeben, sondern ein Verweis auf den tatsächlichen Zyklus. Wenn Sie also rainbow.next () aufrufen, ändert sich auch, wie das nächste Diagramm aussehen wird.
Mwaskom
5

Ich möchte nur hinzufügen, was @Andi oben gesagt hat. Da color_cyclees in matplotlib 1.5 veraltet ist, müssen Sie prop_cyclerjedoch verwenden. Andis Lösung ( ax._get_lines.prop_cycler.next()['color']) hat diesen Fehler für mich zurückgegeben:

AttributeError: Das Objekt 'itertools.cycle' hat kein Attribut 'next'.

Der Code, der für mich funktioniert hat, war : next(ax._get_lines.prop_cycler), was eigentlich nicht weit von @ joe-kingtons ursprünglicher Antwort entfernt ist.

Persönlich bin ich auf dieses Problem gestoßen, als ich eine twinx () -Achse erstellt habe, die den Farbzyklus zurückgesetzt hat. Ich brauchte einen Weg, um die Farben richtig laufen zu lassen, weil ich sie benutzte style.use('ggplot'). Es könnte einen einfacheren / besseren Weg geben, dies zu tun. Sie können mich also gerne korrigieren.

Carson
quelle
Bitte formatieren Sie Ihren Beitrag als Antwort , nicht als Diskussionsmaterial (ansonsten handelt es sich um einen Kommentar). Ihr Hauptpunkt ist, dass dies next(ax._get_lines.prop_cycler)eine mögliche Alternative ist.
UmNyobe
@Carson Wie vermeiden Sie, dass der Anruf next(ax._get_lines.prop_cycler)tatsächlich den Status des Farbzyklers ändert?
Kazemakase
Genau das habe ich versucht - den Zustand der zu ändern prop_cycler. Wenn Sie die akzeptierte Antwort auf diese Frage lesen, werden Sie feststellen, dass es keine Möglichkeit gibt, auf den Status von zuzugreifen, prop_cyclerohne dessen Status zu ändern, da er iterierbar ist.
Carson
Der Fehler, auf den Sie hinweisen, ist ein Unterschied zwischen Python 2 und Python 3. Vielen Dank für den Hinweis, ich habe meine Antwort entsprechend bearbeitet.
Andi
1

Da matplotlib verwendet itertools.cycle, können wir tatsächlich den gesamten Farbzyklus durchsehen und dann den vorherigen Zustand des Iterators wiederherstellen:

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

Dies sollte die Liste zurückgeben, ohne den Status des Iterators zu ändern.

Verwenden Sie es mit matplotlib> = 1.5:

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

oder mit matplotlib <1,5:

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

Der einfachste Weg, den ich finden könnte, ohne die gesamte Schleife durch den Cycler zu machen, ist ax1.lines[-1].get_color().

Akim AKBA5439
quelle
-1

In matplotlib Version 2.2.3 gibt es eine get_next_color()Methode für die _get_linesEigenschaft:

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

get_next_color() Gibt eine HTML-Farbzeichenfolge zurück und erweitert den Farbzyklus-Iterator.

jkokorian
quelle
-1

Wie greife ich auf den Farbzyklus (und den vollständigen Stilzyklus) zu?

Der aktuelle Status wird in gespeichert ax._get_lines.prop_cycler. Es gibt keine integrierten Methoden, um die "Basisliste" für ein Generikum itertools.cycleund insbesondere für ax._get_lines.prop_cycler(siehe unten) verfügbar zu machen.

Ich habe hier einige Funktionen gepostet , um Informationen zu erhalten itertools.cycle. Man könnte dann gebrauchen

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

um die aktuelle Farbe zu erhalten, ohne den Status des Zyklus zu ändern .


TL; DR

Wo ist der Farbzyklus (und der vollständige Stilzyklus) gespeichert?

Der Stilzyklus wird an zwei verschiedenen Stellen gespeichert, eine für die Standardachse und eine für die aktuellen Achsen (vorausgesetzt import matplotlib.pyplot as pltund axist ein Achsenhandler):

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

Beachten Sie, dass diese unterschiedliche Klassen haben. Die Standardeinstellung ist eine "Basiszykluseinstellung" und kennt keinen aktuellen Status für Achsen, während der Strom den folgenden Zyklus und seinen aktuellen Status kennt:

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

Der Standardzyklus kann mehrere Schlüssel (Eigenschaften) zum Zyklisieren haben, und man kann nur die Farben erhalten:

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

Man könnte sogar die cyclerVerwendung für eine gegebene axes, nachdem man das definiert hat custom_prop_cycler, mit ändern

ax.set_prop_cycle(custom_prop_cycler)

Es gibt jedoch keine integrierten Methoden, um die "Basisliste" für ein Generikum itertools.cycleund insbesondere für anzuzeigen ax._get_lines.prop_cycler.

sancho.s ReinstateMonicaCellio
quelle