Виджеты IPython для интерактивной интерактивности Matplotlib

Я хотел бы использовать виджеты ipython для добавления некоторой степени интерактивности к строкам matplotlib.

В целом сюжет может быть довольно тяжелым, и я хочу только обновить конкретный элемент сюжета. Я понимаю, что у виджета есть встроенная встроенная функция дросселирования, которая помогает не заливать ядро, но когда график берется, пусть говорят 30 секунд, я не хочу так долго ждать, чтобы обновить строку.

Прочитав ноутбуки с примерами, я смог создать базовый пример, в котором я добавляю перекрестный курсор (управляемый двумя ползунками) к оси mpl.

Проблема заключается в том, что рисунок отображается дважды. Вот код (ячейка 1):

fig, ax = plt.subplots() 
ax.plot([3,1,2,4,0,5,3,2,0,2,4])

... фигура отображается..., ячейка 2 (редактирование: спасибо Thomas K за улучшение):

vline = ax.axvline(1)
hline = ax.axhline(0.5)

def set_cursor(x, y):
    vline.set_xdata((x, x))
    hline.set_ydata((y, y))
    display(fig)

и, наконец, (ячейка 3):

interact(set_cursor, x=(1, 9, 0.01), y=(0, 5, 0.01))

снова показывает фигуру с виджетами.

Итак, вопрос:

  • Как я могу запретить отображение первой фигуры?
  • - это правильный способ сделать это или есть лучший подход?

ИЗМЕНИТЬ

Я нашел ручку конфигурации ipython, которая, согласно этому ноутбуку, позволяет запретить отображение фигур

%config InlineBackend.close_figures = False

В то время как пример ноутбука работает, я не могу понять, как использовать этот параметр сам по себе (без класса контекстного менеджера, представленного в связанном примере), чтобы скрыть отображение фигур.

РЕДАКТИРОВАТЬ 2

Я нашел некоторую документацию настраиваемого InlineBackend.close_figures.

РЕДАКТИРОВАТЬ 3

Запущен на ответ @shadanan, я хочу уточнить, что моя цель - добавить курсор к существующей фигуре, не перерисовывая график с нуля при каждом перемещении курсора. Объединение трех ячеек в одной ячейке:

fig, ax = plt.subplots()
ax.plot([3,1,2,4,0,5,3,2,0,2,4])

vline = ax.axvline(1)
hline = ax.axhline(0.5)

def set_cursor(x, y):
    vline.set_xdata((x, x))
    hline.set_ydata((y, y))
    display(fig)

interact(set_cursor, x=(1, 9, 0.01), y=(0, 5, 0.01))

он должен "работать", но это не так. При первом запуске ячейки отображаются две цифры. После взаимодействия с виджетами отображается только одна цифра. Это "странное поведение", которое требует обходного пути, как показано в ответе @shadanan. Может ли ipython dev прокомментировать это? Это ошибка?

Ответ 1

Решение оказывается действительно простым. Чтобы не показывать первый рисунок, нам просто нужно добавить вызов close() перед вызовом interact.

Вспоминая пример вопроса, ячейка, подобная этой, будет правильно отображать одну интерактивную фигуру (вместо двух):

fig, ax = plt.subplots()
ax.plot([3,1,2,4,0,5,3,2,0,2,4])
plt.close(fig)

vline = ax.axvline(1)
hline = ax.axhline(0.5)

def set_cursor(x, y):
    vline.set_xdata((x, x))
    hline.set_ydata((y, y))
    display(fig)

interact(set_cursor, x=(1, 9, 0.01), y=(0, 5, 0.01))

Более чистый подход определяет функцию add_cursor (в отдельной ячейке или script):

def add_cursor(fig, ax):
    plt.close(fig)

    vline = ax.axvline(1, color='k')
    hline = ax.axhline(0.5, color='k')

    def set_cursor(x, y):
        vline.set_xdata((x, x))
        hline.set_ydata((y, y))
        display(fig)

    interact(set_cursor, x=ax.get_xlim(), y=ax.get_ylim())

а затем вызовите его, когда мы хотим добавить интерактивный курсор:

fig, ax = plt.subplots()
ax.plot([3,1,2,4,0,5,3,2,0,2,4])
add_cursor(fig, ax)

Ответ 2

Вы можете сделать это очень быстро, используя новый (ish) notebook backend

%matplotlib notebook
import matplotlib.pyplot as plt
from IPython.html.widgets import interactive

fig, ax = plt.subplots()
ax.plot(range(5))


vline = ax.axvline(1, color='k')
hline = ax.axhline(0.5, color='k')

def set_cursor(x, y):
    vline.set_xdata((x, x))
    hline.set_ydata((y, y))
    ax.figure.canvas.draw_idle()

и в отдельной ячейке:

interactive(set_cursor, x=ax.get_xlim(), y=ax.get_ylim())

Это все равно будет перерисовывать всю фигуру каждый раз, когда вы перемещаете курсор, потому что notebook в настоящее время не поддерживает blitting (который обрабатывается на https://github.com/matplotlib/matplotlib/pull/4290)

Ответ 3

У меня есть хакерское обходное решение, которое отображает только одну фигуру. Кажется, проблема состоит в том, что в коде есть две точки, которые генерируют фигуру, и на самом деле, мы хотим только вторую, но мы не можем избежать подавления первого. Обходной путь - использовать первый для первого исполнения, второй - для всех последующих. Здесь некоторый код, который работает путем переключения между двумя в зависимости от инициализированного флага:

%matplotlib inline
import matplotlib.pyplot as plt
from IPython.html.widgets import interact, interactive, fixed
from IPython.html import widgets
from IPython.display import clear_output, display, HTML

class InteractiveCursor(object):
    initialized = False
    fig = None
    ax = None
    vline = None
    hline = None

    def initialize(self):
        self.fig, self.ax = plt.subplots()
        self.ax.plot([3,1,2,4,0,5,3,2,0,2,4])
        self.vline = self.ax.axvline(1)
        self.hline = self.ax.axhline(0.5)

    def set_cursor(self, x, y):
        if not self.initialized:
            self.initialize()

        self.vline.set_xdata((x, x))
        self.hline.set_ydata((y, y))

        if self.initialized:
            display(self.fig)
        self.initialized = True

ic = InteractiveCursor()
def set_cursor(x, y):
    ic.set_cursor(x, y)

interact(set_cursor, x=(1, 9, 0.01), y=(0, 5, 0.01));

Мое мнение таково, что это должно считаться ошибкой. Я попробовал его с объектно-ориентированным интерфейсом, и у него такая же проблема.