Python 如何实时绘制数据
提到 GUI 绘图,大家可能第一反应是 OpenGL 和 Matplotlib,但其实基于 Qt 平台还有个功能强大的 pyqtgraph 绘图库,不仅支持丰富的图形种类,还能实时更新绘图数据并进行交互式操作。
不同于网上其他文章或代码讲解,今天我们集中只关注实时绘制数据功能的实现。为了更精准学习该 pyqtgraph 模块功能,我们将参考官方给出的实例来边学边练。
1. pyqtgraph 简介
1.1 pyqtgraph 特点
关于 pyqtgraph 与 Matplotlib 的对比,大致要点如下:
pyqtgraph 在画图方面不如 Matplotlib 功能完整和成熟,但运行更快 Matplotlib 旨在绘制高质量图像,pyqtgraph 则主要面向数据抓取和数据分析的应用 相比 Matplotlib,pyqtgraph 对 python 和 qt 编程更亲和 pyqtgraph 具备更好的图像交互、3D展示等
1.2 pyqtgraph 安装
一般配合 PyQt5 使用,这些都要预先安装好,我们这里只提 pyqtgraph 相关:
pip install pyqtgraph
1.3 pyqtgraph 实例全集
官方专门给出了一个实例集合,包含了展示与源码,非常方便学习,通过以下代码来运行:
import pyqtgraph.examples
pyqtgraph.examples.run()
运行后,会出现如下 GUI 界面
今天我们主要关注实时绘制数据,找到左侧目录中的 "Scrolling plots",单击右侧可以看到源码
双击或者点击下方的 "Run Example" 便可展示运行效果:
特定截图:
2. 实时绘制学习
结合着实例代码和演示效果,我们可以看到有如下不同实时展示模式:
模式1: 从 0 开始固定 x 轴数值范围,数据在该范围内向左移动展示 模式2: 数据带着 x 轴坐标一起向左移动展示 模式3: 固定 x 轴数值右侧范围到 0,数据左移展示 模式4: 左侧固定从 0 开始,数据累积展示 模式5: 数据范围右侧截止到 0,但仍可查看大于 0 范围
2.1 模式1: 固定 x 范围,左移展示数据
2.1.1 模式1效果

2.1.2 实例1代码
我们可以在实例汇总的代码中将该部分代码抽离出来,大致如下:
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
win = pg.GraphicsLayoutWidget(show=True)
win.setWindowTitle('Scrolling Plots Mode 1')
p1 = win.addPlot()
data1 = np.random.normal(size=300)
curve1 = p1.plot(data1)
def update1():
global data1, ptr1
data1[:-1] = data1[1:] # shift data in the array one sample left
# (see also: np.roll)
data1[-1] = np.random.normal()
curve1.setData(data1)
timer = pg.QtCore.QTimer()
timer.timeout.connect(update1)
timer.start(50)
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
注意,模式 1 中实时绘制效果的实现,是通过将数据列表中的数据整体左移实现的,关键语句就是 data1[:-1] = data1[1:],再通过计时器来绑定该左移数据的函数,最终达到了展示中的数据动态展示效果。
2.1.3 写成 PlotWidget 形式
总结下模式 1 的原理:x 坐标数据不变化,对应的 y 数据设置个左移变换的函数,计时器信号绑定该左移数据的函数,把 y 数据能实时设置到图中即可。
实例 1 中绘制图的写法比较少见,通常应用是通过 pyqtgraph.PlotWidget.plot() 来实现在控件中作图再添加到 GUI 控件中,所以我们将采用 PlotWidget 的写法来实现模式1的绘制,代码如下:
__author__ = 'Ted'
from PyQt5.Qt import *
from pyqtgraph import PlotWidget
from PyQt5 import QtCore
import numpy as np
import pyqtgraph as pq
class Window(QWidget):
def __init__(self):
super().__init__()
# 设置下尺寸
self.resize(600,600)
# 添加 PlotWidget 控件
self.plotWidget_ted = PlotWidget(self)
# 设置该控件尺寸和相对位置
self.plotWidget_ted.setGeometry(QtCore.QRect(25,25,550,550))
# 仿写 mode1 代码中的数据
# 生成 300 个正态分布的随机数
self.data1 = np.random.normal(size=300)
self.curve1 = self.plotWidget_ted.plot(self.data1, name="mode1")
# 设定定时器
self.timer = pq.QtCore.QTimer()
# 定时器信号绑定 update_data 函数
self.timer.timeout.connect(self.update_data)
# 定时器间隔50ms,可以理解为 50ms 刷新一次数据
self.timer.start(50)
# 数据左移
def update_data(self):
self.data1[:-1] = self.data1[1:]
self.data1[-1] = np.random.normal()
# 数据填充到绘制曲线中
self.curve1.setData(self.data1)
if __name__ == '__main__':
import sys
# PyQt5 程序固定写法
app = QApplication(sys.argv)
# 将绑定了绘图控件的窗口实例化并展示
window = Window()
window.show()
# PyQt5 程序固定写法
sys.exit(app.exec())
我们在自己写的代码中重新设置了下窗口尺寸位置,数据还是按照实例中的写法来完成的。
2.1.4 自写模式1效果

