约束布局指南#

如何使用受约束的布局来干净地适应图形中的图。

constrained_layout自动调整子图和装饰,如图例和颜色条,以便它们适合图形窗口,同时仍尽可能保留用户请求的逻辑布局。

constrained_layout类似于 tight_layout,但使用约束求解器来确定允许它们适合的轴的大小。

在将任何轴添加到图形之前,通常需要激活constrained_layout 。这样做的两种方法是

这些将在以下各节中详细描述。

简单的例子#

在 Matplotlib 中,轴(包括子图)的位置在标准化图形坐标中指定。您的轴标签或标题(有时甚至是刻度标签)可能会超出图形区域,从而被剪裁。

import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import matplotlib.gridspec as gridspec
import numpy as np

plt.rcParams['savefig.facecolor'] = "0.8"
plt.rcParams['figure.figsize'] = 4.5, 4.
plt.rcParams['figure.max_open_warning'] = 50


def example_plot(ax, fontsize=12, hide_labels=False):
    ax.plot([1, 2])

    ax.locator_params(nbins=3)
    if hide_labels:
        ax.set_xticklabels([])
        ax.set_yticklabels([])
    else:
        ax.set_xlabel('x-label', fontsize=fontsize)
        ax.set_ylabel('y-label', fontsize=fontsize)
        ax.set_title('Title', fontsize=fontsize)

fig, ax = plt.subplots(layout=None)
example_plot(ax, fontsize=24)
标题

为了防止这种情况,需要调整轴的位置。对于子图,这可以通过使用 调整子图参数来手动完成Figure.subplots_adjust。但是,使用 # 关键字参数指定您的图形layout="constrained"将自动调整 #。

fig, ax = plt.subplots(layout="constrained")
example_plot(ax, fontsize=24)
标题

当您有多个子图时,您通常会看到不同轴的标签相互重叠。

fig, axs = plt.subplots(2, 2, layout=None)
for ax in axs.flat:
    example_plot(ax)
标题,标题,标题,标题

layout="constrained"在调用中指定plt.subplots 会导致布局受到适当的约束。

fig, axs = plt.subplots(2, 2, layout="constrained")
for ax in axs.flat:
    example_plot(ax)
标题,标题,标题,标题

颜色条#

如果您使用 来创建颜色条Figure.colorbar,则需要为其腾出空间。constrained_layout自动执行此操作。请注意,如果您指定use_gridspec=True它将被忽略,因为此选项用于改进布局 tight_layout

笔记

对于pcolormesh关键字参数 ( pc_kwargs),我们使用字典。下面我们将一个颜色条分配给多个轴,每个轴都包含一个ScalarMappable; 指定规范和颜色图可确保颜色条对于所有轴都是准确的。

arr = np.arange(100).reshape((10, 10))
norm = mcolors.Normalize(vmin=0., vmax=100.)
# see note above: this makes all pcolormesh calls consistent:
pc_kwargs = {'rasterized': True, 'cmap': 'viridis', 'norm': norm}
fig, ax = plt.subplots(figsize=(4, 4), layout="constrained")
im = ax.pcolormesh(arr, **pc_kwargs)
fig.colorbar(im, ax=ax, shrink=0.6)
受约束的布局指南
<matplotlib.colorbar.Colorbar object at 0x7f2cfafb53c0>

ax如果您为 的参数指定轴列表(或其他可迭代容器) colorbar,则 constrained_layout 将从指定轴中占用空间。

fig, axs = plt.subplots(2, 2, figsize=(4, 4), layout="constrained")
for ax in axs.flat:
    im = ax.pcolormesh(arr, **pc_kwargs)
fig.colorbar(im, ax=axs, shrink=0.6)
受约束的布局指南
<matplotlib.colorbar.Colorbar object at 0x7f2cfb6eed70>

如果您从坐标轴网格中指定坐标轴列表,颜色条将适当地占用空间并留下间隙,但所有子图仍将具有相同的大小。

fig, axs = plt.subplots(3, 3, figsize=(4, 4), layout="constrained")
for ax in axs.flat:
    im = ax.pcolormesh(arr, **pc_kwargs)
