将matplotlib图例移到轴外使其被图框切断


222

我熟悉以下问题:

Matplotlib savefig在图外带有图例

如何将图例排除在情节之外

这些问题的答案似乎很奢侈,它能够摆弄轴的确切收缩,以使图例适合。

但是,缩小轴并不是一个理想的解决方案,因为它会使数据变小,从而实际上更难以解释。特别是当它复杂并且有很多事情要发生时...因此需要一个大的传说

文档中复杂图例的示例说明了此需求,因为其图中的图例实际上完全遮盖了多个数据点。

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”实际上是如何位于图形框之外的(看起来很不完整-而不是出版物质量!) 在此处输入图片说明

最后,有人告诉我这是R和LaTeX中的正常行为,所以我有些困惑,为什么在python中如此困难……是否有历史原因?Matlab在这件事上是否同样贫穷?

我在pastebin http://pastebin.com/grVjc007上有(仅略长)此代码的较长版本


10
至于原因是什么,是因为matplotlib适用于交互式绘图,而R等并非如此。(是的,在这种特殊情况下,Matlab是“同样差劲的”。)要正确执行此操作,您需要担心每次调整图形的大小,缩放或更新图例的位置时都会调整轴的大小。(实际上,这意味着每次绘制绘图时都会进行检查,这会导致速度变慢。)Ggplot等都是静态的,因此这就是为什么它们倾向于默认执行此操作,而matplotlib和matlab却不这样做。话虽如此,tight_layout()应该更改为考虑传说。
Joe Kington

3
我还在matplotlib用户邮件列表上讨论了这个问题。因此,我建议将savefig行调整为:fig.savefig('samplefigure',bbox_extra_artists =(lgd,),bbox ='tight')
jbbiomed

3
我知道matplotlib喜欢吹捧一切都在用户的控制之下,但这整个带有传奇的东西实在是太好了。如果我将图例放在外面,我显然希望它仍然可见。窗口应自行缩放以适应大小,而不是造成巨大的重新缩放麻烦。至少应该有一个默认的True选项来控制这种自动缩放行为。强迫用户进行大量的重新渲染以尝试以控件的名义获得正确的刻度数,结果相反。
Elliot

Answers:


300

抱歉,EMS,但实际上我刚刚从matplotlib邮件列表中得到了另一个答复(感谢Benjamin Root)。

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

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

这显然类似于调用紧密布局,但是您允许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')

这将产生:

[edit]这个问题的目的是完全避免使用任意文本的任意坐标放置,这是解决这些问题的传统方法。尽管如此,最近许多编辑仍坚持将它们放入,通常以导致代码引发错误的方式进行。我现在已经解决了这些问题,并整理了任意文本,以说明如何在bbox_extra_artists算法中也考虑这些问题。


1
/!\似乎仅在matplotlib> = 1.0时才起作用(Debian压缩为0.99,这不起作用)
Julien Palard 2012年

1
不能得到这个工作:(我通过在LGD以savefig,但它仍然没有调整的问题,可能是我没有使用一个插曲。
6005

8
啊! 我只需要像您一样使用bbox_inches =“ tight”。谢谢!
6005

7
很好,但是当我尝试plt.show()它时,仍然可以减少身材。有什么解决办法吗?
Agostino


23

补充:我发现应该立即解决问题的方法,但是下面的代码其余部分也提供了替代方法。

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

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

然后bbox_to_anchor,在图例命令的图例部分中使用偏移量进行播放,以在所需的位置获得图例框。设置figsize和使用的某种组合subplots_adjust(bottom=...)应该可以为您产生质量图。

备选: 我只是更改了这一行:

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进行编辑,或者viewport在包括图形时仅使用LaTeX 选项进行裁剪。


似乎这是当前最好的解决方案,尽管它仍然需要“播放直到看起来不错”,这对于自动报告生成器来说并不是一个好的解决方案。我实际上已经在使用此解决方案,真正的问题是matplotlib不能动态补偿图例位于轴的bbox之外。正如@Joe所说,tight_layout 应该考虑更多的功能,而不仅仅是轴,标题和标签。我可能会将其作为功能请求添加到matplotlib上。
jbbiomed 2012年

也对我有用,以获得足够大的图片以适应以前被切断的xlabels
Frederick Nord

1
是带有matplotlib.org中示例代码的文档
Yojimbo 2015年

14

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

示例(轴大小相同!):

在此处输入图片说明

码:

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

这并没有为我工作,直到我改变了第一plt.draw()ax.figure.canvas.draw()。我不确定为什么,但是在此更改之前,图例的大小没有得到更新。
ws_e_c421'3

如果尝试在GUI窗口上使用它,则需要更改fig.set_size_inches(widthTot,heightTot)fig.set_size_inches(widthTot,heightTot, forward=True)
ws_e_c421'3
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.