2.2 数据随 x 轴一起左移
2.2.1 模式2效果

2.2.2 实例2代码
该模式代码抽离出来大致如下:
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
win = pg.GraphicsLayoutWidget(show=True)
win.setWindowTitle('pyqtgraph example: Scrolling Plots')
p2 = win.addPlot()
data1 = np.random.normal(size=300)
curve2 = p2.plot(data1)
ptr1 = 0
def update1():
global data1, ptr1
data1[:-1] = data1[1:] # shift data in the array one sample left
data1[-1] = np.random.normal()
ptr1 += 1
curve2.setData(data1)
curve2.setPos(ptr1, 0)
timer = pg.QtCore.QTimer()
timer.timeout.connect(update1)
timer.start(50)
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
对比模式1代码,此部分多了个 curve2.setPos(ptr1, 0),通过 Qt 官网中搜索查阅,setPos(x,y) 是将原点设置到 (x,y):
❝Sets the position of the item to pos, which is in parent coordinates. For items with no parent, pos is in scene coordinates.
❞
The position of the item describes its origin (local coordinate (0, 0)) in parent coordinates.
这样我们可以大致理解为,通过设置坐标系相对原点位置来产生 x 轴移动的效果。
2.2.3 写成 PlotWidget 形式
总结下模式 2 的原理:y 数据与模式1相同,设置左移变换的函数,计时器信号绑定该左移数据的函数,把 y 数据能实时设置到图中;x 数据则通过 setPos() 函数随着 y 的变化同步进行设置,产生 x 轴同步移动的效果。
我们继续采用 PlotWidget 的写法来实现模式2的绘制,在模式1基础上添加几行代码即可,为作区分我们把曲线定义为 curve2:
__author__ = 'Ted'
from PyQt5.Qt import *
from pyqtgraph import PlotWidget
from PyQt5 import QtCore
import numpy as np
import pyqtgraph as pq
class Window(QWidget):
def __init__(self):
super().__init__()
# 设置下尺寸
self.resize(600,600)
# 添加 PlotWidget 控件
self.plotWidget_ted = PlotWidget(self)
# 设置该控件尺寸和相对位置
self.plotWidget_ted.setGeometry(QtCore.QRect(25,25,550,550))
# 仿写 mode1 代码中的数据
# 生成 300 个正态分布的随机数
self.data1 = np.random.normal(size=300)
self.curve2 = self.plotWidget_ted.plot(self.data1, name="mode2")
self.ptr1 = 0
# 设定定时器
self.timer = pq.QtCore.QTimer()
# 定时器信号绑定 update_data 函数
self.timer.timeout.connect(self.update_data)
# 定时器间隔50ms,可以理解为 50ms 刷新一次数据
self.timer.start(50)
# 数据左移
def update_data(self):
self.data1[:-1] = self.data1[1:]
self.data1[-1] = np.random.normal()
# 数据填充到绘制曲线中
self.curve2.setData(self.data1)
# x 轴记录点
self.ptr1 += 1
# 重新设定 x 相关的坐标原点
self.curve2.setPos(self.ptr1,0)
if __name__ == '__main__':
import sys
# PyQt5 程序固定写法
app = QApplication(sys.argv)
# 将绑定了绘图控件的窗口实例化并展示
window = Window()
window.show()
# PyQt5 程序固定写法
sys.exit(app.exec())
我们在自己写的代码中重新设置了下窗口尺寸位置,数据还是按照实例中的写法来完成的。
2.2.4 自写模式1效果

3. 小结
今天先只简单整理这两个较简单的实时绘制模式,给定的代码中数据是用的随机正态分布数据,我们结合着模式 1 和 2 的实例代码来分析其原理算法来仿写了常用版本的代码。
掌握模式 1 和模式 2 的用法后,我们便可以对更多的数据来进行动态展示,比如 CPU 占用率、股票实时价格等,配合着 PyQt5 的 GUI 图形界面,那么完全可以用 Python 来写出看着高大上的数据可视化界面了,这个后续我们再继续研究。
