【问题标题】:Matplotlib tight_layout() doesn't take into account figure suptitleMatplotlib tight_layout() 不考虑图形字幕
【发布时间】:2012-01-05 02:46:30
【问题描述】:

如果我在我的 matplotlib 图中添加了一个副标题,它会被副图的标题覆盖。有人知道如何轻松解决这个问题吗?我尝试了tight_layout() 函数,但它只会让事情变得更糟。

例子:

import numpy as np
import matplotlib.pyplot as plt

f = np.random.random(100)
g = np.random.random(100)
fig = plt.figure()
fig.suptitle('Long Suptitle', fontsize=24)
plt.subplot(121)
plt.plot(f)
plt.title('Very Long Title 1', fontsize=20)
plt.subplot(122)
plt.plot(g)
plt.title('Very Long Title 2', fontsize=20)
plt.tight_layout()
plt.show()

【问题讨论】:

    标签: python matplotlib


    【解决方案1】:

    您可以使用plt.subplots_adjust(top=0.85) 手动调整间距:

    import numpy as np
    import matplotlib.pyplot as plt
    
    f = np.random.random(100)
    g = np.random.random(100)
    fig = plt.figure()
    fig.suptitle('Long Suptitle', fontsize=24)
    plt.subplot(121)
    plt.plot(f)
    plt.title('Very Long Title 1', fontsize=20)
    plt.subplot(122)
    plt.plot(g)
    plt.title('Very Long Title 2', fontsize=20)
    plt.subplots_adjust(top=0.85)
    plt.show()
    

    【讨论】:

    • 不敢相信2017年老爷这还是matplotlib的bug。
    • 请注意,subplots_adjust 必须在对tight_layout 的任何调用之后 调用,否则调用subplots_adjust 将不起作用。
    • @wordsforthewise 现在就实现 2018 年
    • @vlsd 你好,从 2019 年开始
    • 2020 在这里,这仍然是一个问题
    【解决方案2】:

    您可以在代码中轻松更改的一件事是您用于标题的fontsize。但是,我假设您不只是想这样做!

    使用fig.subplots_adjust(top=0.85)的一些替代方法:

    通常tight_layout() 可以很好地将所有内容放置在合适的位置,以免它们重叠。 tight_layout() 在这种情况下没有帮助的原因是因为 tight_layout() 没有考虑 fig.suptitle()。 GitHub 上有一个关于此的未解决问题:https://github.com/matplotlib/matplotlib/issues/829 [关闭2014 年,由于需要一个完整的几何管理器 - 转移到 https://github.com/matplotlib/matplotlib/issues/1109 ]。

    如果您阅读了该主题,您的问题涉及GridSpec 的解决方案。关键是在调用tight_layout 时在图的顶部留出一些空间,使用rect kwarg。对于您的问题,代码变为:

    使用 GridSpec

    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib.gridspec as gridspec
    
    f = np.random.random(100)
    g = np.random.random(100)
    
    fig = plt.figure(1)
    gs1 = gridspec.GridSpec(1, 2)
    ax_list = [fig.add_subplot(ss) for ss in gs1]
    
    ax_list[0].plot(f)
    ax_list[0].set_title('Very Long Title 1', fontsize=20)
    
    ax_list[1].plot(g)
    ax_list[1].set_title('Very Long Title 2', fontsize=20)
    
    fig.suptitle('Long Suptitle', fontsize=24)    
    
    gs1.tight_layout(fig, rect=[0, 0.03, 1, 0.95])  
    
    plt.show()
    

    结果:

    也许GridSpec 对您来说有点矫枉过正,或者您的真正问题将涉及更大画布上的更多子图,或其他复杂情况。一个简单的技巧是只使用annotate() 并将坐标锁定到'figure fraction' 以模仿suptitle。但是,一旦查看输出,您可能需要进行一些更精细的调整。请注意,第二个解决方案使用tight_layout()

    更简单的解决方案(尽管可能需要微调)

    fig = plt.figure(2)
    
    ax1 = plt.subplot(121)
    ax1.plot(f)
    ax1.set_title('Very Long Title 1', fontsize=20)
    
    ax2 = plt.subplot(122)
    ax2.plot(g)
    ax2.set_title('Very Long Title 2', fontsize=20)
    
    # fig.suptitle('Long Suptitle', fontsize=24)
    # Instead, do a hack by annotating the first axes with the desired 
    # string and set the positioning to 'figure fraction'.
    fig.get_axes()[0].annotate('Long Suptitle', (0.5, 0.95), 
                                xycoords='figure fraction', ha='center', 
                                fontsize=24
                                )
    plt.show()
    

    结果:

    [使用Python2.7.3(64位)和matplotlib1.2.0]

    【讨论】:

    • 谢谢,如果我使用您建议的第一个解决方案(使用GridSpec),我如何创建共享轴的子图?我通常在创建子图时使用plt.subplots(N,1, sharex=<>, sharey=<>),但您发布的代码使用add_subplot代替
    • 铸造 fig.tight_layout(rect=[0, 0.03, 1, 0.95]) 也可以。
    • 第二种解决方案是唯一对我有用的解决方案。谢谢!
    【解决方案3】:

    我一直在使用 matplotlib 修剪方法,所以我现在刚刚创建了一个函数来通过 bash 调用 ImageMagickmogrify command 来执行此操作,它运行良好并且可以消除所有额外的空白人物的边缘。这要求您使用 UNIX/Linux,使用 bash shell,并安装了 ImageMagick

    在您的savefig() 调用之后调用它。

    def autocrop_img(filename):
        '''Call ImageMagick mogrify from bash to autocrop image'''
        import subprocess
        import os
    
        cwd, img_name = os.path.split(filename)
    
        bashcmd = 'mogrify -trim %s' % img_name
        process = subprocess.Popen(bashcmd.split(), stdout=subprocess.PIPE, cwd=cwd)
    

    【讨论】:

      【解决方案4】:

      另一种简单易用的解决方案是使用 suptitle 调用中的 y 参数调整图中字幕文本的坐标(参见@​​987654321@):

      import numpy as np
      import matplotlib.pyplot as plt
      
      f = np.random.random(100)
      g = np.random.random(100)
      fig = plt.figure()
      fig.suptitle('Long Suptitle', y=1.05, fontsize=24)
      plt.subplot(121)
      plt.plot(f)
      plt.title('Very Long Title 1', fontsize=20)
      plt.subplot(122)
      plt.plot(g)
      plt.title('Very Long Title 2', fontsize=20)
      plt.show()
      

      【讨论】:

      • 这是在笔记本中的好方法,但 plt.savefig() 命令不服从。此解决方案仍然会在 savefig 命令中截断标题。
      【解决方案5】:

      您可以在非常 tight_layout 调用中调整子图几何,如下所示:

      fig.tight_layout(rect=[0, 0.03, 1, 0.95])
      

      正如文档 (https://matplotlib.org/users/tight_layout_guide.html) 中所述:

      tight_layout() 只考虑刻度标签、轴标签和标题。因此,其他艺术家可能会被剪辑,也可能重叠。

      【讨论】:

      • 它就像魅力一样,但是在指定边界框时会发生什么变化?我阅读了文档,但对我来说并不清楚。如果您能向我解释一下,那就太好了!谢谢。
      • 你能解释一下rect中每个数字的含义吗?我没有在文档中看到它们。
      • @steven 请参阅tight_layout 文档:[left, bottom, right, top] in normalized (0, 1) figure coordinates
      • 您可以通过降低最右边的值来进一步增加字幕和轴之间的空间:tight_layout(rect=[0, 0.03, 1, 0.9]) 而不是答案中的0.95
      • 在 jupyter 中不起作用。尝试了不同的数字值,没有效果
      【解决方案6】:

      在使用tight_layout 处理非常大的图网格(超过 200 个子图)并在 jupyter 笔记本中进行渲染时,我遇到了类似的问题。我制定了一个快速解决方案,始终将您的suptitle 放置在您的顶部子图上方一定距离处:

      import matplotlib.pyplot as plt
      
      n_rows = 50
      n_col = 4
      fig, axs = plt.subplots(n_rows, n_cols)
      
      #make plots ...
      
      # define y position of suptitle to be ~20% of a row above the top row
      y_title_pos = axs[0][0].get_position().get_points()[1][1]+(1/n_rows)*0.2
      fig.suptitle('My Sup Title', y=y_title_pos)
      

      对于可变大小的子图,您仍然可以使用此方法获取最顶部子图的顶部,然后手动定义附加量以添加到字幕。

      【讨论】:

        【解决方案7】:

        正如其他人所提到的,默认情况下紧凑的布局不考虑字幕。但是,我发现可以使用 bbox_extra_artists 参数将字幕作为边界框传递,应该考虑到这一点:

        st = fig.suptitle("My Super Title")
        plt.savefig("figure.png", bbox_extra_artists=[st], bbox_inches='tight')
        

        这会强制紧凑的布局计算将suptitle 考虑在内,它看起来与您预期的一样。

        【讨论】:

        • 这是唯一对我有用的选项。我使用 Jupyter 笔记本制作数字并保存它们。我在 Linux 机器上使用 python 3.6 工作。
        • 唉,在我的例子中,这导致标题和第一个(顶部)子图之间的空间太大。
        • @BrandonRhodes 我无法确认这是否适合您,但就我而言,在plt.subplots 调用中使用constrained_layout=True 可选参数除了 bbox_extra_artists in plt.savefig 消除了多余的空间。
        【解决方案8】:

        紧凑的布局不适用于字幕,但constrained_layout 可以。看到这个问题Improve subplot size/spacing with many subplots in matplotlib

        我发现立即添加子图看起来更好,即

        fig, axs = plt.subplots(rows, cols, constrained_layout=True)
        
        # then iterating over the axes to fill in the plots
        

        但也可以在图形创建时添加:

        fig = plt.figure(constrained_layout=True)
        
        ax1 = fig.add_subplot(cols, rows, 1)
        # etc
        

        注意:为了使我的子图更紧密,我也使用了

        fig.subplots_adjust(wspace=0.05)
        

        并且 constrained_layout 不适用于此 :(

        【讨论】:

          【解决方案9】:

          唯一对我有用的是修改对 suptitle 的调用:

          fig.suptitle("title", y=.995)
          

          【讨论】:

            【解决方案10】:

            This website 有一个简单的解决方案,其中有一个对我有用的示例。实际为标题留出空间的代码行如下:

            plt.tight_layout(rect=[0, 0, 1, 0.95]) 
            

            这是一张证明它对我有用的图片:

            【讨论】:

              【解决方案11】:

              截至v3.3 tight_layout 现在支持suptitle

              import matplotlib.pyplot as plt
              
              fig, axs = plt.subplots(1, 3)
              for i, ax in enumerate(axs):
                  ax.plot([1, 2, 3])
                  ax.set_title(f'Axes {i}')
              
              fig.suptitle('suptitle')
              fig.tight_layout()
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2019-04-07
                • 2010-11-26
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多