动态绘图的图像生成期间会导致界面卡顿 >>>> 引入多线程(QT界面涉及到线程安全,是不允许子线程去更改主线程界面中的任何控件状态,所以就导致了很多的麻烦)。
matplotlib会生成静态图后并不能实时更新 >>>> 使用ion,开启matplotlib的动态绘制模式
图片在绘制过程中会一条一条往上叠加,不会清除原来的曲线 >>>> 一种方案是将图片的hold性质改成False,但是hold属性在较新版本的matplotlib包里面已经删掉了,想要用要回退到以前的版本;另一种方案是在绘图直线执行以下clear操作。
虽然设置了图坐标轴名称及其他文字内容,但是图片更新的过程中坐标轴会移动,而且文字会丢失 >>>> 这个应该有解决方法,但是没找到。
测试每次图片更新间隔时间为0.25s左右,也就是说1秒只能更新四张图片 >>>> 无解,狗带。
图片更新过程中有时会丢失曲线,丢失曲线后报错都是底层包的“list index out of range” >>>> 好像也是无解的。
一个是将原本使用的Python自带多线程Thread替换成QT框架下的QThread,通过信号与槽这样一个机制来向界面主线程发送信息更改界面内容;
另一个是用pyqtgraph库来进行动态绘图,pyqtgraph库是专门用于Qt GUI界面上的图像绘制,主打的就是轻量级的特点。虽然这个库的可参考资料实在不多,很多特性更改都是啃文档和摸索实现的,但是结果是真香!!!!
1. matplotlib实现PyQt界面上的动态绘图
matplotlib动态绘图代码主要分为三个部分:初始化,更新函数,图像绘制。
1.1 界面开启时候的初始化设置
1#画图函数的初始设置
2def drawing_init(self):
3 self.layout = QHBoxLayout(self.label)
4 self.mpl = PlotCanvas(self, width=3, height=9) # 实例化一个画布对象
5 #创建每一幅图的绘图数据
6 self.move_val1 = [0] * 50
7 self.move_val2 = [0] * 50
8 self.move_val3 = [0] * 50
9 self.move_val4 = [0] * 50
1.2 动态图像初始化及更新函数
1#绘图线程
2class PlotCanvas(FigureCanvas):
3 def __init__(self, parent=None, width=5, height=4, dpi=100):
4 # 配置中文显示
5 plt.rcParams['font.family'] = ['SimHei'] # 用来正常显示中文标签
6 plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
7 self.fig = plt.figure(figsize=(width, height), dpi=dpi)
8 self.font1 = {'family': 'KaiTi',
9 'weight': 'normal',
10 'size': 13,
11 }
12 self.axe1 = self.fig.add_subplot(411)
13 # self.axe1.hold(False)
14 self.axe1.set_xlabel("发动机1", self.font1)
15 self.axe1.set_xticks([])
16 self.axe1.set_ylabel("运动情况", self.font1)
17 self.axe1.set_yticks([])
18 plt.subplots_adjust(left=0.1, top=0.99, right=0.99, bottom=0.03, wspace=0, hspace=0.15)
19 self.axe2 = self.fig.add_subplot(412, xlim=(0, 50), ylim=(-10, 10))
20 # self.axe2.hold(False)
21 self.axe2.set_xlabel("发动机2", self.font1)
22 self.axe2.set_xticks([])
23 self.axe2.set_ylabel("运动情况", self.font1)
24 self.axe2.set_yticks([])
25 self.axe3 = self.fig.add_subplot(413, xlim=(0, 50), ylim=(-10, 10))
26 # self.axe3.hold(False)
27 self.axe3.set_xlabel("发动机3", self.font1)
28 self.axe3.set_xticks([])
29 self.axe3.set_ylabel("运动情况", self.font1)
30 self.axe3.set_yticks([])
31 self.axe4 = self.fig.add_subplot(414, xlim=(0, 50), ylim=(-10, 10))
32 # self.axe4.hold(False)
33 self.axe4.set_xlabel("发动机4", self.font1)
34 self.axe4.set_xticks([])
35 self.axe4.set_ylabel("运动情况", self.font1)
36 self.axe4.set_yticks([])
37 FigureCanvas.__init__(self, self.fig)
38 self.setParent(parent)
39 #定义FigureCanvas的尺寸策略,这部分的意思是设置FigureCanvas,使之尽可能的向外填充空间
40 FigureCanvas.setSizePolicy(self,
41 QSizePolicy.Expanding,
42 QSizePolicy.Expanding)
43 FigureCanvas.updateGeometry(self)
44
45 def drawing(self,move_val1,move_val2,move_val3,move_val4):
46 self.axe1.clear()
47 self.axe2.clear()
48 self.axe3.clear()
49 self.axe4.clear()
50 plt.ion()
51 self.axe1.plot(move_val1)
52 self.axe2.plot(move_val2)
53 self.axe3.plot(move_val3)
54 self.axe4.plot(move_val4)
这里函数里面给出了matplotlib绘图基础设置的一些代码,有文字正常显示、字体、多个图像组成一幅图、尺寸设置等。在drawing函数中,50行的plt.ion()开启了动态绘图。
1.3 图像绘制
1#更改绘图数据
2del self.move_val1[0]
3del self.move_val2[0]
4del self.move_val3[0]
5del self.move_val4[0]
6self.move_val1.append(img_val[0])
7self.move_val2.append(img_val[1])
8self.move_val3.append(img_val[2])
9self.move_val4.append(img_val[3])
10#每次绘制之前布局中移除原本图像,再添加新的图像
11self.layout.removeWidget(self.mpl)
12self.mpl.drawing(self.move_val1, self.move_val2, self.move_val3, self.move_val4)
13self.layout.addWidget(self.mpl)
效果如下:
可以看出图像更新有些卡顿,而且有时候图像会没有曲线,还有坐标轴是真的丑。
2. pyqtgraph实现PyQt界面上的动态绘图 2.1 绘图函数初始化 1#画图功能画布初始化
2def drawing_init(self):
3 self.move_val_list = [[], [], [], []]
4 self.data_val_list = [[], [], [], []]
5
6 self.pen1 = QPen(QColor(110, 242, 255)) #Qpen的画笔
7 self.pen1.setWidth(0.03)
8 self.pen1.setStyle(Qt.SolidLine)
9 self.pen2 = QPen(QColor(207, 155, 255))
10 self.pen2.setWidth(0.03)
11 self.pen2.setStyle(Qt.SolidLine)
12 self.pen3 = pg.mkPen({'color': (110, 242, 255), 'width': 2}) #mkPen的画笔
13 self.pen4 = pg.mkPen({'color': (207, 155, 255), 'width': 2})
14 pg.setConfigOption('background', (15, 64, 123)) #设置背景色
15 pg.setConfigOption('foreground', (148, 209, 224)) #设置坐标轴颜色
16 self.plot_layout1 = QGridLayout(self.drawlabel1) #创建网格Layout
17 self.plot_plt1 = pg.PlotWidget() #初始化绘图板
18 self.plot_plt1.setYRange(max=-1.3, min=1.3) #设置纵坐标区域
19 self.plot_layout1.addWidget(self.plot_plt1) #将绘图板置于布局中
20 # self.plot_plt1.setLabel('bottom', "开始运动时间", units='s')
21 # pltItem = self.plot_plt1.getPlotItem()
22 self.plot_plt1.showGrid(x=True, y=True, alpha=0.8) #设置网格及其透明度
23 self.plot_plt1.setMouseEnabled(x=True, y=False) #设置能够通过鼠标滚轮或者拖动图片
24 self.plot_moveval1 = self.plot_plt1.plot(pen=self.pen3) #设置绘图曲线1画笔
25 self.plot_dataval1 = self.plot_plt1.plot(pen=self.pen4) #设置绘图曲线2画笔
解释点:
2.2 图像更新
1try:
2 self.plot_moveval1.setData(move_x_val, graph_move_val_list[0])
3 self.plot_dataval1.setData(data_x_val, graph_data_val_list[0])
4except Exception:
5 pass
解释点:
用setData就能更新图像,setData的机制应该是只重绘新的数据点,且更新对象是曲线,因此不会存在上一条曲线仍然留在图像中的问题。
经过测试图像更新一次时间在10ms以下,比起matplotlib动态图的250ms性能提升很多。
尽量将绘图数据更新置于其他线程,以提高绘图速度。数据的更新与绘图时数据的获取可以考虑采取一个线程锁,保护数据的一致性。为了线程安全,设置了一个子线程每隔一秒钟发射信号让绘图函数执行一次。
这里用了try....except的异常机制是因为x轴的数据是通过np.arrange对一个区间进行均分的,在少数情况下x轴数据与y轴数据会差1个,这时候就会报错。因此引入异常机制,如果位数不等这次图像更新就不执行了,等待下一秒一起更新。
效果:
pyqtgraph好用的地方在于 轻量级 ,而且内嵌了 鼠标事件 ,左下角还有一个A的按钮,可以让图像回到最适合的大小。右键图片还自带图片保存,鼠标事件设置等。 缺点就是资料比较少,主要资料有:官方文档:https://pyqtgraph.readthedocs.io/en/latest/index.html
大佬教程:https://zmister.com/archives/category/data-apply/pyqtgtaph-tutorial/