Python 如何实时绘制数据

共 6178字,需浏览 13分钟

 ·

2020-08-04 12:55

提到 GUI 绘图,大家可能第一反应是 OpenGL 和 Matplotlib,但其实基于 Qt 平台还有个功能强大的 pyqtgraph 绘图库,不仅支持丰富的图形种类,还能实时更新绘图数据并进行交互式操作。

不同于网上其他文章或代码讲解,今天我们集中只关注实时绘制数据功能的实现。为了更精准学习该 pyqtgraph 模块功能,我们将参考官方给出的实例来边学边练。

1. pyqtgraph 简介

1.1 pyqtgraph 特点

关于 pyqtgraph 与 Matplotlib 的对比,大致要点如下:

  1. pyqtgraph 在画图方面不如 Matplotlib 功能完整和成熟,但运行更快
  2. Matplotlib 旨在绘制高质量图像,pyqtgraph 则主要面向数据抓取和数据分析的应用
  3. 相比 Matplotlib,pyqtgraph 对 python 和 qt 编程更亲和
  4. 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效果

模式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 != 1or 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效果

自写模式1效果

2.2  数据随 x 轴一起左移

2.2.1 模式2效果

模式 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 != 1or 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效果

自写模式2

3. 小结

今天先只简单整理这两个较简单的实时绘制模式,给定的代码中数据是用的随机正态分布数据,我们结合着模式 1 和 2 的实例代码来分析其原理算法来仿写了常用版本的代码。

掌握模式 1 和模式 2 的用法后,我们便可以对更多的数据来进行动态展示,比如 CPU 占用率、股票实时价格等,配合着 PyQt5 的 GUI 图形界面,那么完全可以用 Python 来写出看着高大上的数据可视化界面了,这个后续我们再继续研究。


浏览 34
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报