2017-01-27 11 views
1

У меня есть следующий код, который создает сюжет, который может быть изменен в интерактивном режиме. Щелчок/удерживание левой кнопки мыши устанавливает положение маркера, удерживая правую кнопку и перемещая мышь, перемещает построенные данные в направлении x и с помощью колесика мыши входы/выходы. Кроме того, изменение размера окна вызывает figure.tight_layout() так, чтобы размер осей был адаптирован к размеру окна.Python Matplotlib: уменьшить время рендеринга для интерактивного сюжета

# coding=utf-8 
from __future__ import division 

from Tkinter import * 

import matplotlib 
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg 
from matplotlib.figure import Figure 
from numpy import arange, sin, pi 

matplotlib.use('TkAgg') 


class PlotFrame(Frame): 
    def __init__(self, master, **ops): 
     Frame.__init__(self, master, **ops) 

     self.figure = Figure() 
     self.axes_main = self.figure.add_subplot(111) 
     for i in range(10): 
      t = arange(0, 300, 0.01) 
      s = sin(0.02 * pi * (t + 10 * i)) 
      self.axes_main.plot(t, s) 

     self.plot = FigureCanvasTkAgg(self.figure, master=self) 
     self.plot.show() 
     self.plot.get_tk_widget().pack(fill=BOTH, expand=1) 

     self.dragging = False 
     self.dragging_button = None 
     self.mouse_pos = [0, 0] 

     self.marker = self.figure.axes[0].plot((0, 0), (-1, 1), 'black', linewidth=3)[0] 

     self.plot.mpl_connect('button_press_event', self.on_button_press) 
     self.plot.mpl_connect('button_release_event', self.on_button_release) 
     self.plot.mpl_connect('motion_notify_event', self.on_mouse_move) 
     self.plot.mpl_connect('scroll_event', self.on_mouse_scroll) 
     self.plot.mpl_connect("resize_event", self.on_resize) 

    def on_resize(self, _): 
     self.figure.tight_layout() 

    def axes_size(self): 
     pos = self.axes_main.get_position() 
     bbox = self.figure.get_window_extent().transformed(self.figure.dpi_scale_trans.inverted()) 
     width, height = bbox.width * self.figure.dpi, bbox.height * self.figure.dpi 
     axis_size = [(pos.x1 - pos.x0) * width, (pos.y1 - pos.y0) * height] 
     return axis_size 

    def on_button_press(self, event): 
     # right mouse button clicked 
     if not self.dragging and event.button in (1, 3): 
      self.dragging = True 
      self.dragging_button = event.button 
      self.mouse_pos = [event.x, event.y] 
     # left mouse button clicked 
     if event.button == 1 and event.xdata is not None: 
      self.move_marker(event.xdata) 

    def on_button_release(self, event): 
     if self.dragging and self.dragging_button == event.button: 
      self.dragging = False 

    def on_mouse_move(self, event): 
     if self.dragging and self.dragging_button == 3: 
      dx = event.x - self.mouse_pos[0] 
      self.mouse_pos = [event.x, event.y] 
      x_min, x_max = self.figure.axes[0].get_xlim() 
      x_range = x_max - x_min 
      x_factor = x_range/self.axes_size()[0] 
      self.figure.axes[0].set_xlim([x_min - dx * x_factor, x_max - dx * x_factor]) 
      self.plot.draw() 
     elif self.dragging and self.dragging_button == 1: 
      self.move_marker(event.xdata) 

    def on_mouse_scroll(self, event): 
     if event.xdata is None: 
      return 
     zoom_direction = -1 if event.button == 'up' else 1 
     zoom_factor = 1 + .4 * zoom_direction 
     x_min, x_max = self.figure.axes[0].get_xlim() 
     min = event.xdata + (x_min - event.xdata) * zoom_factor 
     max = event.xdata + (x_max - event.xdata) * zoom_factor 
     self.figure.axes[0].set_xlim([min, max]) 
     self.plot.draw() 

    def move_marker(self, x_position): 
     y_min, y_max = self.figure.axes[0].get_ylim() 
     self.marker.set_data((x_position, x_position), (y_min, y_max)) 
     self.plot.draw() 


