Obtenir l'état du cycle de couleur matplotlib

89

Est-il possible d'interroger l'état actuel du cycle de couleurs matplotlib? En d'autres termes, y a-t-il une fonction get_cycle_statequi se comportera de la manière suivante?

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

Où je m'attends à ce que l'état soit l'indice de la prochaine couleur qui sera utilisée dans un tracé. Sinon, s'il retournait la couleur suivante ("r" pour le cycle par défaut dans l'exemple ci-dessus), ce serait bien aussi.

mwaskom
la source
Cela peut être fait, même sans changer l'état actuel, voir la réponse ci
sancho.s ReinstateMonicaCellio

Réponses:

109

Accéder à l'itérateur du cycle de couleurs

Il n'y a pas de méthode "face à l'utilisateur" (aka "public") pour accéder à l'itérateur sous-jacent, mais vous pouvez y accéder via des méthodes "privées" (par convention). Cependant, vous ne pouvez pas obtenir l'état d'un iteratorsans le changer.

Définition du cycle de couleur

Mise à part rapide: Vous pouvez définir le cycle couleur / propriété de différentes manières (par exemple ax.set_color_cycledans les versions <1.5 ou ax.set_prop_cyclerdans> = 1.5). Jetez un œil à l' exemple ici pour la version 1.5 ou supérieure , ou au style précédent ici .

Accéder à l'itérateur sous-jacent

Cependant, bien qu'il n'y ait pas de méthode publique pour accéder à l'itérable, vous pouvez y accéder pour un objet axes donné ( ax) via l' _get_linesinstance de classe d'assistance. ax._get_linesest une touche qui porte un nom déroutant, mais c'est la machinerie des coulisses qui permet à la plotcommande de traiter toutes les manières étranges et variées qui plotpeuvent être appelées. Entre autres choses, c'est ce qui garde une trace des couleurs à attribuer automatiquement. De même, il ax._get_patches_for_fillfaut contrôler le cycle des couleurs de remplissage par défaut et des propriétés de patch.

Dans tous les cas, le cycle de couleur itérable ax._get_lines.color_cycleconcerne les lignes et les ax._get_patches_for_fill.color_cyclepatchs. Sur matplotlib> = 1.5, cela a changé pour utiliser la cyclerbibliothèque , et l'itérable est appelé à la prop_cyclerplace de color_cycleet donne dictdes propriétés au lieu d'une seule couleur.

Dans l'ensemble, vous feriez quelque chose comme:

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

Vous ne pouvez pas afficher l'état d'un iterator

Cependant, cet objet est un "nu" iterator. Nous pouvons facilement obtenir l'élément suivant (par exemple next_color = next(color_cycle), mais cela signifie que la couleur suivante est celle qui sera tracée. De par sa conception, il n'y a aucun moyen d'obtenir l'état actuel d'un itérateur sans le changer.

Dans v1.5ou plus, il serait bien d'obtenir l' cyclerobjet utilisé, car nous pourrions en déduire son état actuel. Cependant, l' cyclerobjet lui-même n'est accessible (publiquement ou en privé) nulle part. Au lieu de cela, seule l' itertools.cycleinstance créée à partir de l' cyclerobjet est accessible. Dans tous les cas, il n'y a aucun moyen d'accéder à l'état sous-jacent du cycleur couleur / propriété.

Faites correspondre la couleur de l'élément précédemment tracé à la place

Dans votre cas, il semble que vous vouliez faire correspondre la couleur de quelque chose qui vient d'être tracé. Au lieu d'essayer de déterminer quelle sera la couleur / la propriété, définissez la couleur / etc de votre nouvel élément en fonction des propriétés de ce qui est tracé.

Par exemple, dans le cas que vous avez décrit, je ferais quelque chose comme ceci:

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

entrez la description de l'image ici

Ce n'est pas le seul moyen, mais c'est plus propre que d'essayer d'obtenir la couleur de la ligne tracée à l'avance, dans ce cas.

Joe Kington
la source
J'écris des fonctions de niveau supérieur pour automatiser les tâches de traçage courantes pour mon domaine de travail. Souvent, cela implique de dessiner plusieurs objets matplotlib, et j'aimerais que les différents objets partagent une couleur dans le cycle de couleur, donc je n'ai pas toujours à fournir un argument de couleur si je me sens paresseux.
mwaskom
12
Si vous souhaitez simplement faire correspondre la couleur d'un objet particulier, vous pouvez toujours saisir sa couleur. par exemple line, = ax.plot(x, y), puis utilisez line.get_color()pour obtenir la couleur de la ligne précédemment tracée.
Joe Kington
3
Il semble ax._get_lines.color_cyclen'existe plus en 1.5?
endolith
6
Je pense que l'équivalent (proche) est un itérable, ax._get_lines.prop_cyclervous pouvez avoir une construction quelque chose comme if 'color' in ax._get_lines._prop_keys:suivi de next(ax._get_lines.prop_cycler)['color']pour faire l'équivalent de ce qui est suggéré color_cycledans la réponse, je crois. Je ne sais pas si vous pouvez obtenir une itération des couleurs uniquement, j'aurais besoin d'explorer davantage.
J Richard Snape
1
@endolith Bien sûr (et merci) - mais je pensais juste que ce serait mieux de s'asseoir dans la réponse déjà très bonne et acceptée de Joe. S'il ne parvient pas à le mettre d'ici lundi - je vais coller une réponse appropriée là-haut au lieu de la redoutable "réponse dans les commentaires"
J Richard Snape
25

Voici un moyen qui fonctionne dans la version 1.5 qui, espérons-le, sera à l'épreuve du temps car il ne repose pas sur des méthodes précédées de traits de soulignement:

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

Cela vous donnera une liste des couleurs définies dans l'ordre pour le style actuel.

Mike DePalatis
la source
1
Fonctionne en 2.0.2.
KevinG
16

Remarque: dans les dernières versions de matplotlib (> = 1.5) _get_linesa changé. Vous devez maintenant l'utiliser next(ax._get_lines.prop_cycler)['color']en Python 2 ou 3 (ou ax._get_lines.prop_cycler.next()['color']en Python 2 ) pour obtenir la couleur suivante du cycle de couleurs.

Dans la mesure du possible, utilisez l'approche plus directe indiquée dans la partie inférieure de la réponse de @ joe-kington. Comme il _get_linesne fait pas face à l'API, il pourrait changer à nouveau d'une manière non rétrocompatible à l'avenir.

Et moi
la source
Comment éviter que l'interrogation de l'élément suivant du cycleur de couleur change l'état du cycleur?
kazemakase
6

Bien sûr, cela le fera.

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

Donne:

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

Voici la fonction itertools que matplotlib utilise itertools.cycle

Edit: Merci pour le commentaire, il semble qu'il n'est pas possible de copier un itérateur. Une idée serait de vider un cycle complet et de garder une trace de la valeur que vous utilisez, laissez-moi revenir là-dessus.

Edit2: Très bien, cela vous donnera la couleur suivante et créera un nouvel itérateur qui se comporte comme si le prochain n'était pas appelé. Cela ne préserve pas l'ordre de coloration, juste la valeur de couleur suivante, je vous laisse cela.

Cela donne la sortie suivante, notez que la pente du tracé correspond à l'indice, par exemple, le premier g est le graphe le plus bas et ainsi de suite.

#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

Faire pivoter la couleur

arynaq
la source
Merci! Juste pour clarifier, il ne semble pas que cela renvoie une copie, mais plutôt une référence au cycle réel. Ainsi, appeler rainbow.next () va en fait changer à quoi ressemblera le prochain tracé.
mwaskom
5

Je veux juste ajouter ce que @Andi a dit ci-dessus. Comme il color_cycleest obsolète dans matplotlib 1.5, vous devez utiliser prop_cycler, cependant, la solution d'Andi ( ax._get_lines.prop_cycler.next()['color']) a renvoyé cette erreur pour moi:

AttributeError: l'objet 'itertools.cycle' n'a pas d'attribut 'next'

Le code qui a fonctionné pour moi était next(ax._get_lines.prop_cycler):, qui n'est pas loin de la réponse originale de @ joe-kington.

Personnellement, j'ai rencontré ce problème lors de la création d'un axe twinx (), qui réinitialise le cycleur de couleur. J'avais besoin d'un moyen de faire le cycle des couleurs correctement parce que j'utilisais style.use('ggplot'). Il pourrait y avoir un moyen plus facile / meilleur de le faire, alors n'hésitez pas à me corriger.

Carson
la source
veuillez formater votre message comme une réponse , pas comme un support de discussion (sinon c'est un commentaire), votre point principal étant qu'il next(ax._get_lines.prop_cycler)s'agit d'une alternative possible.
UmNyobe
@Carson Comment éviter que l'appel next(ax._get_lines.prop_cycler)change réellement l'état du cycleur de couleur?
kazemakase
C'est en fait ce que j'essayais de faire - changer l'état du prop_cycler. Si vous lisez la réponse acceptée à cette question, vous verrez qu'il n'y a aucun moyen d'accéder à l'état de prop_cyclersans changer son état car c'est un itérable.
Carson le
L'erreur que vous signalez est une différence Python 2 / Python 3. Merci d'avoir signalé cela, j'ai modifié ma réponse en conséquence.
Andi
1

Depuis l'utilisation de matplotlib, itertools.cyclenous pouvons en fait parcourir tout le cycle de couleur, puis restaurer l'itérateur à son état précédent:

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

Cela devrait renvoyer la liste sans changer l'état de l'itérateur.

Utilisez-le avec matplotlib> = 1.5:

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

ou avec matplotlib <1,5:

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

Le moyen le plus simple que je pourrais trouver sans faire toute la boucle à travers le cycleur est ax1.lines[-1].get_color().

Akim AKBA5439
la source
-1

Dans matplotlib version 2.2.3, il existe une get_next_color()méthode sur la _get_linespropriété:

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

get_next_color() renvoie une chaîne de couleurs html et fait avancer l'itérateur du cycle de couleurs.

jkokorian
la source
-1

Comment accéder au cycle couleur (et style complet)?

L'état actuel est stocké dans ax._get_lines.prop_cycler. Il n'y a pas de méthode intégrée pour exposer la "liste de base" pour un générique itertools.cycle, et en particulier pour ax._get_lines.prop_cycler(voir ci-dessous).

J'ai posté ici quelques fonctions pour obtenir des informations sur un fichier itertools.cycle. On pourrait alors utiliser

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

pour obtenir la couleur actuelle sans changer l'état du cycle .


TL; DR

Où est stocké le cycle de couleur (et le style complet)?

Le cycle de style est stocké à deux endroits différents, un pour la valeur par défaut et un pour les axes courants (en supposant que import matplotlib.pyplot as pltet axest un gestionnaire d'axe):

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

Notez que ceux-ci ont des classes différentes. La valeur par défaut est un "réglage du cycle de base" et il ne connaît aucun état actuel pour aucun axe, tandis que le courant connaît le cycle à suivre et son état actuel:

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

Le cycle par défaut peut avoir plusieurs clés (propriétés) à parcourir, et on ne peut obtenir que les couleurs:

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

On pourrait même changer le cyclerà utiliser pour une donnée axes, après avoir défini cela custom_prop_cycler, avec

ax.set_prop_cycle(custom_prop_cycler)

Mais il n'y a pas de méthode intégrée pour exposer la "liste de base" pour un générique itertools.cycle, et en particulier pour ax._get_lines.prop_cycler.

sancho.s Réintégrer MonicaCellio
la source