Le déplacement de la légende matplotlib en dehors de l'axe le fait découper par la boîte de figure

je connais les questions suivantes:

Matplotlib savefig avec une légende en dehors de la parcelle

comment mettre la légende hors de l'intrigue

il semble que les réponses à ces questions aient le luxe de pouvoir jouer avec le rétrécissement exact de l'axe pour que la légende corresponde.

Réduire les axes, cependant, n'est pas un idéal solution parce qu'elle rend les données plus petites, ce qui les rend en fait plus difficiles à interpréter, surtout quand elles sont complexes et qu'il y a beaucoup de choses qui se passent ... d'où la nécessité d'une grande légende

l'exemple d'une légende complexe dans la documentation démontre la nécessité de ceci parce que la légende dans leur parcelle masque 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 étendre dynamiquement la taille de la boîte de figure pour accommoder la légende de 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 l'étiquette finale "tan inversé" est en fait en dehors de la boîte de figure (et semble mal découpé - pas la qualité de publication!) enter image description here

enfin, on m'a dit que C'est un comportement normal en R et LaTeX, donc je suis un peu confus pourquoi c'est si difficile en python... Est-il une raison historique? Matlab est-il aussi pauvre en la matière?

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

159
demandé sur Community 2012-04-11 11:32:08

3 réponses

Désolé EMS, mais je viens de recevoir une autre réponse de la liste de publipostage matplotlib (merci à Benjamin Root).

le code que je cherche est le réglage de l'appel savefig à:

fig.savefig('samplefigure', bbox_extra_artists=(lgd,), bbox_inches='tight')
#Note that the bbox_extra_artists must be an iterable

ceci est apparemment similaire à l'appel de tight_layout, mais à la place vous permettez à savefig de tenir compte des artistes supplémentaires dans le calcul. Cela a fait en fait redimensionner la boîte de chiffre comme désiré.

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')
handles, labels = ax.get_legend_handles_labels()
lgd = ax.legend(handles, labels, loc='upper center', bbox_to_anchor=(0.5,-0.1))
ax.grid('on')
fig.savefig('samplefigure', bbox_extra_artists=(lgd,), bbox_inches='tight')

This produit:

enter image description here

225
répondu jbbiomed 2012-06-28 08:18:40

ajouté: j'ai trouvé quelque chose qui devrait faire l'affaire tout de suite, mais le reste du code ci-dessous offre également une alternative.

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

fig.subplots_adjust(bottom=0.2) # <-- Change the 0.02 to work for your plot.

puis jouer avec l'offset dans la légende bbox_to_anchor partie de la commande de légende, pour obtenir la boîte de légende où vous le voulez. Une combinaison du réglage du figsize et de l'utilisation du subplots_adjust(bottom=...) devrait produire une parcelle de qualité pour vous.

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 modifié

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

à

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

et il s'affiche très bien sur mon écran (un moniteur CRT de 24 pouces).

ici figsize=(M,N) définit la fenêtre de chiffre à m pouces par N pouce. Il suffit de jouer avec jusqu'à ce qu'il semble bon pour vous. Convertissez-le dans un format d'image plus extensible et utilisez GIMP pour éditer si nécessaire, ou simplement recadrez avec L'option LaTeX viewport lorsque vous incluez des graphiques.

18
répondu ely 2012-04-13 06:54:47

Voici une autre solution très manuelle. Vous pouvez définir la taille de l'axe et les rembourrages sont considérés en conséquence (y compris la légende et les graduations). J'espère que c'est de l'utiliser pour quelqu'un.

Exemple

(les axes sont de la même taille!):

enter image description here

Code:

#==================================================
# Plot table

colmap = [(0,0,1) #blue
         ,(1,0,0) #red
         ,(0,1,0) #green
         ,(1,1,0) #yellow
         ,(1,0,1) #magenta
         ,(1,0.5,0.5) #pink
         ,(0.5,0.5,0.5) #gray
         ,(0.5,0,0) #brown
         ,(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','human\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'])

#--------------------------------------------------
# Change padding and margins, insert legend

fig.tight_layout() #tight margins
leg = ax.legend(loc='upper left', bbox_to_anchor=(1.02, 1), borderaxespad=0)
plt.draw() #to know size of legend

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 #inches
heightAx = 3 #inches
widthTot = widthAx+padLeft+padRight+padLegend
heightTot = heightAx+padTop+padBottom

# resize ipython window (optional)
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() #to resize ipython window. Has to be done BEFORE figure resizing!

# set figure size and ax position
fig.set_size_inches(widthTot,heightTot)
ax.set_position([padLeft/widthTot, padBottom/heightTot, widthAx/widthTot, heightAx/heightTot])
plt.draw()
plt.show()
#--------------------------------------------------
#==================================================
12
répondu gebbissimo 2013-04-15 08:32:29