如何创建一组带有链接(共享)x轴的图表,在缩放期间自动缩放所有“从”图的y轴?例如:
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = fig.add_subplot(211)
ax2 = fig.add_subplot(212, sharex=ax1)
ax1.plot([0,1])
ax2.plot([2,1])
plt.show()
当我放大ax1时,这也会更新ax2的x轴(到目前为止一直很好),但我也希望ax2的y轴基于现在可见的数据范围进行自动缩放。所有自动缩放设置均已启用(默认设置)。创建ax2后,手动设置自动缩放设置没有帮助:
ax2.autoscale(enable=True, axis='y', tight=True)
ax2.autoscale_view(tight=True, scalex=False, scaley=True)
print ax2.get_autoscaley_on()
-> True
我错过了什么吗?
答案 0 :(得分:23)
在研究了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之间进入无限循环所必需的( )和'xlim_changed'回调。
此外,似乎没有统一的方法来确定水平裁剪对象的垂直范围,因此在axes.py中有单独的代码来处理Lines,Patches,Collections等,这些都需要复制在回调中。
在任何情况下,上面的代码都适合我,因为我的情节中只有线条,我很满意紧张=真实的布局。看来,只需对axes.py进行一些更改,就可以更加优雅地适应这种功能。
修改强>
我错误的是无法加入更高级别的自动缩放功能。它只需要一组特定的命令即可正确分离x和y。我更新了代码以在y中使用高级自动缩放,这应该使它更加健壮。特别是,tight = False现在可以工作(毕竟看起来好多了),反转/对数轴应该不是问题。
剩下的一个问题是,一旦裁剪到特定的x范围,就确定所有类型对象的数据限制。这个功能应该是内置的matplotlib,因为它可能需要渲染器(例如,如果一个放大得足够远,屏幕上只剩下0或1个点,上面的代码就会中断)。 Axes.relim()方法看起来很合适。如果数据已更改,则应重新计算数据限制,但目前仅处理行和补丁。 Axes.relim()可以有一个可选参数,用于指定x或y中的窗口。
答案 1 :(得分:0)
我不知道合适的协议,但是最近我用这个答案重新缩放了一些具有fill_betweens的时间序列数据。以下是我为实现此目的所做的更改。我敢打赌,经过8年之后,还有一些更简单的方法可以做到这一点...
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()
if np.issubdtype(x.dtype, np.datetime64):
# convert dates to numbers so searchsorted works
x = matplotlib.dates.date2num(x)
# 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))
for c in a.collections:
for p in c.get_paths():
vertices = p.vertices
x, y = vertices[:, 0], vertices[:, 1]
# x won't be sorted when you pull path vertices
yc = y[(x >= xlim[0]) & (x <= xlim[1])]
ylim = min(ylim[0], np.nanmin(yc)), max(ylim[1], np.nanmax(yc))
# TODO: update limits from Patches, Texts, ...
# 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