fig.colorbar(im, ax=axs[1:, ][:, 1], shrink=0.8)
fig.colorbar(im, ax=axs[:, -1], shrink=0.6)
受约束的布局指南
<matplotlib.colorbar.Colorbar object at 0x7f2cdd1a3340>

副标题#

constrained_layout也可以腾出空间suptitle

fig, axs = plt.subplots(2, 2, figsize=(4, 4), layout="constrained")
for ax in axs.flat:
    im = ax.pcolormesh(arr, **pc_kwargs)
fig.colorbar(im, ax=axs, shrink=0.6)
fig.suptitle('Big Suptitle')
大字幕
Text(0.5, 0.9895825, 'Big Suptitle')

传说#

图例可以放置在其父轴之外。约束布局旨在为Axes.legend(). 但是,受约束的布局不处理通过 Figure.legend()(还)创建的图例。

fig, ax = plt.subplots(layout="constrained")
ax.plot(np.arange(10), label='This is a plot')
ax.legend(loc='center left', bbox_to_anchor=(0.8, 0.5))
受约束的布局指南
<matplotlib.legend.Legend object at 0x7f2cfb266d70>

但是,这将从子图布局中窃取空间:

fig, axs = plt.subplots(1, 2, figsize=(4, 2), layout="constrained")
axs[0].plot(np.arange(10))
axs[1].plot(np.arange(10), label='This is a plot')
axs[1].legend(loc='center left', bbox_to_anchor=(0.8, 0.5))
受约束的布局指南
<matplotlib.legend.Legend object at 0x7f2cf99852a0>

为了不让图例或其他艺术家从子情节布局中窃取空间,我们可以leg.set_in_layout(False). 当然,这可能意味着图例最终会被裁剪,但如果随后使用. 但是请注意,必须再次切换图例的状态才能使保存的文件正常工作,如果我们希望 constrained_layout 在打印之前调整轴的大小,我们必须手动触发绘制。fig.savefig('outname.png', bbox_inches='tight')get_in_layout

fig, axs = plt.subplots(1, 2, figsize=(4, 2), layout="constrained")

axs[0].plot(np.arange(10))
axs[1].plot(np.arange(10), label='This is a plot')
leg = axs[1].legend(loc='center left', bbox_to_anchor=(0.8, 0.5))
leg.set_in_layout(False)
# trigger a draw so that constrained_layout is executed once
# before we turn it off when printing....
fig.canvas.draw()
# we want the legend included in the bbox_inches='tight' calcs.
leg.set_in_layout(True)
# we don't want the layout to change at this point.
fig.set_layout_engine(None)
try:
    fig.savefig('../../doc/_static/constrained_layout_1b.png',
                bbox_inches='tight', dpi=100)
except FileNotFoundError:
    # this allows the script to keep going if run interactively and
    # the directory above doesn't exist
    pass
受约束的布局指南

保存的文件如下所示:

../../_images/constrained_layout_1b.png

解决这种尴尬的更好方法是简单地使用提供的 legend 方法Figure.legend

fig, axs = plt.subplots(1, 2, figsize=(4, 2), layout="constrained")
axs[0].plot(np.arange(10))
lines = axs[1].plot(np.arange(10), label='This is a plot')
labels = [l.get_label() for l in lines]
leg = fig.legend(lines, labels, loc='center left',
                 bbox_to_anchor=(0.8, 0.5), bbox_transform=axs[1].transAxes)
try:
    fig.savefig('../../doc/_static/constrained_layout_2b.png',
                bbox_inches='tight', dpi=100)
except FileNotFoundError:
    # this allows the script to keep going if run interactively and
    # the directory above doesn't exist
    pass
受约束的布局指南

保存的文件如下所示:

../../_images/constrained_layout_2b.png

填充和间距#

轴之间的填充在水平方向由w_padwspace控制,垂直方向由h_padhspace控制。这些可以通过编辑setw/h_pad是以英寸为单位的轴周围的最小空间:

fig, axs = plt.subplots(2, 2, layout="constrained")
for ax in axs.flat:
    example_plot(ax, hide_labels=True)
fig.get_layout_engine().set(w_pad=4 / 72, h_pad=4 / 72, hspace=0,
                            wspace=0)
受约束的布局指南

