我的目标是在matplotlib中进行实时绘图,并将这些绘图嵌入用pygtk(或pygobject,永远记不清哪一个)编写的GTK3 GUI中。有效执行此操作的一种方法是使用“ blitting”和this example通过保存背景区域,在每次更新后还原它并重画艺术家线条来演示如何进行操作。经过相当多的敲击操作,我发现default matplotlib examples文档介绍了如何在GTK中使用matplotlib进行图形绘制,但是使用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))
这是我正在处理的示例,其中我尝试使用保存的背景区域并通过点按以提高绘图速度和减少CPU负载来集成上述链接的示例。
最后,最终用途将不是从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()
这是示例示例: Example GIF
这是上面的代码示例为我生成的: GIF of above code example
图形线也无法正确呈现。
任何帮助将不胜感激!
编辑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
如果更新间隔快于130ms,我仍然有一个问题,即图形边框和刻度线不被重绘。如果timeout_add()
使用的数字小于130,则图形刻度和边框将无法正确绘制。我认为这是因为重绘刻度线需要花费超过130ms的时间,因此可能需要另存为另一组“背景”。
编辑2:
另一个突破!在this answer的帮助下解决了更新间隔问题。下面的代码能够以2ms的间隔刷新,但是我注意到如果尝试调整窗口大小,它仍然存在一些问题。它还具有几乎没有CPU负载的额外好处!为了解决这个问题,我将刷新率保持在10ms。这样做的目的是无论如何都要为图形提供异步更新,而不是下面显示的测试的定期更新间隔。
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()