263 votes

Déplacer la légende matplotlib à l'extérieur de l'axe la fait couper par la boîte de la figure

Je suis familier avec les questions suivantes:

Matplotlib savefig with a legend outside the plot

How to put the legend out of the plot

Il semble que les réponses à ces questions ont le luxe de pouvoir jouer avec le rétrécissement exact de l'axe pour que la légende s'adapte.

Cependant, rétrécir les axes n'est pas une solution idéale car cela rend les données plus petites, ce qui rend en fait plus difficile à interpréter; particulièrement lorsque c'est complexe et qu'il y a beaucoup de choses en cours ... d'où le besoin d'une grande légende

L'exemple d'une légende complexe dans la documentation démontre le besoin car la légende dans leur graphique obscurcit en fait complètement plusieurs points de données.

http://matplotlib.sourceforge.net/users/legend_guide.html#legend-of-complex-plots

Ce que j'aimerais pouvoir faire, c'est agrandir dynamiquement la taille de la boîte de la figure pour accueillir la légende de la figure en expansion.

import matplotlib.pyplot as plt
import numpy as np

x = np.arange(-2*np.pi, 2*np.pi, 0.1)
fig = plt.figure(1)
ax = fig.add_subplot(111)
ax.plot(x, np.sin(x), label='Sine')
ax.plot(x, np.cos(x), label='Cosine')
ax.plot(x, np.arctan(x), label='Inverse tan')
lgd = ax.legend(loc=9, bbox_to_anchor=(0.5,0))
ax.grid('on')

Remarquez comment le dernier label 'Inverse tan' est en fait à l'extérieur de la boîte de la figure (et semble mal coupé - pas de qualité de publication!) entrer la description de l'image ici

Enfin, on m'a dit que c'est un comportement normal en R et en LaTeX, donc je suis un peu confus pourquoi cela est si difficile en python... Y a-t-il une raison historique? Est-ce que Matlab est tout aussi pauvre sur cette question?

J'ai la version (seulement légèrement) plus longue de ce code sur pastebin http://pastebin.com/grVjc007

10 votes

En ce qui concerne les raisons, c'est parce que matplotlib est conçu pour des graphiques interactifs, tandis que R, etc., ne le sont pas. (Et oui, Matlab est "tout aussi pauvre" dans ce cas particulier.) Pour le faire correctement, vous devez vous soucier de redimensionner les axes à chaque fois que la figure est redimensionnée, zoomée ou que la position de la légende est mise à jour. (En effet, cela signifie vérifier à chaque fois que le tracé est dessiné, ce qui entraîne des ralentissements.) Ggplot, etc., sont statiques, c'est pourquoi ils ont tendance à le faire par défaut, alors que matplotlib et matlab ne le font pas. Cela étant dit, tight_layout() devrait être modifié pour prendre en compte les légendes.

3 votes

Je discute également de cette question sur la liste de diffusion des utilisateurs de matplotlib. J'ai donc la suggestion d'ajuster la ligne savefig à: fig.savefig('samplefigure', bbox_extra_artists=(lgd,), bbox='tight')

6 votes

Je sais que matplotlib aime promouvoir le fait que tout est sous le contrôle de l'utilisateur, mais tout ce problème avec les légendes est un peu trop. Si je mets la légende à l'extérieur, je veux évidemment qu'elle reste visible. La fenêtre devrait simplement se redimensionner pour s'adapter au lieu de créer ce problème énorme de redimensionnement. Au moins, il devrait y avoir une option par défaut True pour contrôler ce comportement de redimensionnement automatique. Forcer les utilisateurs à faire un nombre ridicule de redessins pour essayer d'ajuster les chiffres d'échelle correctement au nom du contrôle aboutit à l'effet inverse.

350voto

jazzvibes Points 1270

Désolé EMS, mais je viens en fait de recevoir une autre réponse de la liste de diffusion matplotlib (Merci à Benjamin Root).

Le code que je recherche consiste à ajuster l'appel à savefig pour :

fig.savefig('figureexemple', bbox_extra_artists=(lgd,), bbox_inches='tight')
#Notez que bbox_extra_artists doit être itérable

Cela semble similaire à l'appel de tight_layout, mais au lieu de cela vous permettez à savefig de considérer des artistes supplémentaires dans le calcul. Cela a effectivement redimensionné la boîte de la figure comme souhaité.

import matplotlib.pyplot as plt
import numpy as np

