Je connais les questions suivantes:
Matplotlib savefig avec une légende en dehors du tracé
Comment sortir la légende de l'intrigue
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’intègre.
Réduire les axes, cependant, n'est pas une solution idéale car cela réduit la taille des données, ce qui rend leur interprétation plus difficile. surtout quand c'est complexe et qu'il y a beaucoup de choses qui se passent ... donc nécessitant une grande légende
L'exemple d'une légende complexe dans la documentation en démontre la nécessité, car la légende de leur tracé masque 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 de manière dynamique la taille de la boîte de caractères afin de l'adapter à la légende des chiffres 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')
Notez que l'étiquette finale "Inverse tan" est en dehors de la zone des chiffres (et a l'air mal coupée - pas de qualité de publication!)
Enfin, on m'a dit que c'était un comportement normal dans R et LaTeX, alors je suis un peu confus pourquoi c'est si difficile en python ... Y a-t-il une raison historique? Matlab est-il également pauvre en la matière?
J'ai la version (légèrement plus longue) de ce code sur Pastebin http://Pastebin.com/grVjc007
Désolé, EMS, mais je viens tout juste de recevoir une autre réponse de la liste de diffusion matplotlib (Merci à Benjamin Root).
Le code que je recherche ajuste l'appel de savefig à:
fig.savefig('samplefigure', bbox_extra_artists=(lgd,), bbox_inches='tight')
#Note that the bbox_extra_artists must be an iterable
Cela ressemble apparemment à appeler tight_layout, mais vous permettez à savefig de prendre en compte des artistes supplémentaires dans le calcul. Cela a effectivement redimensionné la boîte de la figure comme vous le souhaitez.
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='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))
text = ax.text(-0.2,1.05, "Aribitrary text", transform=ax.transAxes)
ax.set_title("Trigonometry")
ax.grid('on')
fig.savefig('samplefigure', bbox_extra_artists=(lgd,text), bbox_inches='tight')
Cela produit:
[modifier] L'intention de cette question était d'éviter complètement l'utilisation d'emplacements de coordonnées arbitraires de texte arbitraire, comme c'était la solution traditionnelle à ces problèmes. Malgré cela, de nombreuses modifications ont récemment insisté pour les intégrer, souvent de manière à ce que le code génère une erreur. J'ai maintenant corrigé les problèmes et mis de l'ordre dans le texte arbitraire pour montrer comment ils sont également pris en compte dans l'algorithme bbox_extra_artists.
Ajouté: J'ai trouvé quelque chose qui devrait faire l'affaire 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-parcelle vers le haut:
fig.subplots_adjust(bottom=0.2) # <-- Change the 0.02 to work for your plot.
Puis jouez avec le décalage 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 souhaitez. Une combinaison du réglage de figsize
et de l’utilisation de subplots_adjust(bottom=...)
devrait produire un tracé 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 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 il apparaît bien sur mon écran (un moniteur CRT de 24 pouces).
Ici, figsize=(M,N)
définit la fenêtre de figure sur M pouces sur N pouces. Il suffit de jouer avec cela jusqu'à ce que cela vous convient Convertissez-le en un format d'image plus évolutif et utilisez GIMP pour le modifier si nécessaire, ou recadrez-le simplement avec l'option LaTeX viewport
lorsque vous incluez des graphiques.
Voici une autre solution très manuelle. Vous pouvez définir la taille de l'axe et les marges sont considérées en conséquence (y compris la légende et les graduations). J'espère que c'est utile à quelqu'un.
Exemple (la taille des axes est la même!):
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()
#--------------------------------------------------
#==================================================