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_grid1
的 make_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()
我正在开发一个 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_grid1
的 make_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()