plt.gcf().clear()
x = np.arange(-2*np.pi, 2*np.pi, 0.1)
fig = plt.figure(1)
ax = fig.add_subplot(111)
ax.plot(x, np.sin(x), label='Sinus')
ax.plot(x, np.cos(x), label='Cosinus')
ax.plot(x, np.arctan(x), label='Tangente inverse')
poignées, étiquettes = ax.get_legend_handles_labels()
lgd = ax.legend(poignées, étiquettes, loc='upper center', bbox_to_anchor=(0.5,-0.1))
texte = ax.text(-0.2,1.05, "Texte arbitraire", transform=ax.transAxes)
ax.set_title("Trigonométrie")
ax.grid('on')
fig.savefig('figureexemple', bbox_extra_artists=(lgd,texte), bbox_inches='tight')

Cela produit :

[éditer] L'intention de cette question était d'éviter complètement l'utilisation de placements de coordonnées arbitraires de textes arbitraires comme c'était la solution traditionnelle à ces problèmes. Malgré cela, de nombreuses modifications récentes ont insisté pour les inclure, souvent de manière à ce que le code génère une erreur. J'ai maintenant corrigé les problèmes et nettoyé le texte arbitraire pour montrer comment ceux-ci sont également pris en compte dans l'algorithme bbox_extra_artists.

[éditer] Certains des commentaires ci-dessous notent qu'à partir de 2019, la commande a été simplifiée. plt.savefig('x.png', bbox_inches='tight') était suffisant. Merci pour le partage. – mateuszb 27 juin 2019

1 votes

/!\ Semble fonctionner uniquement depuis matplotlib >= 1.0 (Debian squeeze a 0.99 et cela ne fonctionne pas)

1 votes