子图之间的间距由wspacehspace进一步设置。这些被指定为子图组整体大小的一部分。如果这些值小于w_padh_pad,则使用固定焊盘代替。请注意下面的边缘空间如何与上面的空间没有变化,但子图之间的空间会发生变化。

fig, axs = plt.subplots(2, 2, layout="constrained")
for ax in axs.flat:
    example_plot(ax, hide_labels=True)
fig.get_layout_engine().set(w_pad=4 / 72, h_pad=4 / 72, hspace=0.2,
                            wspace=0.2)
受约束的布局指南

如果多于两列,则它们之间共享wspace,因此这里将 wspace 一分为二,每列之间的wspace为 0.1:

fig, axs = plt.subplots(2, 3, layout="constrained")
for ax in axs.flat:
    example_plot(ax, hide_labels=True)
fig.get_layout_engine().set(w_pad=4 / 72, h_pad=4 / 72, hspace=0.2,
                            wspace=0.2)
受约束的布局指南

GridSpecs 也有可选的hspacewspace关键字参数,它们将被用来代替由constrained_layout:

fig, axs = plt.subplots(2, 2, layout="constrained",
                        gridspec_kw={'wspace': 0.3, 'hspace': 0.2})
for ax in axs.flat:
    example_plot(ax, hide_labels=True)
# this has no effect because the space set in the gridspec trumps the
# space set in constrained_layout.
fig.get_layout_engine().set(w_pad=4 / 72, h_pad=4 / 72, hspace=0.0,
                            wspace=0.0)
受约束的布局指南

颜色条间距#

颜色条放置在距其父级的距离垫上,其中 是父级宽度的一小部分。然后下一个子图的间距由w/hspace给出。

fig, axs = plt.subplots(2, 2, layout="constrained")
pads = [0, 0.05, 0.1, 0.2]
for pad, ax in zip(pads, axs.flat):
    pc = ax.pcolormesh(arr, **pc_kwargs)
    fig.colorbar(pc, ax=ax, shrink=0.6, pad=pad)
    ax.set_xticklabels([])
    ax.set_yticklabels([])
    ax.set_title(f'pad: {pad}')
fig.get_layout_engine().set(w_pad=2 / 72, h_pad=2 / 72, hspace=0.2,
                            wspace=0.2)
垫:0,垫:0.05,垫:0.1,垫:0.2

rcParams #

可以在脚本或文件中设置五个rcParammatplotlibrc 。它们都有前缀figure.constrained_layout

  • use : 是否使用 constrained_layout。默认为假

  • w_padh_pad:围绕轴对象填充。表示英寸的浮点数。默认值为 3./72。英寸(3 分)

  • wspacehspace:子图组之间的空间。浮点数表示被分离的子图宽度的一部分。默认值为 0.02。

plt.rcParams['figure.constrained_layout.use'] = True
fig, axs = plt.subplots(2, 2, figsize=(3, 3))
for ax in axs.flat:
    example_plot(ax)
标题,标题,标题,标题

与 GridSpec 一起使用#

constrained_layout 旨在与subplots()subplot_mosaic()GridSpec()一起使用 add_subplot()

请注意,在以下内容中layout="constrained"

plt.rcParams['figure.constrained_layout.use'] = False
fig = plt.figure(layout="constrained")

gs1 = gridspec.GridSpec(2, 1, figure=fig)
ax1 = fig.add_subplot(gs1[0])
ax2 = fig.add_subplot(gs1[1])

example_plot(ax1)
example_plot(ax2)
标题,标题

更复杂的 gridspec 布局是可能的。注意这里我们使用了便利功能add_gridspecsubgridspec.

fig = plt.figure(layout="constrained")

gs0 = fig.add_gridspec(1, 2)

gs1 = gs0[0].subgridspec(2, 1)
ax1 = fig.add_subplot(gs1[0])
ax2 = fig.add_subplot(gs1[1])

example_plot(ax1)
example_plot(ax2)

gs2 = gs0[1].subgridspec(3, 1)

for ss in gs2:
    ax = fig.add_subplot(ss)
    example_plot(ax)
    ax.set_title("")
    ax.set_xlabel("")

ax.set_xlabel("x-label", fontsize=12)
标题,标题
Text(0.5, 41.33399999999999, 'x-label')

