2012-04-11 51 views
139

我熟悉以下问题截止:轴外移动matplotlib传说使它由图框

Matplotlib savefig with a legend outside the plot

How to put the legend out of the 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') 

注意最后标签“反正切”如何实际上是图框外(而且看上去很糟糕截止 - !不出版质量) enter image description here

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

我有(仅略)长版本的代码对引擎收录http://pastebin.com/grVjc007

+6

至于为什么是这是因为matplotlib是面向互动情节,而R,等等,不是。 (是的,在这种情况下,Matlab“同样很差”。)要正确执行此操作,每次图形调整大小,缩放或更新图例位置时,都需要考虑调整轴的大小。 (实际上,这意味着每次绘图时都要检查一下,这会导致速度变慢。)Ggplot等是静态的,所以这就是为什么他们倾向于默认这样做,而matplotlib和matlab则不这样做。之前已经说过,'tight_layout()'应该改变,以将传说考虑在内。 – 2012-04-11 16:03:58

+2

我也在matplotlib用户邮件列表上讨论这个问题。所以我有调整savefig行的建议: fig.savefig('samplefigure',bbox_extra_artists =(lgd,),bbox ='tight') – jbbiomed 2012-04-13 06:23:03

+3

我知道matplotlib喜欢吹嘘一切都在控制之下用户,但与传说的整个事情是太多的一件好事。如果我把传说放在外面,我显然希望它仍然可见。该窗口应该只是适应自己而不是创建这个巨大的缩放麻烦。至少应该有一个默认的True选项来控制这种自动缩放行为。强迫用户通过一些荒谬的重新渲染,试图以控制的名义获得正确的比例数字,完成了相反的事情。 – Elliot 2013-01-02 21:50:04

回答

190

对不起EMS,但我实际上刚刚从matplotlib mailling list中得到了另一个响应(谢谢出去Benjamin Root)。

我找的是调整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 

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

这产生:

enter image description here

+1

/!\似乎只从matplotlib> = 1.0(Debian squeeze有0.99,这不起作用)工作 – 2012-12-04 15:26:00

+1

无法得到这个工作:(我传递给lgd savefig但它仍然不能调整大小。 – 6005 2015-03-15 06:39:53

+4

啊!我只是需要像使用bbox_inches =“tight”一样,谢谢! – 6005 2015-03-15 06:44:26

13

补充:我发现了一些应该做的伎俩马上,但下面的代码的其余部分还提供了一种替代。

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

+0

看起来这是目前最好的解决方案,尽管它仍然需要'玩到看起来不错',这对于自动报告生成器来说不是一个好的解决方案。实际上我已经在使用这个解决方案,真正的问题是matplotlib不会动态补偿位于轴的bbox之外的图例。正如@Joe所说,tight_layout *应该考虑更多的功能,而不仅仅是轴,标题和标签。我可能会将其作为matplotlib上的功能请求添加。 – jbbiomed 2012-04-14 15:11:35

+0

也适用于我得到一个足够大的图片,以适应以前被切断的xlabels – 2014-02-20 17:57:16

+1

[这里](http://matplotlib.org/examples/pylab_examples/subplots_adjust.html)是来自matplotlib.org的示例代码的文档 – Yojimbo 2015-02-18 17:26:52

11

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

例(轴尺寸都是一样的!):

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

直到我将第一个'plt.draw()'更改为'ax.figure.canvas.draw()'时,这并不适用于我。我不知道为什么,但在此更改之前,图例大小没有得到更新。 – 2016-03-09 23:57:33

+0

如果你想在GUI窗口中使用它,你需要将'fig.set_size_inches(widthTot,heightTot)'改成'fig.set_size_inches(widthTot,heightTot,forward = True)'。 – 2016-03-09 23:58:40