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

标签 matplotlib

我正在尝试为一篇科学文章制作一些图形,所以我希望我的图形具有特定的尺寸。我还看到默认情况下,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')

这将创建一个精确大小为 3x2(英寸)的 pdf 文件。

figure01.png

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

figure02.png

我在其他地方找到的解决方案是添加参数 bbox_inches = 'tight'到 savefig 命令。现在像我想要的那样包含文本框,但 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')

figure03.png

(这个数字是3.307x2.248)

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

最佳答案

所以要求是:

  • 具有固定的、预定义的图形大小
  • 在轴外添加文本标签或图例
  • 轴和文本不能重叠
  • 轴与标题和轴标签一起紧靠图形边框。

  • 所以tight_layoutpad = 0 , 解决 1. 和 4. 但与 2. 矛盾。

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

    使用 bbox_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(右):
    enter image description here
    enter image description here

    用图例做同样的事情稍微容易一些,因为我们可以直接使用 bbox_to_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()
    

    enter image description here
    enter image description here

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

    关于matplotlib - 创建具有精确大小且无填充的图形(以及轴外的图例),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42994338/

    相关文章:

    python - Matplotlib 的 Gnuplot 命令

    python - 如何在图形标签中包含平方符号

    python - pyplot : really slow creating heatmaps

    matplotlib - matplotlib 条形图中奇怪的空白,具有 > 140 个值

    python - 更改 DataFrame 中的子图颜色?

    python - Matplotlib 自定义颜色图不尊重着色

    python - 在 Python 中使用 matplotlib 在 4x4 多重图上显示 X 和 Y 刻度

    python - 更改箭袋的轴刻度 - Python

    python - 如何从绘图中完全删除 x 轴(和 y 轴)并使用 Python 或 R 编程在某些点绘制切线?

    python - MatPlotlib Seaborn 多图格式化