请注意,在上面的左列和右列没有相同的垂直范围。如果我们希望两个网格的顶部和底部对齐,那么它们需要在同一个 gridspec 中。我们还需要使这个数字更大,以使轴不会塌陷到零高度:

fig = plt.figure(figsize=(4, 6), layout="constrained")

gs0 = fig.add_gridspec(6, 2)

ax1 = fig.add_subplot(gs0[:3, 0])
ax2 = fig.add_subplot(gs0[3:, 0])

example_plot(ax1)
example_plot(ax2)

ax = fig.add_subplot(gs0[0:2, 1])
example_plot(ax, hide_labels=True)
ax = fig.add_subplot(gs0[2:4, 1])
example_plot(ax, hide_labels=True)
ax = fig.add_subplot(gs0[4:, 1])
example_plot(ax, hide_labels=True)
fig.suptitle('Overlapping Gridspecs')
重叠网格规范、标题、标题
Text(0.5, 0.993055, 'Overlapping Gridspecs')

此示例使用两个 gridspecs 使颜色条仅与一组 pcolors 相关。请注意左列是如何比右列更宽的。当然,如果您希望子图的大小相同,则只需要一个 gridspec。请注意,使用 可以实现相同的效果subfigures

fig = plt.figure(layout="constrained")
gs0 = fig.add_gridspec(1, 2, figure=fig, width_ratios=[1, 2])
gs_left = gs0[0].subgridspec(2, 1)
gs_right = gs0[1].subgridspec(2, 2)

for gs in gs_left:
    ax = fig.add_subplot(gs)
    example_plot(ax)
axs = []
for gs in gs_right:
    ax = fig.add_subplot(gs)
    pcm = ax.pcolormesh(arr, **pc_kwargs)
    ax.set_xlabel('x-label')
    ax.set_ylabel('y-label')
    ax.set_title('title')
    axs += [ax]
fig.suptitle('Nested plots using subgridspec')
fig.colorbar(pcm, ax=axs)
使用 subgridspec、Title、Title、title、title、title、title 的嵌套图
<matplotlib.colorbar.Colorbar object at 0x7f2cdf471c30>

Matplotlib 现在提供subfigures 的不是使用 subgridspecs,而是提供了以下功能constrained_layout

fig = plt.figure(layout="constrained")
sfigs = fig.subfigures(1, 2, width_ratios=[1, 2])

axs_left = sfigs[0].subplots(2, 1)
for ax in axs_left.flat:
    example_plot(ax)

axs_right = sfigs[1].subplots(2, 2)
for ax in axs_right.flat:
    pcm = ax.pcolormesh(arr, **pc_kwargs)
    ax.set_xlabel('x-label')
    ax.set_ylabel('y-label')
    ax.set_title('title')
fig.colorbar(pcm, ax=axs_right)
fig.suptitle('Nested plots using subfigures')
使用子图的嵌套图,标题,标题,标题,标题,标题,标题
Text(0.5, 0.9895825, 'Nested plots using subfigures')

手动设置轴位置#

手动设置轴位置可能有充分的理由。手动调用set_position将设置轴,因此 constrained_layout 不再对其产生影响。(请注意,constrained_layout仍然为移动的轴留下空间)。

fig, axs = plt.subplots(1, 2, layout="constrained")
example_plot(axs[0], fontsize=12)
axs[1].set_position([0.2, 0.2, 0.4, 0.4])
标题

固定纵横比轴的网格:“压缩”布局#

constrained_layout在轴的“原始”位置网格上运行。但是,当轴具有固定的纵横比时,通常会使一侧变短,并在缩短的方向上留下很大的间隙。在下面,轴是方形的,但图形很宽,所以有一个水平间隙:

fig, axs = plt.subplots(2, 2, figsize=(5, 3),
                        sharex=True, sharey=True, layout="constrained")
for ax in axs.flat:
    ax.imshow(arr)
fig.suptitle("fixed-aspect plots, layout='constrained'")
固定纵横比图,布局='约束'
Text(0.5, 0.98611, "fixed-aspect plots, layout='constrained'")

解决此问题的一种明显方法是使图形尺寸更方形,但是,缩小差距确实需要反复试验。对于简单的 Axes 网格,我们可以使用它layout="compressed"来为我们完成这项工作:

