在研究了 matplotlib 的axes.py 的详细细节之后,似乎没有任何规定可以根据数据视图自动缩放轴,因此没有高级方法来实现我想要的。
然而,有一些“xlim_changed”事件,可以附加一个回调:
import numpy as np
def on_xlim_changed(ax):
xlim = ax.get_xlim()
for a in ax.figure.axes:
# shortcuts: last avoids n**2 behavior when each axis fires event
if a is ax or len(a.lines) == 0 or getattr(a, 'xlim', None) == xlim:
continue
ylim = np.inf, -np.inf
for l in a.lines:
x, y = l.get_data()
# faster, but assumes that x is sorted
start, stop = np.searchsorted(x, xlim)
yc = y[max(start-1,0):(stop+1)]
ylim = min(ylim[0], np.nanmin(yc)), max(ylim[1], np.nanmax(yc))
# TODO: update limits from Patches, Texts, Collections, ...
# x axis: emit=False avoids infinite loop
a.set_xlim(xlim, emit=False)
# y axis: set dataLim, make sure that autoscale in 'y' is on
corners = (xlim[0], ylim[0]), (xlim[1], ylim[1])
a.dataLim.update_from_data_xy(corners, ignore=True, updatex=False)
a.autoscale(enable=True, axis='y')
# cache xlim to mark 'a' as treated
a.xlim = xlim
for ax in fig.axes:
ax.callbacks.connect('xlim_changed', on_xlim_changed)
不幸的是,这是一个相当低级的黑客行为,很容易被破坏(除线、反转或对数轴之外的其他对象,...)
似乎不可能挂钩axes.py中的更高级别的功能,因为更高级别的方法不会将emit = False参数转发给set_xlim(),这是避免在set_xlim()和set_xlim()之间进入无限循环所必需的“xlim_changed”回调。
此外,似乎没有统一的方法来确定水平裁剪对象的垂直范围,因此在axes.py中有单独的代码来处理线条、补丁、集合等,这些代码都需要在回调中复制。
无论如何,上面的代码对我有用,因为我的图中只有线条,并且我对ight=True 布局感到满意。看来,只需对axes.py进行一些更改,就可以更优雅地适应这一功能。
Edit:
我错误地认为无法连接到更高级别的自动缩放功能。它只需要一组特定的命令来正确分隔 x 和 y。我更新了代码以在 y 中使用高级自动缩放,这应该使其更加健壮。特别是,tight=False 现在可以工作(毕竟看起来好多了),并且反转/对数轴不应该成为问题。
剩下的一个问题是确定所有类型对象在裁剪到特定 x 范围后的数据限制。此功能实际上应该内置于 matplotlib 中,因为它可能需要渲染器(例如,如果放大得足够远以至于屏幕上仅保留 0 或 1 个点,则上面的代码将会中断)。 Axes.relim() 方法看起来是一个不错的候选者。如果数据已更改,它应该重新计算数据限制,但目前仅处理线和补丁。 Axes.relim() 可能有可选参数,用于指定 x 或 y 中的窗口。