在轴外移动matplotlib图例,使它被数字框截断。

时间:2021-06-09 23:40:20

I'm familiar with the following questions:

我熟悉以下问题:

Matplotlib savefig with a legend outside the plot

在情节之外还有一个传奇故事。

How to put the legend out of the plot

如何把这个传奇故事从情节中去掉?

It seems that the answers in these questions have the luxury of being able to fiddle with the exact shrinking of the axis so that the legend fits.

似乎这些问题的答案都是可以用来摆弄轴的精确收缩的,这样传说就可以了。

Shrinking the axes, however, is not an ideal solution because it makes the data smaller making it actually more difficult to interpret; particularly when its complex and there are lots of things going on ... hence needing a large legend

然而,缩小坐标轴并不是一个理想的解决方案,因为它使数据变得更小,使其更难解释;特别是当它复杂的时候,有很多事情发生……因此需要一个大的传奇。

The example of a complex legend in the documentation demonstrates the need for this because the legend in their plot actually completely obscures multiple data points.

文档中一个复杂的图例的例子说明了这一点,因为在他们的情节中,传说实际上完全掩盖了多个数据点。

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

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

What I would like to be able to do is dynamically expand the size of the figure box to accommodate the expanding figure legend.

我想要做的是动态地扩展图形框的大小,以适应不断膨胀的图形图例。

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

Notice how the final label 'Inverse tan' is actually outside the figure box (and looks badly cutoff - not publication quality!) 在轴外移动matplotlib图例,使它被数字框截断。

请注意,最终标签“arctan”实际上是在数字框之外(而且看起来很糟糕——不是发布质量!)

Finally, I've been told that this is normal behaviour in R and LaTeX, so I'm a little confused why this is so difficult in python... Is there a historical reason? Is Matlab equally poor on this matter?

最后,我被告知这是R和LaTeX的正常行为,所以我有点搞不懂为什么这在python中如此困难……有什么历史原因吗?在这个问题上,Matlab也一样穷吗?

I have the (only slightly) longer version of this code on pastebin http://pastebin.com/grVjc007

在pastebin http://pastebin.com/grVjc007中,我有(只是稍微)更长的这段代码。

3 个解决方案

#1


200  

Sorry EMS, but I actually just got another response from the matplotlib mailling list (Thanks goes out to Benjamin Root).

对不起,我刚刚收到了matplotlib邮件列表的另一个回复(感谢本杰明Root)。

The code I am looking for is adjusting the savefig call to:

我正在寻找的代码正在调整savefig调用:

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

This is apparently similar to calling tight_layout, but instead you allow savefig to consider extra artists in the calculation. This did in fact resize the figure box as desired.

这显然类似于调用tight_layout,但是您允许savefig在计算中考虑额外的艺术家。这实际上是按需要调整数字框的大小。

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 produces:

这产生:

在轴外移动matplotlib图例,使它被数字框截断。

#2


15  

Added: I found something that should do the trick right away, but the rest of the code below also offers an alternative.

补充:我发现了一些应该马上做的事情,但是下面的代码也提供了另一种选择。

Use the subplots_adjust() function to move the bottom of the subplot up:

使用subplots_调整()函数来移动subplot的底部:

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

Then play with the offset in the legend bbox_to_anchor part of the legend command, to get the legend box where you want it. Some combination of setting the figsize and using the subplots_adjust(bottom=...) should produce a quality plot for you.

然后使用legend命令的bbox_to_anchor部分的偏移量来获得您想要的legend框。设置图形和使用subplots_调整(底部=…)的一些组合应该会为您生成一个质量图。

Alternative: I simply changed the line:

选择:我只是改变了路线:

fig = plt.figure(1)

to:

:

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

and changed

和改变

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

to

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

and it shows up fine on my screen (a 24-inch CRT monitor).

它在我的屏幕上显示很好(一个24英寸的显示器)。

Here figsize=(M,N) sets the figure window to be M inches by N inches. Just play with this until it looks right for you. Convert it to a more scalable image format and use GIMP to edit if necessary, or just crop with the LaTeX viewport option when including graphics.

在这里,figsize=(M,N)将图形窗口设置为M英寸×N英寸。只要玩这个,直到它看起来适合你。将其转换为更可缩放的图像格式,并在必要时使用GIMP进行编辑,或者在包含图形时使用LaTeX viewport选项。

#3


12  

Here is another, very manual solution. You can define the size of the axis and paddings are considered accordingly (including legend and tickmarks). Hope it is of use to somebody.

这是另一个非常手工的解决方案。您可以定义轴的大小,并相应地考虑到paddings(包括legend和tickmarks)。希望它对某人有用。

Example (axes size are the same!):

示例(轴大小相同!):

在轴外移动matplotlib图例,使它被数字框截断。

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()
#--------------------------------------------------
#==================================================

#1


200  

Sorry EMS, but I actually just got another response from the matplotlib mailling list (Thanks goes out to Benjamin Root).

对不起,我刚刚收到了matplotlib邮件列表的另一个回复(感谢本杰明Root)。

The code I am looking for is adjusting the savefig call to:

我正在寻找的代码正在调整savefig调用:

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

This is apparently similar to calling tight_layout, but instead you allow savefig to consider extra artists in the calculation. This did in fact resize the figure box as desired.

这显然类似于调用tight_layout,但是您允许savefig在计算中考虑额外的艺术家。这实际上是按需要调整数字框的大小。

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 produces:

这产生:

在轴外移动matplotlib图例,使它被数字框截断。

#2


15  

Added: I found something that should do the trick right away, but the rest of the code below also offers an alternative.

补充:我发现了一些应该马上做的事情,但是下面的代码也提供了另一种选择。

Use the subplots_adjust() function to move the bottom of the subplot up:

使用subplots_调整()函数来移动subplot的底部:

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

Then play with the offset in the legend bbox_to_anchor part of the legend command, to get the legend box where you want it. Some combination of setting the figsize and using the subplots_adjust(bottom=...) should produce a quality plot for you.

然后使用legend命令的bbox_to_anchor部分的偏移量来获得您想要的legend框。设置图形和使用subplots_调整(底部=…)的一些组合应该会为您生成一个质量图。

Alternative: I simply changed the line:

选择:我只是改变了路线:

fig = plt.figure(1)

to:

:

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

and changed

和改变

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

to

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

and it shows up fine on my screen (a 24-inch CRT monitor).

它在我的屏幕上显示很好(一个24英寸的显示器)。

Here figsize=(M,N) sets the figure window to be M inches by N inches. Just play with this until it looks right for you. Convert it to a more scalable image format and use GIMP to edit if necessary, or just crop with the LaTeX viewport option when including graphics.

在这里,figsize=(M,N)将图形窗口设置为M英寸×N英寸。只要玩这个,直到它看起来适合你。将其转换为更可缩放的图像格式,并在必要时使用GIMP进行编辑,或者在包含图形时使用LaTeX viewport选项。

#3


12  

Here is another, very manual solution. You can define the size of the axis and paddings are considered accordingly (including legend and tickmarks). Hope it is of use to somebody.

这是另一个非常手工的解决方案。您可以定义轴的大小,并相应地考虑到paddings(包括legend和tickmarks)。希望它对某人有用。

Example (axes size are the same!):

示例(轴大小相同!):

在轴外移动matplotlib图例,使它被数字框截断。

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()
#--------------------------------------------------
#==================================================