if __name__ == '__main__': 
    gui = Tk() 
    vf = PlotFrame(gui) 
    vf.pack(fill=BOTH, expand=1) 
    gui.mainloop() 

Реализация отлично работает, но рендеринг выполняется очень медленно при отображении большого количества строк. Как сделать рендеринг быстрее? Как вы можете видеть в приведенной выше реализации, весь сюжет рисуется полностью каждый раз, когда что-то меняется, что не нужно. Мои мысли по этому поводу:

  • Изменение размера окна: сделать все
  • Zooming: нарисовать все
  • Перемещение маркера: просто перерисовать маркера (одна линия) вместо того, чтобы рисовать все
  • Перемещение участка в x: перемещайте пиксели, отображаемые на графике влево/вправо, и рисуйте только пиксели, которые перемещаются в видимую область.

Рисование всего, когда изменение размера/масштабирования подходит для меня, но я действительно не знаю более быстрый чертеж последних двух модификаций. Я уже рассматривал анимацию matplotlib, но, насколько я понял, они не помогут в моем случае. Любая помощь очень ценится, спасибо!

+0

Этот код хорошо написан и отлично работает для меня даже при добавлении 100 кривых вместо 10. Он становится медленным, добавляя 1000 кривых; но кто будет использовать такой сюжет? Конечно, есть альтернатива, если потребуется так много кривых (например, построение изображения). Два ответа считались * blitting *. Это только вариант для точки 3 (перемещение маркера), другие не могут быть решены таким образом (как при изменении части или всего холста, ** требуется ** перерисовать). Включение: для обычного случая это работает отлично, а Matplotlib не предназначен для скорости или производительности. – ImportanceOfBeingErnest

+0

Зная больше об использовании, может помочь найти лучшие решения. Отступив от matplotlib, можно подумать о pyqtgraph, который строится на PyQt вместо Tkinter. Это будет вариант? Это действительно намного быстрее, поскольку он напрямую использует рисование qt для рисования. (см., например, здесь: [Fast Live Plotting ...] (https://stackoverflow.com/questions/40126176/fast-live-plotting-in-matplotlib-pyplot/) – ImportanceOfBeingErnest

ответ

1

Решение, как представляется, к элементам кэша, которые получают перерисованы, как вы сказали:

Одна главная вещь, которая получает перерисованы предыстория:

# cache the background 
    background = fig.canvas.copy_from_bbox(ax.bbox) 

После кэширования восстановить его с помощью restore region затем просто повторно нарисуйте точки/линию при каждом звонке

 # restore background 
     fig.canvas.restore_region(background) 

     # redraw just the points 
     ax.draw_artist(points) 

     # fill in the axes rectangle 
     fig.canvas.blit(ax.bbox) 
+0

Blitting - это только вариант для точки 3 - перемещение маркера. Все остальные команды потребуют перерисовки фона.Даже для случая с маркером это также довольно сложно, потому что вам нужно не только сохранить фон, но и все кривые. Это заставляет меня задуматься, если это действительно стоит. – ImportanceOfBeingErnest

0

Для оптимизации использования бланкета можно использовать. При этом вместо всего рисунка будут показаны только художники (те, которые были изменены).

Motplotlib использует эту технику внутри модуля animation. Вы можете использовать класс Animation в качестве ссылки для реализации того же поведения в вашем коде. Посмотрите на _blit_draw() и несколько связанных функций после него в источниках.

+0

Blitting терпит неудачу при изменении границ осей, поэтому это действительно только вариант для точки 3 - перемещение маркера. Я думаю, что даже метод биения анимационного модуля терпит неудачу, когда нужно очертить лимиты оселей ?! – ImportanceOfBeingErnest

+0

«Неудачи» - слишком сильное слово. Вы можете очистить кеш на таком событии, а затем продолжить blitting. Это то, что происходит, например, в '' 'Animation._handle_resize()' ''. В конце концов, вы не меняете пределов все время. – wombatonfire

 Смежные вопросы

  • Нет связанных вопросов^_^