fig, axs = plt.subplots(2, 2, figsize=(5, 3),
                        sharex=True, sharey=True, layout='compressed')
for ax in axs.flat:
    ax.imshow(arr)
fig.suptitle("fixed-aspect plots, layout='compressed'")
固定纵横比图,layout='compressed'
Text(0.5, 0.98611, "fixed-aspect plots, layout='compressed'")

手动关闭constrained_layout#

constrained_layout通常在图形的每次绘制上调整轴位置。如果您想获取由提供 constrained_layout但不更新的间距,请进行初始绘制,然后调用fig.set_layout_engine(None). 这对于刻度标签可能会改变长度的动画可能很有用。

请注意,使用工具栏的后端的 GUI 事件constrained_layout已关闭。这可以防止轴在缩放和平移期间改变位置。ZOOMPAN

限制#

不兼容的功能#

constrained_layout将与 一起使用pyplot.subplot,但前提是每次调用的行数和列数相同。原因是,如果几何形状不同,每次调用pyplot.subplot都会创建一个新 GridSpec实例,并且 constrained_layout. 所以以下工作正常:

fig = plt.figure(layout="constrained")

ax1 = plt.subplot(2, 2, 1)
ax2 = plt.subplot(2, 2, 3)
# third axes that spans both rows in second column:
ax3 = plt.subplot(2, 2, (2, 4))

example_plot(ax1)
example_plot(ax2)
example_plot(ax3)
plt.suptitle('Homogenous nrows, ncols')
同质 nrows, ncols, Title, Title, Title
Text(0.5, 0.9895825, 'Homogenous nrows, ncols')

但以下导致布局不佳:

fig = plt.figure(layout="constrained")

ax1 = plt.subplot(2, 2, 1)
ax2 = plt.subplot(2, 2, 3)
ax3 = plt.subplot(1, 2, 2)

example_plot(ax1)
example_plot(ax2)
example_plot(ax3)
plt.suptitle('Mixed nrows, ncols')
混合 nrows、ncols、Title、Title、Title
Text(0.5, 0.9895825, 'Mixed nrows, ncols')

类似地, subplot2grid具有相同的限制,即 nrows 和 ncols 无法更改以使布局看起来不错。

fig = plt.figure(layout="constrained")

ax1 = plt.subplot2grid((3, 3), (0, 0))
ax2 = plt.subplot2grid((3, 3), (0, 1), colspan=2)
ax3 = plt.subplot2grid((3, 3), (1, 0), colspan=2, rowspan=2)
ax4 = plt.subplot2grid((3, 3), (1, 2), rowspan=2)

example_plot(ax1)
example_plot(ax2)
example_plot(ax3)
example_plot(ax4)
fig.suptitle('subplot2grid')
subplot2grid,标题,标题,标题,标题
Text(0.5, 0.9895825, 'subplot2grid')

其他注意事项#

  • constrained_layout仅考虑刻度标签、轴标签、标题和图例。因此,其他艺术家可能被剪裁,也可能重叠。

  • 它假定刻度标签、轴标签和标题所需的额外空间与轴的原始位置无关。这通常是正确的,但在极少数情况下并非如此。

  • 后端处理渲染字体的方式存在细微差别,因此结果不会是像素相同的。

  • 使用超出坐标区边界的坐标区坐标的美术师在添加到坐标区时会产生不寻常的布局。这可以通过将艺术家直接添加到 Figureusing 来避免add_artist()。参见 ConnectionPatch示例。

调试#

约束布局可能会以某种意想不到的方式失败。因为它使用约束求解器,所以求解器可以找到数学上正确的解决方案,但这根本不是用户想要的。通常的失效模式是所有尺寸都崩溃到它们的最小允许值。如果发生这种情况,这是由于以下两个原因之一:

  1. 您要求绘制的元素没有足够的空间。

  2. 有一个错误 - 在这种情况下,请在 https://github.com/matplotlib/matplotlib/issues打开一个问题。

如果有错误,请使用不需要外部数据或依赖项(numpy 除外)的自包含示例进行报告。

算法注释#

约束的算法相对简单,但由于我们可以布局图形的复杂方式而具有一定的复杂性。

