Matplotlib保存带有图例的图像并将图例放在图像外部

54

阅读以下文章后,我成功地将图例放在了绘图之外。

代码:

import matplotlib.pyplot as pyplot

x = [0, 1, 2, 3, 4]
y = [xx*xx for xx in x]

fig = pyplot.figure()
ax  = fig.add_subplot(111)

box = ax.get_position()
ax.set_position([box.x0, box.y0, box.width*0.8, box.height])

ax.plot(x, y)
leg = ax.legend(['abc'], loc = 'center left', bbox_to_anchor = (1.0, 0.5))
#pyplot.show()

fig.savefig('aaa.png', bbox_inches='tight')

pyplot.show() 显示具有位于外部的图例的正确图形。但是当我使用 fig.savefig() 将其保存为文件时,图例被截断。

一些谷歌搜索向我展示了解决方法,例如在 savefig() 中添加 bbox_extra_artists=[leg.legendPatch]bbox_extra_artists=[leg],但两者都没有起作用。

正确的方法是什么?Matplotlib 版本为 0.99.3。

谢谢。


3
我看到这是一个旧的帖子,但它在谷歌中排名第一。通过包含演员来保存图形,有更好的解决方案:https://dev59.com/nmkw5IYBdhLWcg3wJXMh - Alleo
1
另一个答案:https://dev59.com/ylcP5IYBdhLWcg3wRYBh#44649558 - dparkar
5
像@MPa在问题@dparker指向的链接(https://dev59.com/ylcP5IYBdhLWcg3wRYBh#44649558)中建议的那样使用`fig.savefig('aaa.png', bbox_inches='tight', bbox_inches="tight")`,这在我现在刚刚尝试时有效。 - a11
1
plt.savefig(path_output, bbox_inches='tight') 中添加 bbox_inches='tight' 对我很有效。在保存之前,我设置了 fig.legend(lines, labels, bbox_to_anchor=(0, 1, 1, 0), loc="lower left", mode="expand", ncol=4); plt.tight_layout();。因此图例位于绘图的顶部。 - FisNaN
4个回答

28
问题在于当您动态绘制时,matplotlib 自动确定边框以适应所有对象。但是,保存文件时并没有自动完成此操作,因此您需要指定图形的大小,然后指定轴对象的边界框。以下是如何更正您的代码:
import matplotlib.pyplot as pyplot

x = [0, 1, 2, 3, 4]
y = [xx*xx for xx in x]

fig = pyplot.figure(figsize=(3,3))
ax  = fig.add_subplot(111)

#box = ax.get_position()
#ax.set_position([0.3, 0.4, box.width*0.3, box.height])
# you can set the position manually, with setting left,buttom, witdh, hight of the axis
# object
ax.set_position([0.1,0.1,0.5,0.8])
ax.plot(x, y)
leg = ax.legend(['abc'], loc = 'center left', bbox_to_anchor = (1.0, 0.5))

fig.savefig('aaa.png')

谢谢,它起作用了。我希望savefig()的未来版本能够支持类似于pyplot.show()的边框计算。 - niboshi
嗯,你说得对。我会考虑升级到更近期的版本。 - niboshi
67
我知道Matplotlib喜欢宣称一切都在用户控制之下,但是图例的整个设定有点过了。如果我把图例放在外面,那显然是希望它仍然可见的。窗口应该自动缩放以适应图例大小,而不是创建一个巨大的缩放麻烦。至少应该有一个默认的True选项来控制这种自适应缩放行为。强制用户通过无数次重新渲染来尝试调整比例大小并保持控制反而达不到目的。 - Elliot
有一个解决方法(在顶部的评论和我的回答中提供),适用于大多数情况。需要注意的是,matplotlib开发人员已经意识到这个问题,但显然很难修复。 - tim-oh

20

虽然这种方法可以用于图例,但是当存在多个子图并且我们想要一个总体图例时,似乎在figlegend上效果不佳。当保存图形时,figlegend仍然会被裁剪。我在下面贴出了我的临时解决方案,以防有人遇到这种情况。

import matplotlib.pyplot as plt

para = {
    ## this parameter will indicate the position of
    ## subplot within figure, but will not be shown
    ## if using bbox_inches='tight' when saving
    'figure.subplot.top': 0.5
}
#plt.rcParams.update(para)

fig = plt.figure()

ax=fig.add_subplot(221)
## only needed when what to manually control
## subplot ration
#ax.set_position([0.1,0.6,0.5, 0.4])
ax.plot([1,1,1])


ax=fig.add_subplot(222)
#ax.set_position([0.7,0.6,0.5, 0.4])
ax.plot([2,2,2])

ax=fig.add_subplot(223)
#ax.set_position([0.1,0.1,0.5, 0.4])
ax.plot([3,3,3])


ax=fig.add_subplot(224)
#ax.set_position([0.7,0.1,0.5, 0.4])
p1, = ax.plot([4,4,4])
p2, = ax.plot([2,3,2])

## figlegend does not work fine with tight bbox
## the legend always get cropped by this option
## even add bbox extra will not help
## had to use legend, and manually adjust it to
## arbitary position such as (0.3, 2.5)

## http://matplotlib.org/users/tight_layout_guide.html
## according to this link, tight layout is only
## an experimental feature, might not support figlegend

#lgd = plt.figlend(
lgd = plt.legend(
    [p1,p2],
    ['a', 'b'],
    ## by default, legend anchor to axis, but can
    ## also be anchored to arbitary position
    ## positions within [1,1] would be within the figure
    ## all numbers are ratio by default

    bbox_to_anchor=(-0.1, 2.5),

    ## loc indicates the position within the figure
    ## it is defined consistent to the same Matlab function 
    loc='center',

    ncol=2
    #mode="expand",
    #borderaxespad=0.
    )



#plt.show()

plt.savefig('temp.png', bbox_inches='tight')#, bbox_extra_artist=[lgd])

谢谢你。你是否已经提交了一个关于这个(bbox_extra_artists?)的错误报告?我和你一样,遇到了一个多轴图和超出轴范围的图例的问题。我无法将你的解决方法应用到我的情况中。 - CPBL
我还没有提交任何错误报告。我不确定这是一个错误还是它就是被设计成这样的。 - Ning

10

对于大多数情况,一种简单的解决方法是修改plt.savefig()命令:

plt.savefig('your_figure.png', bbox_inches='tight')

这个解决方案已经在问题下面的评论中提出,但我忽略了那些评论,在matplotlib在GitHub上找到了它。为了其他没有阅读顶部评论的人保留这个答案。


3
对我很有效,而且比上面的方法简单多了! - dhokas
这也适用于使用matplotlib.backends.backend_pdf的PdfPages保存为pdf文件时。 - undefined

0

如果其他方法都失败了,我会使用Inkscape的边界框功能来处理我所谓的Matplotlib输出中的持久性错误。如果您正在运行GNU / Linux,则只需将Matplotlib提供的任何内容保存为pdf,然后发送到以下位置

def tightBoundingBoxInkscape(pdffile,use_xvfb=True):
    """Makes POSIX-specific OS calls. Preferably, have xvfb installed, to avoid any GUI popping up in the background. If it fails anyway, could always resort to use_xvfb=False, which will allow some GUIs to show as they carry out the task 
      pdffile: the path for a PDF file, without its extension
    """
    usexvfb='xvfb-run '*use_xvfb
    import os
    assert not pdffile.endswith('.pdf')
    os.system("""
       inkscape -f %(FN)s.pdf -l %(FN)s_tmp.svg
       inkscape -f %(FN)s_tmp.svg --verb=FitCanvasToDrawing \
                                   --verb=FileSave \
                                   --verb=FileQuit
      inkscape -f %(FN)s_tmp.svg -A %(FN)s-tightbb.pdf
"""%{'FN':pdffile}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接