Matplotlib 创建具有精确大小且无填充的图形(以及轴外的图例)

Matplotlib 创建具有精确大小且无填充的图形(以及轴外的图例),matplotlib,Matplotlib,我正试图为一篇科学文章制作一些数字,所以我希望我的数字有一个特定的大小。我还看到Matplotlib在默认情况下在图形的边框上添加了很多填充,这是我不需要的(因为图形将在白色背景上) 要设置特定的图形大小,我只需使用plt.figure(figsize=[w,h]),然后添加参数tight_layout={'pad':0},以删除填充。这非常有效,甚至在我添加标题、y/x标签等时也有效。示例: fig = plt.figure( figsize = [3,2], tight_la

我正试图为一篇科学文章制作一些数字,所以我希望我的数字有一个特定的大小。我还看到Matplotlib在默认情况下在图形的边框上添加了很多填充,这是我不需要的(因为图形将在白色背景上)

要设置特定的图形大小,我只需使用
plt.figure(figsize=[w,h])
,然后添加参数
tight_layout={'pad':0}
,以删除填充。这非常有效,甚至在我添加标题、y/x标签等时也有效。示例:

fig = plt.figure(
    figsize = [3,2],
    tight_layout = {'pad': 0}
)
ax = fig.add_subplot(111)
plt.title('title')
ax.set_ylabel('y label')
ax.set_xlabel('x label')
plt.savefig('figure01.pdf')
plt.close('all')
fig = plt.figure(
    figsize = [3,2],
    tight_layout = {'pad': 0}
)
ax = fig.add_subplot(111)
plt.title('title')
ax.set_ylabel('y label')
ax.set_xlabel('x label')
t = ax.text(0.7, 1.1, 'my text here', bbox = dict(boxstyle = 'round'))
plt.savefig('figure02.pdf')
这将创建一个精确大小为3x2(英寸)的pdf文件

我遇到的问题是,例如,当我在轴外添加文本框(通常是图例框)时,Matplotlib不会像添加标题/轴标签时那样为文本框腾出空间。通常,文本框会被切断,或者根本不显示在保存的图形中。例如:

fig = plt.figure(
    figsize = [3,2],
    tight_layout = {'pad': 0}
)
ax = fig.add_subplot(111)
plt.title('title')
ax.set_ylabel('y label')
ax.set_xlabel('x label')
plt.savefig('figure01.pdf')
plt.close('all')
fig = plt.figure(
    figsize = [3,2],
    tight_layout = {'pad': 0}
)
ax = fig.add_subplot(111)
plt.title('title')
ax.set_ylabel('y label')
ax.set_xlabel('x label')
t = ax.text(0.7, 1.1, 'my text here', bbox = dict(boxstyle = 'round'))
plt.savefig('figure02.pdf')

我在SO的其他地方找到的一个解决方案是在savefig命令中添加参数
bbox\u inches='tight'
。文本框现在包含在我想要的内容中,但pdf现在的大小不正确。Matplotlib似乎只是使图形变大,而不是像添加标题和x/y标签时那样减小轴的大小

例如:

plt.close('all')
fig = plt.figure(
    figsize = [3,2],
    tight_layout = {'pad': 0}
)
ax = fig.add_subplot(111)
plt.title('title')
ax.set_ylabel('y label')
ax.set_xlabel('x label')
t = ax.text(0.7, 1.1, 'my text here', bbox = dict(boxstyle = 'round'))
plt.savefig('figure03.pdf', bbox_inches = 'tight')

(该图为3.307x2.248)


是否有任何解决方案可以覆盖大多数轴外有图例的情况?

因此要求如下:

  • 具有固定的、预定义的图形大小
  • 在轴外部添加文本标签或图例
  • 轴和文本不能重叠
  • 轴以及标题和轴标签紧靠在图形边框上
  • 因此,使用
    pad=0的
    tight_布局
    ,解决了1。四,。但与第2条相矛盾

    可以考虑将
    pad
    设置为更大的值。这将解决2。然而,因为它在所有方向上都是对称的,所以它与4相矛盾

    使用
    bbox\u inches='tight'
    更改体形大小。反驳1

    所以我认为这个问题没有通用的解决方案

    我能想到的是:它在图形坐标中设置文本,然后在水平或垂直方向上调整轴的大小,以便轴和文本之间没有重叠

    import matplotlib.pyplot as plt 
    import matplotlib.transforms
    
    fig = plt.figure(figsize = [3,2]) 
    ax = fig.add_subplot(111)
    plt.title('title')
    ax.set_ylabel('y label')
    ax.set_xlabel('x label')
    
    def text_legend(ax, x0, y0, text, direction = "v", padpoints = 3, margin=1.,**kwargs):
        ha = kwargs.pop("ha", "right")
        va = kwargs.pop("va", "top")
        t = ax.figure.text(x0, y0, text, ha=ha, va=va, **kwargs) 
        otrans = ax.figure.transFigure
    
        plt.tight_layout(pad=0)
        ax.figure.canvas.draw()
        plt.tight_layout(pad=0)
        offs =  t._bbox_patch.get_boxstyle().pad * t.get_size() + margin # adding 1pt
        trans = otrans + \
                matplotlib.transforms.ScaledTranslation(-offs/72.,-offs/72.,fig.dpi_scale_trans)
        t.set_transform(trans)
        ax.figure.canvas.draw()
    
        ppar = [0,-padpoints/72.] if direction == "v" else [-padpoints/72.,0] 
        trans2 = matplotlib.transforms.ScaledTranslation(ppar[0],ppar[1],fig.dpi_scale_trans) + \
                 ax.figure.transFigure.inverted() 
        tbox = trans2.transform(t._bbox_patch.get_window_extent())
        bbox = ax.get_position()
        if direction=="v":
            ax.set_position([bbox.x0, bbox.y0,bbox.width, tbox[0][1]-bbox.y0]) 
        else:
            ax.set_position([bbox.x0, bbox.y0,tbox[0][0]-bbox.x0, bbox.height]) 
    
    # case 1: place text label at top right corner of figure (1,1). Adjust axes height.
    #text_legend(ax, 1,1, 'my text here', bbox = dict(boxstyle = 'round'), )
    
    # case 2: place text left of axes, (1, y), direction=="v"
    text_legend(ax, 1., 0.8, 'my text here', margin=2., direction="h", bbox = dict(boxstyle = 'round') )
    
    plt.savefig(__file__+'.pdf')
    plt.show()
    
    案例1(左)和案例2(右):


    对图例执行相同的操作稍微容易一些,因为我们可以直接使用
    bbox\u to\u anchor
    参数,而不需要控制图例周围的花式方框

    import matplotlib.pyplot as plt 
    import matplotlib.transforms
    
    fig = plt.figure(figsize = [3.5,2]) 
    ax = fig.add_subplot(111)
    ax.set_title('title')
    ax.set_ylabel('y label')
    ax.set_xlabel('x label')
    ax.plot([1,2,3], marker="o", label="quantity 1")
    ax.plot([2,1.7,1.2], marker="s", label="quantity 2")
    
    def legend(ax, x0=1,y0=1, direction = "v", padpoints = 3,**kwargs):
        otrans = ax.figure.transFigure
        t = ax.legend(bbox_to_anchor=(x0,y0), loc=1, bbox_transform=otrans,**kwargs)
        plt.tight_layout(pad=0)
        ax.figure.canvas.draw()
        plt.tight_layout(pad=0)
        ppar = [0,-padpoints/72.] if direction == "v" else [-padpoints/72.,0] 
        trans2=matplotlib.transforms.ScaledTranslation(ppar[0],ppar[1],fig.dpi_scale_trans)+\
                 ax.figure.transFigure.inverted() 
        tbox = t.get_window_extent().transformed(trans2 )
        bbox = ax.get_position()
        if direction=="v":
            ax.set_position([bbox.x0, bbox.y0,bbox.width, tbox.y0-bbox.y0]) 
        else:
            ax.set_position([bbox.x0, bbox.y0,tbox.x0-bbox.x0, bbox.height]) 
    
    # case 1: place text label at top right corner of figure (1,1). Adjust axes height.
    #legend(ax, borderaxespad=0)
    # case 2: place text left of axes, (1, y), direction=="h"
    legend(ax,y0=0.8, direction="h", borderaxespad=0.2)
    
    plt.savefig(__file__+'.pdf')
    plt.show()
    



    为什么
    72
    72
    是每英寸点数(ppi)。这是一个固定的印刷单位,例如字体大小总是以点(如12pt)表示。由于matplotlib以相对于fontsize(即点)的单位定义文本框的填充,因此我们需要使用
    72
    转换回英寸(然后显示坐标)。此处未触及默认的每英寸点数(dpi),但在图dpi\U scale\U trans中进行了说明。如果要更改dpi,则需要确保在创建图形以及保存图形时设置图形dpi(在调用
    plt.figure()
    以及
    plt.savefig()
    )时使用
    dpi=..

    matplotlib==3.1.3
    起,您可以使用
    constrated\u layout=True
    来实现所需的结果。这目前是实验性的,但请参阅,以获取非常有用的指南(特别是图例)。请注意,图例将从情节中窃取空间,但这是不可避免的。我发现,只要图例相对于绘图的大小不占用太多空间,那么图形就可以在不裁剪任何内容的情况下保存

    import matplotlib.pyplot as plt
    
    fig, ax = plt.subplots(figsize=(3, 2), constrained_layout=True)
    ax.set_title('title')
    ax.set_ylabel('y label')
    ax.set_xlabel('x label')
    ax.plot([0,1], [0,1], label='my text here')
    ax.legend(loc='center left', bbox_to_anchor=(1.1, 0.5))
    fig.savefig('figure03.pdf')
    

    谢谢,这太棒了!您介意解释一下在某些地方出现的硬编码
    72
    是什么吗?我猜这是默认的dpi,但我不确定。我在答案中添加了一些解释。再次感谢。有没有什么简单的方法可以让这部作品成为传奇?例如,在轴外放置图例时,我在答案中添加了图例的大小写。