专栏首页用户6811391的专栏Python 如何实时绘制数据

Python 如何实时绘制数据

提到 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 != 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效果

自写模式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 != 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效果

自写模式2

3. 小结

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

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

本文分享自微信公众号 - TTTEED(TEDxPY),作者:TEDxPY

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-08-01

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Python 助力词频统计自动化

    上周除了爬虫的问题,还尝试写了份词频统计的代码。最初听到关于词频的需求描述,有点懵。在了解其具体操作流程后发现:类似的需求可能涉及各行各业,但本质只是 Word...

    TTTEED
  • 白嫖和付费

    几天前看到个 1 分钱学 Redis 的课程,近乎白嫖的价格,买完发现所谓的课程毫无质量可言,拉的群组是另一门 Java 开发课程的推广群。

    TTTEED
  • 你还在用 format 格式化字符串?

    提到格式化字符串,我想大家应该都要磨拳擦掌了,但是 Python 3.6 带来了一种更为简洁,更加 Pythonic的方式,今天就带大家见识一下~

    TTTEED
  • 接口测试 | 24 requests + unittest集成你的接口测试

    概述 本文就如何结合requests、unittest进行实例演示,如果你还不了解unittest、PO模型,请翻阅公众号前期发布的unittest专题和PO模...

    苦叶子
  • python pyqt5 pandas处理数据

    """ Module implementing MainWindow. """

    用户5760343
  • 接口测试 | 24 requests + unittest集成你的接口测试

    概述 本文就如何结合requests、unittest进行实例演示,如果你还不了解unittest、PO模型,请翻阅公众号前期发布的unittest专题和PO模...

    苦叶子
  • Python实现翻译小工具

    一、背景 利用Requests模块获取有道词典web页面的post信息,BeautifulSoup来获取需要的内容,通过tkinter模块生成gui界面。

    py3study
  • 爬取数据不保存,就是耍流氓 !

    OK,通过前面两篇文章《爬虫利器初体验(1)》《听说你的爬虫又被封了?(2)》,我们初体验也过了,爬虫代码健壮性也升级为 PLUS 了。都分析到这个地步了,是不...

    小小詹同学
  • Scrapy框架系列--数据不保存,就是耍流氓(3)

    OK,通过签名两篇文章《爬虫利器初体验(1)》《听说你的爬虫又被封了?(2)》,我们初体验也过了,爬虫代码健壮性也升级为 PLUS 了。都分析到这个地步了,是不...

    1480
  • 实战项目:飞机大战

    运行程序,英雄飞机出现在屏幕底边的中央区域,飞机中央每隔一秒会自动发射一枚子弹。随着游戏的开始,敌机会在屏幕上方随机不定时的出现在屏幕内向下飞行。英雄飞机需要通...

    佛系编程人

扫码关注云+社区

领取腾讯云代金券