Моя цель - создавать графики в режиме реального времени в matplotlib и встраивать эти графики в графический интерфейс GTK3, написанный с помощью pygtk (или pygobject, никогда не запомните, какой именно). Один из способов сделать это эффективно - использовать «blitting», и этот пример покажет, как это сделать, сохранив фоновую область, восстановив ее после каждого обновления и перерисовав линию исполнителя. После нескольких попыток smacking-head-on-keyboard я обнаружил, что стандартные примеры matplotlib , в которых описывается, как выполнять построение графиков с помощью matplotlib в GTK, используют рендерер GTK3Cairo, которого было недостаточно (так как этого не происходит). поддержка blitting) и что мне нужно было переключить на GTK3Agg. Это сработало успешно, и я могу подражать приведенному выше примеру, встраивая его в виджет GTK ScrolledWindow. Однако графики, кажется, рисуют друг над другом в нижней части каждого графика.
Я проследил это до пары вопросов. Похоже, что сохраненная фоновая область расположена неправильно и слегка смещена. Если я изменю вызов функции restore_region(background)
(см. Ниже) и вместо этого попытаюсь изменить значения bbox и xy, я могу сместить область фона вокруг, но это не решит проблему нижней части графика, рисующего над собой (смещения ниже приведены только произвольные числа).
x1, y1, x2, y2 = background.get_extents()
restore_region(background, bbox=(x1, y1-20, x2, y2), xy=(x1+9, y1+10))
Вот пример, над которым я работаю, в котором я пытался интегрировать приведенный выше пример, используя сохраненные фоновые области и блиц для увеличения скорости печати и уменьшения нагрузки на процессор.
В конце концов, окончательное использование не будет обновляться с timeout_add()
, я буду обновлять данные, полученные из отдельного потока обработки.
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GObject
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas
class TestPlot(Gtk.ScrolledWindow):
def __init__(self):
Gtk.ScrolledWindow.__init__(self)
self.x = np.arange(0, 2*np.pi, 0.1)
self.y = np.sin(self.x)
self.fig, self.axes = plt.subplots(nrows=6)
self.canvas = FigureCanvas(self.fig)
self.add_with_viewport(self.canvas)
self.fig.canvas.draw()
styles = ['r-', 'g-', 'y-', 'm-', 'k-', 'c-']
def plot(ax, style):
return ax.plot(self.x, self.y, style, animated=True)[0]
self.lines = [plot(ax, style) for ax, style in zip(self.axes, styles)]
self.backgrounds = [self.fig.canvas.copy_from_bbox(ax.bbox) for ax in self.axes]
self.i = 1
GObject.timeout_add(10, self.update_graph)
def update_graph(self):
try:
items = enumerate(zip(self.lines, self.axes, self.backgrounds), start=1)
for j, (line, ax, background) in items:
line.set_ydata(np.sin(j*self.x + self.i/10.))
self.fig.canvas.restore_region(background)
ax.draw_artist(line)
self.fig.canvas.blit(ax.bbox)
self.i += 1
except:
pass
return True
class TestWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="TestWindow")
self.set_default_size(700, 500)
self.graph = TestPlot()
self.add(self.graph)
win = TestWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()
Вот как выглядит пример:
Пример GIF
Это то, что приведенный выше пример кода производит для меня:
GIF приведенного выше примера кода
Линии графика также отображаются неправильно.
Любая помощь будет принята с благодарностью!
Редактировать 1:
Я решил проблему перерисовки графа над собой, а вызов restore_background()
не стирает все. Сначала я установил self.backgrounds = None
и изменил update_graph()
, чтобы сначала проверить, были ли сохранены фоны, и сохранить их, если они не были. Это дает основному потоку GTK время для правильного обновления и изменения размеров окон и графиков, а затем сохраняет фоны, как только это будет сделано.
def update_graph(self):
if self.backgrounds is None:
self.backgrounds = [self.fig.canvas.copy_from_bbox(ax.bbox) for ax in self.axes]
try:
items = enumerate(zip(self.lines, self.axes, self.backgrounds), start=1)
for j, (line, ax, background) in items:
line.set_ydata(np.sin(j*self.x + self.i/10.))
self.fig.canvas.restore_region(background)
ax.draw_artist(line)
self.fig.canvas.blit(ax.bbox)
self.i += 1
except:
pass
return True
У меня все еще есть проблема, заключающаяся в том, что граница графика и галочки не перерисовываются, если интервал обновления превышает 130 мс. Если timeout_add()
использует значение меньше 130, отметки и границы графика будут отображены неправильно. Я предполагаю, что это потому, что перерисовывание тиков занимает больше 130 мс, поэтому может потребоваться сохранить его как другой набор «фонов».
Редактировать 2:
Еще один прорыв! Решена проблема интервала обновления с помощью этого ответа . Приведенный ниже код может обновляться с интервалом в 2 мс, но я заметил, что он все еще имеет некоторые проблемы, если я пытаюсь изменить размер окна. Это также имеет дополнительное преимущество: практически нет загрузки процессора! Чтобы решить эту проблему, я сохранил частоту обновления на 10 мс. Целью этого является обеспечение асинхронного обновления графика в любом случае вместо регулярных интервалов обновления для теста, показанного ниже.
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GObject
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas
class TestPlot(Gtk.ScrolledWindow):
def __init__(self):
Gtk.ScrolledWindow.__init__(self)
self.background = None
self.x = np.arange(0, 2*np.pi, 0.1)
self.y = np.sin(self.x)
self.z = np.cos(self.x)
self.fig = Figure()
self.canvas = FigureCanvas(self.fig)
self.ax = self.fig.add_subplot(111, axisbg="black")
self.add_with_viewport(self.canvas)
self.red_line, = self.ax.plot(self.x, self.y, "r-", animated=True)
self.green_line, = self.ax.plot(self.x, self.z, "lime", animated=True)
self.i = 1
self.canvas.mpl_connect("draw_event", self.on_draw)
GObject.timeout_add(10, self.update_graph)
def update_graph(self):
try:
self.red_line.set_ydata(np.sin(self.x + self.i/10.))
self.green_line.set_ydata(np.cos(self.x + self.i/10.))
self.fig.canvas.restore_region(self.background)
self.ax.draw_artist(self.red_line)
self.ax.draw_artist(self.green_line)
self.fig.canvas.blit(self.ax.clipbox)
self.i += 1
except:
pass
return True
def save_bg(self):
self.background = self.fig.canvas.copy_from_bbox(self.ax.get_figure().bbox)
def on_draw(self, *args):
self.save_bg()
return False
class TestWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="TestWindow")
self.set_default_size(700, 500)
self.graph = TestPlot()
self.add(self.graph)
win = TestWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()