Matplotlib связанные оси x с автомасштабированными о осями при масштабировании - программирование
Подтвердить что ты не робот

Matplotlib связанные оси x с автомасштабированными о осями при масштабировании

Как создать стек графиков со связанными (общими) осями 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, это также обновляет оси x ax2 (пока это так хорошо), но я также хочу, чтобы оси y ax2 были автомасштабированы на основе теперь видимого диапазона данных. Все настройки автомасштаба включены (как и по умолчанию). Это не помогло вручную установить параметры автомасштабирования после создания ax2:

ax2.autoscale(enable=True, axis='y', tight=True)
ax2.autoscale_view(tight=True, scalex=False, scaley=True)

print ax2.get_autoscaley_on()
-> True

Я что-то пропустил?

4b9b3361

Ответ 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.