У меня есть следующий код, который создает сюжет, который может быть изменен в интерактивном режиме. Щелчок/удерживание левой кнопки мыши устанавливает положение маркера, удерживая правую кнопку и перемещая мышь, перемещает построенные данные в направлении 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, но, насколько я понял, они не помогут в моем случае. Любая помощь очень ценится, спасибо!
Этот код хорошо написан и отлично работает для меня даже при добавлении 100 кривых вместо 10. Он становится медленным, добавляя 1000 кривых; но кто будет использовать такой сюжет? Конечно, есть альтернатива, если потребуется так много кривых (например, построение изображения). Два ответа считались * blitting *. Это только вариант для точки 3 (перемещение маркера), другие не могут быть решены таким образом (как при изменении части или всего холста, ** требуется ** перерисовать). Включение: для обычного случая это работает отлично, а Matplotlib не предназначен для скорости или производительности. – ImportanceOfBeingErnest
Зная больше об использовании, может помочь найти лучшие решения. Отступив от matplotlib, можно подумать о pyqtgraph, который строится на PyQt вместо Tkinter. Это будет вариант? Это действительно намного быстрее, поскольку он напрямую использует рисование qt для рисования. (см., например, здесь: [Fast Live Plotting ...] (https://stackoverflow.com/questions/40126176/fast-live-plotting-in-matplotlib-pyplot/) – ImportanceOfBeingErnest