Matplotlib 中的布局是通过类使用 gridspecs 执行的GridSpec。网格规范是将图形逻辑划分为行和列,这些行和列中轴的相对宽度由width_ratiosheight_ratios设置。

在 constrained_layout 中,每个 gridspec 都有一个与之关联的layoutgrid 。layoutgrid的每一列都有一系列变量left,每一行都有变量,此外它还有左、右、下和上的边距。在每一行中,底部/顶部边距被加宽,直到该行中的所有装饰器都被容纳。对于列和左/右页边距也是如此。rightbottomtop

简单案例:一个轴#

对于单个轴,布局是直截了当的。图形有一个父布局网格,由一列和一行组成,而网格规范的子布局网格包含轴,同样由一行和一列组成。为轴的每一侧的“装饰”留出了空间。在代码中,这是通过以下条目完成的 do_constrained_layout()

gridspec._layoutgrid[0, 0].edit_margin_min('left',
      -bbox.x0 + pos.x0 + w_pad)

其中bbox是轴的紧密边界框,以及pos它的位置。请注意四个边距如何包含轴装饰。

from matplotlib._layoutgrid import plot_children

fig, ax = plt.subplots(layout="constrained")
example_plot(ax, fontsize=24)
plot_children(fig)
标题

简单案例:两个轴#

当有多个轴时,它们的布局以简单的方式绑定。在此示例中,左侧轴的装饰比右侧大得多,但它们共享一个底部边距,该边距足够大以容纳更大的 xlabel。与共享上边距相同。左右边距不共享,因此允许不同。

fig, ax = plt.subplots(1, 2, layout="constrained")
example_plot(ax[0], fontsize=32)
example_plot(ax[1], fontsize=8)
plot_children(fig)
标题,标题

两个轴和颜色条#

颜色条只是扩展父布局网格单元格边距的另一个项目:

fig, ax = plt.subplots(1, 2, layout="constrained")
im = ax[0].pcolormesh(arr, **pc_kwargs)
fig.colorbar(im, ax=ax[0], shrink=0.6)
im = ax[1].pcolormesh(arr, **pc_kwargs)
plot_children(fig)
受约束的布局指南

与 Gridspec 关联的颜色

如果一个颜色条属于网格的多个单元格,那么它会为每个单元格留出更大的边距:

fig, axs = plt.subplots(2, 2, layout="constrained")
for ax in axs.flat:
    im = ax.pcolormesh(arr, **pc_kwargs)
fig.colorbar(im, ax=axs, shrink=0.6)
plot_children(fig)
受约束的布局指南

大小不一的轴#

有两种方法可以使 Gridspec 布局中的轴具有不均匀的大小,或者通过将它们指定为跨 Gridspecs 行或列,或者通过指定宽度和高度比率。

这里使用第一种方法。请注意,中间topbottom边距不受左侧列的影响。这是算法的一个有意识的决定,并导致两个右手轴具有相同高度但不是左手轴高度的1/2的情况。这与gridspec没有约束布局的工作方式一致。

fig = plt.figure(layout="constrained")
gs = gridspec.GridSpec(2, 2, figure=fig)
ax = fig.add_subplot(gs[:, 0])
im = ax.pcolormesh(arr, **pc_kwargs)
ax = fig.add_subplot(gs[0, 1])
im = ax.pcolormesh(arr, **pc_kwargs)
ax = fig.add_subplot(gs[1, 1])
im = ax.pcolormesh(arr, **pc_kwargs)
plot_children(fig)
受约束的布局指南

需要微调的一种情况是边距没有任何艺术家限制其宽度。在下面的例子中,第 0 列的右边距和第 3 列的左边距没有边距艺术家来设置它们的宽度,所以我们取有艺术家的边距宽度的最大宽度。这使得所有轴具有相同的大小:

fig = plt.figure(layout="constrained")
gs = fig.add_gridspec(2, 4)
ax00 = fig.add_subplot(gs[0, 0:2])
ax01 = fig.add_subplot(gs[0, 2:])
ax10 = fig.add_subplot(gs[1, 1:3])
example_plot(ax10, fontsize=14)
plot_children(fig)
plt.show()
标题

脚本总运行时间:(0分18.885秒)

由 Sphinx-Gallery 生成的画廊