subplots_adjust 不可预测地移动轴?

subplots_adjust moves axes unpredictably?

我正在开发一个 python 模块,该模块创建一个带有 on_resize 侦听器的 matplotlib 图。侦听器将下轴的高度强制为特定数量的像素(而不是相对于图形大小缩放)。有用。但是,如果(在 matplotlib 交互模式下)在创建绘图后用户调用 fig.subplots_adjust() 它会弄乱子图的大小。这是模块功能的彻底简化版本:

import matplotlib.pyplot as plt
plt.ion()

def make_plot():
    fig = plt.figure()
    gs = plt.GridSpec(10, 1, figure=fig)
    ax_upper = fig.add_subplot(gs[:-1])
    ax_lower = fig.add_subplot(gs[-1])
    ax_upper.plot([0, 1])
    ax_lower.plot([0, 1])
    fig.canvas.mpl_connect('resize_event', on_resize)
    return fig


def on_resize(event):
    fig = event.canvas.figure
    # get the current position
    ax_lower_pos = list(fig.axes[1].get_position().bounds)  # L,B,W,H
    # compute desired height in figure-relative coords
    desired_height_px = 40
    xform = fig.transFigure.inverted()
    desired_height_rel = xform.transform([0, desired_height_px])[1]
    # set the new height
    ax_lower_pos[-1] = desired_height_rel
    fig.axes[1].set_position(ax_lower_pos)

    # adjust ax_upper accordingly
    ax_lower_top = fig.axes[1].get_position().extents[-1]   # L,B,R,T
    ax_upper_pos = list(fig.axes[0].get_position().bounds)  # L,B,W,H
    # new bottom
    new_upper_bottom = ax_lower_top + desired_height_rel
    ax_upper_pos[1] = new_upper_bottom
    # new height
    ax_upper_top = fig.axes[0].get_position().extents[-1]   # L,B,R,T
    new_upper_height = ax_upper_top - new_upper_bottom
    ax_upper_pos[-1] = new_upper_height
    # set the new position
    fig.axes[0].set_position(ax_upper_pos)

    fig.canvas.draw()

这是用户调用 fig = make_plot():

时的输出

现在,如果用户调用 fig.subplots_adjust,底轴被挤压,底轴和顶轴之间的 space 被挤压得更厉害(on_resize 监听器将它们都设置为40px):

fig.subplots_adjust(top=0.7)

在这一点上,抓住 window 的角并拖动一点点就足以触发 on_resize 侦听器并恢复我想要的内容(底轴的固定像素高度和 space 轴之间)同时保持新添加的宽上边距不变:

如何在不必手动触发调整大小事件的情况下获得该结果? 据我所知,subplots_adjust 不会触发我触发的任何事件可以听。

认为问题出在ax.update_params() updating the axes position with a figbox taken from the underlying subplotspec (which as far as I can tell doesn't get updated after initial figure creation?). (note: update_params is called from within subplots_adjust, see here).

潜在的问题似乎是制作一个具有特定像素高度的轴。一个简单的解决方案是使用 mpl_toolkits.axes_grid1make_axes_locatable。 这允许摆脱任何回调,从而摆脱事件中竞争条件的完整问题。

注意:情节似乎是更大图书馆的一部分。由于不光顾此类软件包的用户总是好的,因此通常会允许他们指定要绘图的轴,这样他们就可以将绘图与其他元素一起放入更大的图形中。下面的解决方案使这特别容易。

当然,也可以随时调用plt.subplots_adjust

import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.axes_divider import make_axes_locatable

desired_height_px = 40 #pixel

def make_plot(ax=None):
    if not ax:
        fig, ax = plt.subplots()
    else:
        fig = ax.figure
    div = make_axes_locatable(ax)
    cax = div.append_axes("bottom", desired_height_px/fig.dpi, pad=0.25)

    sc1 = ax.scatter([2,1,3], [2,3,1], c=[1,2,3])
    sc2 = cax.scatter([3,2,1],[2,3,1], c=[3,1,2])

    return fig, ax, cax, (sc1, sc2) 


fig, (ax1, ax2) = plt.subplots(1,2)

make_plot(ax=ax1)

#user plot on ax2
ax2.plot([1,3])

fig.subplots_adjust(top=0.7)
plt.show()