强基初中数学&学Python——第264课 数字和数学第三方模块Matplotlib之七:封装端-约束布局指南(3)

自行设置图表域(axes)位置

  有充分的理由自行设置图表域(axes)位置。自行调用set_position将设置图表域(axes),因此约束布局不再对该图表域产生影响。(请注意,约束布局仍为移动的图表域保留间距(space))。

· 

· 

· 

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])

  完整代码:

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

ipython%matplotlib tkimport matplotlib.pyplot as pltimport matplotlib.colors as mcolorsimport matplotlib.gridspec as gridspecimport 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([12])    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, 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])

 

 

固定纵横比图表域(Axes)的网格:“压缩”布局

  约束布局在图表域(axes)的“原始”位置网格上运行。然而,当图表域(Axes)具有固定的纵横比时,一侧通常会变短,并在缩短的方向上留下较大的间隙。在下文中,图表域(Axes)是方形的,但图表(figure)相当宽,因此存在水平间隙:

· 

· 

· 

· 

· 

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'")

  完整代码:

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

ipython%matplotlib tkimport matplotlib.pyplot as pltimport matplotlib.colors as mcolorsimport numpy as nparr = 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, 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'")

 

  解决这一问题的一个明显方法是使图表大小更为方正,然而,缩小差距恰恰需要反复尝试。对于简单的图表域(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'")

  完整代码:

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

ipython%matplotlib tkimport matplotlib.pyplot as pltimport matplotlib.colors as mcolorsimport numpy as nparr = 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, 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'")

 

 

手动关闭约束布局

  在每次绘制图表(figure)时,约束布局通常会调整每个图表域(axes)位置。如果想获得约束布局提供的间距,但不更新它,那么进行初始绘制后调用fig.set_layout_engine(None)关闭布局。这对于刻度标签可能会改变长度的动画可能很有用。

  另外,对于后端工具栏的缩放(ZOOM)和平移(PAN)的图形界面(GUI)事件,约束布局也已关闭,这样可防止图表域在缩放和平移过程中改变位置。

局限性

不兼容的功能

  约束布局将使用pyplot.subplot,但前提是每次调用的行数和列数相同。原因是,如果几何图形不相同,每次调用pyplot.subplot都会创建一个新的GridSpec实例,并且再进行约束布局。因此,以下操作正常:

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

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')

  完整代码:

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

ipython%matplotlib tkimport matplotlib.pyplot as pltimport matplotlib.colors as mcolorsimport matplotlib.gridspec as gridspecimport 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([12])    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 = 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')

 

  但下面的代码就会导致差劲的布局。

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

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')

  完整代码:

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

ipython%matplotlib tkimport matplotlib.pyplot as pltimport matplotlib.colors as mcolorsimport matplotlib.gridspec as gridspecimport 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([12])    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 = 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')

 

类似地,subplot2grid也有同样的限制,即行数和列数不能更改以使布局看起来良好。

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

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')

  完整代码:

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

ipython%matplotlib tkimport matplotlib.pyplot as pltimport matplotlib.colors as mcolorsimport matplotlib.gridspec as gridspecimport 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([12])    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 = 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')

 

其他注意事项

  ● 约束布局只考虑刻度标签、轴标签、标题和图例。因此,其他艺术家(artist)可能被剪辑,也可能重叠。

  ● 它假设刻度标签、轴标签和标题所需的额外空间独立于图表域(axes)的原始位置。这通常是正确的,但极少数情况不是这样。

  ● 后端处理渲染字体的方式有一些小的差异,因此结果不会像数完全相同。

  ● 当艺术家使用超出图表域边界的图表域坐标,添加到图表域时将导致出现异常布局。这可以通过使用add_artist()将艺术家直接添加到图表(Figure)中来避免。有关示例,请参阅连接面片(ConnectionPatch)。

 

调试

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

  1、没有足够的空间放置您请求绘制的元素。

  2、存在一个错误(bug)-在这种情况下,在matplotlb/matplotlib#issues中打开一个问题。

  如果存在错误,请使用不需要外部数据或依赖项(numpy除外)的独立示例进行报告。

 

关于算法的注释

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

  Matplotlib中的布局是通过GridSpec类使用gridspecs执行的。gridspecs将图表按逻辑划分为行和列,这些行和列中的图表域(Axes)的相对宽度由宽度比例(width_ratios)和高度比例(height_ratios)设置。

  在约束布局中,每个gridsec都有一个与其关联的layoutgrid。layoutgrid有一系列left(左)、right(右)变量用于每列,top(上)和bottom(下)变量用于每行,此外,它还为左、右、上和下中的每一个留有一个边距。在每一行中,上/下边距都被加宽,直到该行中的所有装饰符都被容纳。列和左/右页边距也是如此。

 

set_position:

https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.set_position.html#matplotlib.axes.Axes.set_position

pyplot.subplot:

https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.subplot.html#matplotlib.pyplot.subplot

GridSpec:

https://matplotlib.org/stable/api/_as_gen/matplotlib.gridspec.GridSpec.html#matplotlib.gridspec.GridSpec

subplot2grid:

https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.subplot2grid.html#matplotlib.pyplot.subplot2grid

Figure:

https://matplotlib.org/stable/api/figure_api.html#matplotlib.figure.Figure

add_artist():

https://matplotlib.org/stable/api/figure_api.html#matplotlib.figure.Figure.add_artist

ConnectionPatch:

https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.ConnectionPatch.html#matplotlib.patches.ConnectionPatch

matplotlib/matplotlib#issues:

https://github.com/matplotlib/matplotlib/issues