464 votes

Axe secondaire avec twinx() : comment ajouter à la légende ?

J'ai un tracé avec deux axes y, utilisant twinx() . Je donne également des étiquettes aux lignes, et je veux les afficher avec legend() mais je ne parviens à faire apparaître les étiquettes d'un seul axe dans la légende :

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rc
rc('mathtext', default='regular')

fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(time, Swdown, '-', label = 'Swdown')
ax.plot(time, Rn, '-', label = 'Rn')
ax2 = ax.twinx()
ax2.plot(time, temp, '-r', label = 'temp')
ax.legend(loc=0)
ax.grid()
ax.set_xlabel("Time (h)")
ax.set_ylabel(r"Radiation ($MJ\,m^{-2}\,d^{-1}$)")
ax2.set_ylabel(r"Temperature ($^\circ$C)")
ax2.set_ylim(0, 35)
ax.set_ylim(-20,100)
plt.show()

Je n'obtiens donc que les étiquettes du premier axe dans la légende, et pas l'étiquette "temp" du deuxième axe. Comment puis-je ajouter cette troisième étiquette à la légende ?

enter image description here

25 votes

[ Ne faites pas cela dans un code de production, même s'il est très proche de celui-ci. Lorsque mon seul objectif est de générer un beau graphique avec la légende appropriée le plus rapidement possible, j'utilise une astuce peu glorieuse qui consiste à tracer un tableau vide sur ax avec le style que j'utilise sur ax2 : dans votre cas, ax.plot([], [], '-r', label = 'temp') . C'est beaucoup plus rapide et plus simple que de le faire correctement...

0 votes

Voir aussi stackoverflow.com/a/57484812/3642162 pour les pandas et les twinx

562voto

Paul Points 13042

Vous pouvez facilement ajouter une deuxième légende en ajoutant la ligne :

ax2.legend(loc=0)

Vous aurez ça :

enter image description here

Mais si vous voulez toutes les étiquettes sur une seule légende, vous devez faire quelque chose comme ceci :

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rc
rc('mathtext', default='regular')

time = np.arange(10)
temp = np.random.random(10)*30
Swdown = np.random.random(10)*100-10
Rn = np.random.random(10)*100-10

fig = plt.figure()
ax = fig.add_subplot(111)

lns1 = ax.plot(time, Swdown, '-', label = 'Swdown')
lns2 = ax.plot(time, Rn, '-', label = 'Rn')
ax2 = ax.twinx()
lns3 = ax2.plot(time, temp, '-r', label = 'temp')

# added these three lines
lns = lns1+lns2+lns3
labs = [l.get_label() for l in lns]
ax.legend(lns, labs, loc=0)

ax.grid()
ax.set_xlabel("Time (h)")
ax.set_ylabel(r"Radiation ($MJ\,m^{-2}\,d^{-1}$)")
ax2.set_ylabel(r"Temperature ($^\circ$C)")
ax2.set_ylim(0, 35)
ax.set_ylim(-20,100)
plt.show()

Ce qui vous donnera ceci :

enter image description here

12 votes

Cela échoue avec errorbar parcelles. Pour une solution qui les gère correctement, voir ci-dessous : stackoverflow.com/a/10129461/1319447

2 votes

Pour éviter que deux légendes ne se chevauchent, comme dans mon cas où j'ai spécifié deux .legend(loc=0), vous devez spécifier deux valeurs différentes pour la valeur d'emplacement de la légende (toutes deux différentes de 0). Voir : matplotlib.org/api/legend_api.html

3 votes

J'ai eu du mal à ajouter une seule ligne à une intrigue secondaire comportant plusieurs lignes. ax1 . Dans ce cas, utilisez lns1=ax1.lines et ensuite ajouter lns2 à cette liste.

290voto

zgana Points 321

Je ne sais pas si cette fonctionnalité est nouvelle, mais vous pouvez également utiliser la méthode get_legend_handles_labels() plutôt que de garder la trace des lignes et des étiquettes vous-même :

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rc
rc('mathtext', default='regular')

pi = np.pi

# fake data
time = np.linspace (0, 25, 50)
temp = 50 / np.sqrt (2 * pi * 3**2) \
        * np.exp (-((time - 13)**2 / (3**2))**2) + 15
Swdown = 400 / np.sqrt (2 * pi * 3**2) * np.exp (-((time - 13)**2 / (3**2))**2)
Rn = Swdown - 10

fig = plt.figure()
ax = fig.add_subplot(111)

ax.plot(time, Swdown, '-', label = 'Swdown')
ax.plot(time, Rn, '-', label = 'Rn')
ax2 = ax.twinx()
ax2.plot(time, temp, '-r', label = 'temp')

# ask matplotlib for the plotted objects and their labels
lines, labels = ax.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax2.legend(lines + lines2, labels + labels2, loc=0)

ax.grid()
ax.set_xlabel("Time (h)")
ax.set_ylabel(r"Radiation ($MJ\,m^{-2}\,d^{-1}$)")
ax2.set_ylabel(r"Temperature ($^\circ$C)")
ax2.set_ylim(0, 35)
ax.set_ylim(-20,100)
plt.show()

2 votes

C'est la seule solution qui permet de gérer les axes où les tracés se chevauchent avec les légendes (le dernier axe est celui qui doit tracer les légendes).

8 votes

Cette solution fonctionne également avec errorbar alors que la méthode acceptée échoue (elle montre une ligne et ses barres d'erreur séparément, et aucune d'entre elles n'a la bonne étiquette). De plus, c'est plus simple.

0 votes

Petit hic : cela ne fonctionne pas si vous voulez écraser l'étiquette pour ax2 et il n'y en a pas un de prévu dès le départ.

177voto

À partir de la version 2.1 de matplotlib, vous pouvez utiliser une balise légende des figures . Au lieu de ax.legend() qui produit une légende avec les poignées des axes. ax on peut créer une légende des chiffres

fig.legend(loc="upper right")

qui rassemblera toutes les poignées de tous les sous-plots de la figure. Puisqu'il s'agit d'une légende de figure, elle sera placée dans le coin de la figure, et l'attribut loc L'argument est relatif à la figure.

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0,10)
y = np.linspace(0,10)
z = np.sin(x/3)**2*98

fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x,y, '-', label = 'Quantity 1')