Ne parviens pas à faire fonctionner cela :( je passe lgd pour savefig mais il ne redimensionne toujours pas. Le problème peut être que je n'utilise pas de subplot.

10 votes

Ah! J'avais juste besoin d'utiliser bbox_inches = "tight" comme tu l'as fait. Merci!

32voto

EMS Points 9249

Ajouté: J'ai trouvé quelque chose qui devrait fonctionner immédiatement, mais le reste du code ci-dessous offre également une alternative.

Utilisez la fonction subplots_adjust() pour déplacer le bas de la sous-trame vers le haut:

fig.subplots_adjust(bottom=0.2) # <-- Changez le 0.02 pour qu'il fonctionne pour votre tracé.

Ensuite, jouez avec le décalage dans la partie bbox_to_anchor de la commande de légende, pour placer la boîte de légende où vous le souhaitez. Une combinaison de réglage de figsize et l'utilisation de subplots_adjust(bottom=...) devraient vous donner un tracé de qualité.

Alternative: J'ai simplement changé la ligne :

fig = plt.figure(1)

à :

fig = plt.figure(num=1, figsize=(13, 13), dpi=80, facecolor='w', edgecolor='k')

et changé

lgd = ax.legend(loc=9, bbox_to_anchor=(0.5,0))

à

lgd = ax.legend(loc=9, bbox_to_anchor=(0.5,-0.02))

et cela s'affiche correctement sur mon écran (un moniteur CRT de 24 pouces).

Ici figsize=(M,N) définit la fenêtre de la figure à M pouces par N pouces. Jouez simplement avec cela jusqu'à ce que cela paraisse correct pour vous. Convertissez-le dans un format d'image plus scalable et utilisez GIMP pour éditer si nécessaire, ou recadrez simplement avec l'option viewport de LaTeX lors de l'inclusion des graphiques.

0 votes

Il semblerait que cela soit la meilleure solution pour le moment, même si cela nécessite toujours de 'jouer jusqu'à ce que cela ait l'air bien', ce qui n'est pas une bonne solution pour un générateur de rapports automatique. J'utilise déjà cette solution, le véritable problème est que matplotlib ne compense pas dynamiquement le fait que la légende se trouve en dehors de la zone de tracé (bbox) de l'axe. Comme l'a dit @Joe, tight_layout devrait prendre en compte plus de fonctionnalités que seulement l'axe, les titres et les étiquettes. Je pourrais ajouter cela en tant que demande de fonctionnalité sur matplotlib.

0 votes

Il fonctionne aussi pour obtenir une image suffisamment grande pour s'adapter aux xlabels qui étaient auparavant coupés

1 votes

ici se trouve la documentation avec du code d'exemple provenant de matplotlib.org

16voto

cgebbe Points 81

Voici une autre solution, très manuelle. Vous pouvez définir la taille de l'axe et les rembourrages sont pris en compte en conséquence (y compris la légende et les marques de graduation). J'espère que cela sera utile à quelqu'un.

Exemple (taille des axes identique!) :

entrer la description de l'image ici

Code :

#==================================================
# Tableau de tracé

colmap = [(0,0,1) #bleu
         ,(1,0,0) #rouge
         ,(0,1,0) #vert
         ,(1,1,0) #jaune
         ,(1,0,1) #magenta
         ,(1,0.5,0.5) #rose
         ,(0.5,0.5,0.5) #gris
         ,(0.5,0,0) #marron
         ,(1,0.5,0) #orange
         ]

import matplotlib.pyplot as plt
import numpy as np

import collections
df = collections.OrderedDict()
df['labels']        = ['GWP100a\n[kgCO2eq]\n\nasedf\nasdf\nadfs','humain\n[pts]','ressource\n[pts]'] 
df['all-petroleum long name'] = [3,5,2]
df['all-electric']  = [5.5, 1, 3]
df['HEV']           = [3.5, 2, 1]
df['PHEV']          = [3.5, 2, 1]

numLabels = len(df.values()[0])
numItems = len(df)-1
posX = np.arange(numLabels)+1
width = 1.0/(numItems+1)

fig = plt.figure(figsize=(2,2))
ax = fig.add_subplot(111)
for iiItem in range(1,numItems+1):
  ax.bar(posX+(iiItem-1)*width, df.values()[iiItem], width, color=colmap[iiItem-1], label=df.keys()[iiItem])
ax.set(xticks=posX+width*(0.5*numItems), xticklabels=df['labels'])

#--------------------------------------------------
# Changer le rembourrage et les marges, insérer la légende

fig.tight_layout() #marges serrées
leg = ax.legend(loc='upper left', bbox_to_anchor=(1.02, 1), borderaxespad=0)
plt.draw() #pour connaître la taille de la légende

padLeft   = ax.get_position().x0 * fig.get_size_inches()[0]
padBottom = ax.get_position().y0 * fig.get_size_inches()[1]
padTop    = ( 1 - ax.get_position().y0 - ax.get_position().height ) * fig.get_size_inches()[1]
padRight  = ( 1 - ax.get_position().x0 - ax.get_position().width ) * fig.get_size_inches()[0]
dpi       = fig.get_dpi()
padLegend = ax.get_legend().get_frame().get_width() / dpi 

widthAx = 3 #pouces
heightAx = 3 #pouces
widthTot = widthAx+padLeft+padRight+padLegend
heightTot = heightAx+padTop+padBottom

# redimensionner la fenêtre ipython (facultatif)
posScreenX = 1366/2-10 #pixel
posScreenY = 0 #pixel
canvasPadding = 6 #pixel
canvasBottom = 40 #pixel
ipythonWindowSize = '{0}x{1}+{2}+{3}'.format(int(round(widthTot*dpi))+2*canvasPadding
                                            ,int(round(heightTot*dpi))+2*canvasPadding+canvasBottom
                                            ,posScreenX,posScreenY)
fig.canvas._tkcanvas.master.geometry(ipythonWindowSize) 
plt.draw() #pour redimensionner la fenêtre ipython. Doit être fait AVANT le redimensionnement de la figure!

# définir la taille de la figure et la position de l'axe
fig.set_size_inches(widthTot,heightTot)
ax.set_position([padLeft/widthTot, padBottom/heightTot, widthAx/widthTot, heightAx/heightTot])
plt.draw()
plt.show()
#--------------------------------------------------
#==================================================

0 votes

Cela n'a pas fonctionné pour moi jusqu'à ce que je change le premier plt.draw() en ax.figure.canvas.draw(). Je ne suis pas sûr pourquoi, mais avant ce changement, la taille de la légende n'était pas mise à jour.

0 votes

Si vous essayez d'utiliser ceci sur une fenêtre GUI, vous devez changer fig.set_size_inches(widthTot,heightTot) en fig.set_size_inches(widthTot,heightTot, forward=True).

2voto

Ann Points 73

J'ai essayé un moyen très simple, il suffit de rendre la figure un peu plus large :

fig, ax = plt.subplots(1, 1, figsize=(a, b))

ajuster a et b à une valeur appropriée de telle sorte que la légende soit incluse dans la figure

before

after

1voto

John Thomas Points 31

Étant donné que Google m'a conduit ici pour une question similaire, il est probablement utile de noter que maintenant vous n'avez besoin que d'utiliser plt.savefig('myplot.png', bbox_inches='tight') pour agrandir le canevas afin d'adapter une légende.

La plupart des réponses ici sont obsolètes.

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