上一篇介绍了如何在PyQt的界面上通过Matplotlib绘制静态的曲线图、柱形图、二维图和三维图。这一篇介绍一下如何通过数据更新实现这些图形的动态显示。
要实现图形或数据的动态更新,必然需要一个时间循环,在PyQt中一般通过QTimer来实现循环控制。QTimer的使用方式如下所示。
def __init__(self,parent=None):
super(ImgDisp,self).__init__(parent)
self.setupUi(self)
self.Init_Widgets()
self.timer=QTimer()
self.timer.start(1)
self.ts=time.time()
self.timer.timeout.connect(self.UpdateImgs)
我们在上一篇创建的ImgDisp类的初始函数中添加QTimer计时器。QTimer计时器的使用方法分成三步:
1、创建一个计时器:
self.timer=QTimer()
2、启动计时器,括号里的数字代表循环的周期,单位为毫秒:
self.timer.start(1)
需要注意的是,如果待循环的函数体如果在设置的周期内没有完成,那么计时器会等待循环函数体执行完后再开始下一个循环。
3、设置循环函数体:
self.timer.timeout.connect(self.UpdateImgs)
这样,程序开始运行后就会开始计时,每个计时周期调用一次self.UpdateImgs函数,知道关闭程序。
如果程序运行中想要停止循环,可以通过调用计时器停止函数:
self.timer.stop()
如前所述,我们定义好了循环函数self.UpdateImgs,接下来需要在该函数中添加代码实现图形的动态更新。
self.UpdateImgs函数的代码内容如下:
def UpdateImgs(self):
dt=time.time()-self.ts
self.LineUpdate(dt)
self.BarUpdate(dt)
self.ImgUpdate(dt)
self.SurfUpdate(dt)
我们定义程序运行当前时间与程序开始的时间之差作为时间变量,实现图像的动态变化。图形的更新通过代码中四个函数调用实现。下面我们来分别编写这四个功能函数实现图形更新功能。
在Matlab里我们要对图形进行更新只要在该轴上重新绘制图形即可,而在Matplotlib中,重新调用plot、imshow等函数会在原来的图形上重复绘制新的图形,不仅达不到预期效果,还会消耗大量的计算资源。通过调用clear函数可以清楚掉以前的图形,但同时也会清楚掉坐标范围等属性,并不理想,最好的办法是直接更新产生图形的数据。
对于曲线图,直接调用我们上一篇定义的Line2D对象的set_ydata()函数即可对其数据进行更新,然后调用draw()函数即可:
def LineUpdate(self,dt):
z=np.sin(self.x+dt)
self.line.set_ydata(z)
self.LineFigure.draw()
对于柱形图,我们只需要调用set_height()函数更新每一个矩形的高度,然后调用draw函数更新图形,即可:
def BarUpdate(self,dt):
x=np.sin(np.arange(-4,4,0.5)+dt)
for i in range(len(self.patches)):
self.patches[i].set_height(x[i])
self.bar.patches=self.patches
self.BarFigure.draw()
对于二维图的更新比较简单,只需要调用set_data函数,将新的二维矩阵传递给imag对象即可:
def ImgUpdate(self,dt):
X=self.X+dt
Y=self.Y+dt
R=np.sqrt(X**2+Y**2)
Z=np.sin(R)
self.ImgFig.set_data(Z)
self.ImgFigure.draw()
对于三维图则比较麻烦,它没有给出直接更新其数据的方法。如果我们去阅读plot_surface函数的帮助文档,会发现其返回的是一个Patch3DCollection的对象,再去查阅Patch3DCollection的定以可以发现它是一个多边形(polys)的列表。我们通过plot_surface的函数代码可以发现,它也是先利用X,Y,X数据创建好polys列表,然后再调用set_verts函数创建Patch3DCollection对象。因此,为了更新三维图,需要先利用新的X,Y,Z数据生成polys对象,然后再调用set_verts函数。而生成polys对象的方法可以从plot_surface函数的定义中借鉴过来。
因此,实现三维图的更新的代码如下所示,我们要先定义一个Get3dVerts(self,X,Y,Z)函数来生成polys对象,然后调用set_verts函数将该对象传递给Patch3DCollection对象。
def SurfUpdate(self,dt):
X = self.X + dt
Y = self.Y + dt
R = np.sqrt(X ** 2 + Y ** 2)
Z = np.sin(R)
polys=self.Get3dVerts(self.X,self.Y,Z)
self.Surf.set_verts(polys)
self.SurfFigure.draw()
def Get3dVerts(self,X,Y,Z):
if Z.ndim != 2:
raise ValueError("Argument Z must be 2-dimensional.")
X, Y, Z = np.broadcast_arrays(X, Y, Z)
rows, cols = Z.shape
rcount = 50
ccount = 50
rstride = int(max(np.ceil(rows / rcount), 1))
cstride = int(max(np.ceil(cols / ccount), 1))
# evenly spaced, and including both endpoints
row_inds = list(range(0, rows - 1, rstride)) + [rows - 1]
col_inds = list(range(0, cols - 1, cstride)) + [cols - 1]
polys = []
for rs, rs_next in zip(row_inds[:-1], row_inds[1:]):
for cs, cs_next in zip(col_inds[:-1], col_inds[1:]):
ps = [
# +1 ensures we share edges between polygons
cbook._array_perimeter(a[rs:rs_next + 1, cs:cs_next + 1])
for a in (X, Y, Z)
]
# ps = np.stack(ps, axis=-1)
ps = np.array(ps).T
polys.append(ps)
return polys