Ответ 1
После изучения подробностей о матах 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 имеется отдельный код для обработки строк, патчей, коллекций и т.д., которые все должны быть реплицированы в обратном вызове.
В любом случае, код выше работал у меня, так как у меня есть только строки в моем сюжете, и я доволен тугой = True макет. Похоже, что всего лишь несколько изменений в axes.py можно было бы приспособить эту функциональность намного элегантнее.
Edit:
Я ошибался в том, что не смог подключиться к функции автомасштабирования более высокого уровня. Для этого требуется только определенный набор команд для правильного разделения x и y. Я обновил код, чтобы использовать автосканирование высокого уровня в y, что должно сделать его значительно более надежным. В частности, tight = False теперь работает (выглядит намного лучше в конце концов), а обратные/лог-оси не должны быть проблемой.
Одной из оставшихся проблем является определение пределов данных для всех видов объектов, однажды обрезанных до определенной степени. Эта функциональность действительно должна быть встроена в matplotlib, так как для этого может потребоваться средство визуализации (например, вышеописанный код будет разорван, если он масштабируется достаточно далеко, чтобы на экране осталось только 0 или 1 балл). Метод Axes.relim() выглядит хорошим кандидатом. Предполагается, что данные будут пересчитаны, если данные были изменены, но в настоящее время обрабатываются только строки и патчи. Могут быть необязательные аргументы Axes.relim(), которые указывают окно в x или y.