ax2 = ax.twinx()
ax2.plot(x,z, '-r', label = 'Quantity 2')
fig.legend(loc="upper right")

ax.set_xlabel("x [units]")
ax.set_ylabel(r"Quantity 1")
ax2.set_ylabel(r"Quantity 2")

plt.show()

enter image description here

Afin de replacer la légende dans les axes, il faut fournir un fichier bbox_to_anchor y un bbox_transform . Ce dernier serait la transformée des axes dans lesquels la légende devrait résider. Le premier peut être les coordonnées du bord défini par loc donné en coordonnées d'axes.

fig.legend(loc="upper right", bbox_to_anchor=(1,1), bbox_transform=ax.transAxes)

enter image description here

0 votes

Donc, la version 2.1 est déjà sortie ? Mais dans Anaconda 3, j'ai essayé conda upgrade matplotlib aucune version plus récente n'a été trouvée, j'utilise toujours la v.2.0.2

2 votes

C'est une façon plus propre d'obtenir le résultat final.

2 votes

Beau et python

57voto

Sub Struct Points 151

Vous pouvez facilement obtenir ce que vous voulez en ajoutant la ligne en ax :

ax.plot([], [], '-r', label = 'temp')

o

ax.plot(np.nan, '-r', label = 'temp')

Cela ne ferait rien d'autre que d'ajouter une étiquette à la légende de l'axe.

Je pense que c'est un moyen beaucoup plus facile. Il n'est pas nécessaire de suivre les lignes automatiquement lorsque vous n'avez que quelques lignes dans le second axe, car il serait assez facile de les fixer à la main comme ci-dessus. Quoi qu'il en soit, cela dépend de ce dont vous avez besoin.

Le code complet est le suivant :

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rc
rc('mathtext', default='regular')

time = np.arange(22.)
temp = 20*np.random.rand(22)
Swdown = 10*np.random.randn(22)+40
Rn = 40*np.random.rand(22)

fig = plt.figure()
ax = fig.add_subplot(111)
ax2 = ax.twinx()

#---------- look at below -----------

ax.plot(time, Swdown, '-', label = 'Swdown')
ax.plot(time, Rn, '-', label = 'Rn')

ax2.plot(time, temp, '-r')  # The true line in ax2
ax.plot(np.nan, '-r', label = 'temp')  # Make an agent in ax

ax.legend(loc=0)

#---------------done-----------------

ax.grid()
ax.set_xlabel("Time (h)")
ax.set_ylabel(r"Radiation ($MJ\,m^{-2}\,d^{-1}$)")
ax2.set_ylabel(r"Temperature ($^\circ$C)")
ax2.set_ylim(0, 35)
ax.set_ylim(-20,100)
plt.show()

L'intrigue est la suivante :

enter image description here


Mise à jour : ajouter une meilleure version :

ax.plot(np.nan, '-r', label = 'temp')

Cela ne fera rien pendant que plot(0, 0) peut modifier la portée de l'axe.


Un exemple supplémentaire de dispersion

ax.scatter([], [], s=100, label = 'temp')  # Make an agent in ax
ax2.scatter(time, temp, s=10)  # The true scatter in ax2

ax.legend(loc=1, framealpha=1)

5 votes

J'aime ça. C'est un peu laid dans la façon dont il "trompe" le système, mais si simple à mettre en œuvre.

0 votes

C'est très simple à mettre en œuvre. Mais lorsqu'on l'utilise avec la diffusion, la taille de la diffusion qui en résulte dans la légende est juste un point minuscule.

1 votes

@greeeeeeen Dans ce cas, vous devez spécifier la taille du marqueur lors de la création du nuage de points :-)

19voto

Suuuehgi Points 926

Préparation

import numpy as np
from matplotlib import pyplot as plt

fig, ax1 = plt.subplots( figsize=(15,6) )

Y1, Y2 = np.random.random((2,100))

ax2 = ax1.twinx()

Contenu

Je suis surpris que cela ne soit pas apparu jusqu'à présent, mais le plus simple est de les rassembler manuellement dans l'un des objets d'axes (qui se trouvent les uns sur les autres).

l1 = ax1.plot( range(len(Y1)), Y1, label='Label 1' )
l2 = ax2.plot( range(len(Y2)), Y2, label='Label 2', color='orange' )

ax1.legend( handles=l1+l2 )

Plot_axes

ou les faire collecter automatiquement dans la figure environnante par fig.legend() et manipuler les bbox_to_anchor paramètre :

ax1.plot( range(len(Y1)), Y1, label='Label 1' )
ax2.plot( range(len(Y2)), Y2, label='Label 2', color='orange' )

fig.legend( bbox_to_anchor=(.97, .97) )

Plot_figlegend

Finalisation

fig.tight_layout()
fig.savefig('stackoverflow.png', bbox_inches='tight')

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X