Mover la leyenda matplotlib fuera del eje hace que se corte por el cuadro de figura


Estoy familiarizado con las siguientes preguntas:

Matplotlib savefig con una leyenda fuera de la trama

Cómo poner la leyenda de la trama

Parece que las respuestas en estas preguntas tienen el lujo de poder jugar con la contracción exacta del eje para que la leyenda encaje.

Reducir los ejes, sin embargo, no es una solución ideal porque hace que los datos sean más pequeños, lo que en realidad es más difícil de interpretar; particularmente cuando su complejo y hay un montón de cosas pasando ... por lo tanto, necesita una gran leyenda

El ejemplo de una leyenda compleja en la documentación demuestra la necesidad de esto porque la leyenda en su parcela en realidad oscurece completamente varios puntos de datos.

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

Lo que me gustaría poder hacer es expandir dinámicamente el tamaño del cuadro de figura a acomodar la leyenda de la figura en expansión.

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

Observe cómo la etiqueta final 'Inverse tan' está realmente fuera del cuadro de la figura (y se ve mal cortada - no la calidad de la publicación!) introduzca la descripción de la imagen aquí

Finalmente, me han dicho que este es un comportamiento normal en R y LaTeX, así que estoy un poco confundido por qué esto es tan difícil en python... Hay una razón histórica? ¿Es Matlab igualmente pobre en este asunto?

Tengo la versión (solo ligeramente) más larga de este código en pastebin http://pastebin.com/grVjc007

Author: Community, 2012-04-11

3 answers

Lo siento EMS, pero en realidad acabo de recibir otra respuesta de la lista de mailling matplotlib (Gracias a Benjamin Root).

El código que estoy buscando está ajustando la llamada savefig a:

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

Esto es aparentemente similar a llamar a tight_layout, pero en su lugar permite que savefig considere artistas adicionales en el cálculo. Esto hizo, de hecho, cambiar el tamaño de la caja de la figura como se desee.

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

Esto produce:

introduzca la descripción de la imagen aquí

 225
Author: jbbiomed,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-06-28 08:18:40

Agregado: Encontré algo que debería hacer el truco de inmediato, pero el resto del código a continuación también ofrece una alternativa.

Use la función subplots_adjust() para mover la parte inferior de la subtrama hacia arriba:

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

Luego juegue con el desplazamiento en la parte de leyenda bbox_to_anchor del comando leyenda, para obtener el cuadro de leyenda donde lo desee. Una combinación de configurar figsize y usar subplots_adjust(bottom=...) debería producir un gráfico de calidad para usted.

Alternativa: Simplemente se cambió la línea:

fig = plt.figure(1)

A:

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

Y cambiado

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

A

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

Y aparece bien en mi pantalla (un monitor CRT de 24 pulgadas).

Aquí figsize=(M,N) establece la ventana de la figura para que sea M pulgadas por N pulgadas. Solo juega con esto hasta que se vea bien para ti. Conviértalo a un formato de imagen más escalable y use GIMP para editar si es necesario, o simplemente recorte con la opción LaTeX viewport cuando incluya gráficos.

 18
Author: ely,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-13 06:54:47

Aquí hay otra solución muy manual. Puede definir el tamaño del eje y los rellenos se consideran en consecuencia (incluidas la leyenda y las marcas de verificación). Espero que sea útil para alguien.

Ejemplo (¡el tamaño de los ejes es el mismo!):

introduzca la descripción de la imagen aquí

Código:

#==================================================
# 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
Author: gebbissimo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-15 08:32:29