将matplotlib图例移到轴之外会使它被图形框截断

我对以下问题比较熟悉:

Matplotlib在plot之外保存一个图例

如何把传说的情节

似乎这些问题的答案可以随意调整坐标轴的收缩,这样传说才会符合。

然而,缩小坐标轴并不是一个理想的解决方案,因为它使数据更小,实际上更难以解释;特别是当它很复杂,有很多事情在发生的时候……因此需要一个大的图例

文档中的一个复杂图例说明了这样做的必要性,因为图中的图例实际上完全掩盖了多个数据点。

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

我希望能够动态地扩展图形框的大小,以适应不断扩展的图形图例。

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')
注意最后的标签'Inverse tan'实际上是在图形框之外的(看起来很糟糕-不是发布质量!) enter image description here

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

我在pastebin http://pastebin.com/grVjc007上有这个代码的(只有一点点)长版本

219041 次浏览

补充道:我找到了一些东西,应该做的把戏马上,但下面的其余代码也提供了一个替代方案。

使用subplots_adjust()函数将子图的底部向上移动:

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

然后在legend命令的legend bbox_to_anchor部分中使用偏移量,将传说框移到你想要的位置。设置figsize和使用subplots_adjust(bottom=...)的一些组合应该为你生成一个高质量的plot。

< >强选择: 我简单地改变了这一行:

fig = plt.figure(1)

:

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

和改变

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

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

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

这里figsize=(M,N)将图形窗口设置为M英寸乘N英寸。一直玩到你觉得合适为止。将其转换为更可伸缩的图像格式,并在必要时使用GIMP进行编辑,或者在包含图形时仅使用LaTeX viewport选项进行裁剪。

对不起,EMS,但我实际上刚刚从matplotlib邮件列表中得到了另一个响应(感谢本杰明·鲁特)。

我要找的代码是调整savefig调用:

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

这显然类似于调用tight_layout,但是你允许savefig在计算中考虑额外的艺术家。这实际上调整了图形框的大小。

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

这产生:

< img src = " https://i.imgur.com/qysSE4t.png " alt = " " >

这个问题的目的是完全避免使用任意文本的任意坐标位置,这是解决这些问题的传统方法。尽管如此,最近还是有很多编辑坚持要添加这些元素,而这种方式往往会导致代码产生错误。我现在已经修复了这些问题,并整理了任意文本,以显示如何在bbox_extra_artists算法中考虑这些问题。

这是另一个非常手工的解决方案。您可以定义轴的大小,并相应地考虑填充(包括图例和标记)。希望对别人有用。

示例(轴大小相同!):

enter image description here

代码:

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

我尝试了一个非常简单的方法,只是让图形更宽一点:

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

ab调整为适当的值,以便图例包含在图中

before

after