pyqtgraph 滚动图:分块绘制,仅显示当前 window 中最新的 10 秒样本

pyqtgraph scrolling plots: plot in chunks, show only latest 10s samples in current window

我在使用 pygtgraph 滚动图时遇到问题

预期结果

预期结果与pyqtgraph-examples-scrolling plots-plot5

非常相似

X值是时间,可以通过一个简单的函数生成。 Y 值是随机值。

每 10 秒样本作为一个块,每个图最多可以有。 30 秒样本,这意味着 3 个块。 当前图window只显示最近10秒的样本

例如,现在共有60秒样本:

我的代码

我目前的代码如下,它只能显示最新的 30s 数据。

import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
import random


win = pg.GraphicsLayoutWidget(show=True)
win.setWindowTitle('Scrolling Plots')

p1 = win.addPlot()
p1.setYRange(0,10)

xVal = [0]
yVal = [0]

def genTime():  # used to generate time
    t = 0
    while True:
        t += np.random.random_sample()
        yield t
        t = np.ceil(t)

xTime = genTime() 

#=====================================================

viewSize = 10   # current window show only latest 10s data
plotSize = 30   # plot 30s data -> 3 chunk
lstCurves = []  # List for Curves

def update():
    global p1, xVal, yVal, lstCurves

    #for c in lstCurves:
    #    c.setPos(xVal[-1], 0)

    i = np.ceil(xVal[-1]) % viewSize  # e.g. when time is 9.2s -> one 10s view size is full, append to curves list as one chunk
    if i == 0:
        curve = p1.plot()
        lstCurves.append(curve)
        xValLast = xVal[-1]
        yValLast = yVal[-1]

        xVal = [xValLast]
        yVal = [yValLast]

        while len(lstCurves) > 3:  # max 3 chunk (30 s)
            p1.removeItem(lstCurves.pop(0))  # remove the oldest 10s
        
    else:
        curve = lstCurves[-1]    # latest 10s curve
        
    xVal.append(next(xTime))
    yVal.append(random.randint(0,9))
    curve.setData(xVal, yVal)
    print(len(lstCurves))

    
#======================================================

timer = pg.QtCore.QTimer()
timer.timeout.connect(update)
timer.start(1000)


## 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_()

问题

我试过用curve.setPos(xx, 0),看起来整条曲线都是沿着x轴移动的,但是X值和Y值的映射关系被破坏了

我也尝试过使用 setXRange()update() 函数中动态更改 x 轴显示范围。但是在这种情况下,我不能再用鼠标拖回x轴查看旧数据了。

我的英文不好,希望你能理解我的问题。任何建议将不胜感激!

问题

您的代码没有按照您的要求执行的原因是:

  • 当您拖动以查看其他块时,您禁用了绘图的自动自动范围,之后,每次要查看新数据时都必须手动拖动绘图。此外,默认情况下,绘图的自动范围将覆盖您正在绘制的所有数据。
  • 当您在 update 函数中使用 setRange() 方法时,每次您向数据添加另一个值时,它都会强制使用该范围。那么拖动就不会如你所愿

你能做什么

嗯,从我的角度来看,使用鼠标拖动来可视化其他数据不是很方便,我建议使用外部小部件来控制要查看的数据范围,例如滑块,滚动条, 旋转框, ... QScrollBar 可以完成这项工作,并且在 GUI 中看起来很美观。

在我的替代解决方案之前,我有一个建议给你:

  • 使用对象来创建你的小部件,生成一个 class 并将变量用作属性,这样你就可以避免使用关键字 global,并且你可以将小部件重用于其他目的.

备选方案

试试这个:

import sys
import random
import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui

class MyApp(QtGui.QWidget):
    def __init__(self):
        QtGui.QWidget.__init__(self)
        ## Creating the Widgets and Layouts
        self.plot_widget = pg.PlotWidget()
        self.layout = QtGui.QVBoxLayout()
        self.sbutton = QtGui.QPushButton("Start / Continue")
        self.ebutton = QtGui.QPushButton("Stop")
        self.timer = pg.QtCore.QTimer()
        self.scroll = QtGui.QScrollBar(QtCore.Qt.Horizontal)
        ## Creating the variables and constants
        self.data = [[0], [random.randint(0,9)]]  ## [xVal, yVal] to create less variables
        self.plot_item = self.plot_widget.plot(*self.data)
        self.plot_widget.setYRange(0, 10)
        self.xTime = self.genTime()
        self.vsize = 10
        self.psize = 30
        ## Building the Widget
        self.setLayout(self.layout)
        self.layout.addWidget(self.sbutton)
        self.layout.addWidget(self.ebutton)
        self.layout.addWidget(self.plot_widget)
        self.layout.addWidget(self.scroll)
        ## Changing some properties of the widgets
        self.plot_widget.setMouseEnabled(x=False, y=False)
        self.ebutton.setEnabled(False)
        self.scroll.setEnabled(False)
        self.scroll.setMaximum(self.psize-self.vsize)
        self.scroll.setValue(self.psize-self.vsize)
        ## Coneccting the signals
        self.sbutton.clicked.connect(self.start)
        self.ebutton.clicked.connect(self.stop)
        self.timer.timeout.connect(self.update)
        self.scroll.valueChanged.connect(self.upd_scroll)

    def genTime(self):  # used to generate time
        t = 0
        while True:
            t += np.random.random_sample()
            yield t
            t = np.ceil(t)

    def upd_scroll(self):
        val = self.scroll.value()
        xmax = np.ceil(self.data[0][-1+self.vsize-self.psize+val])-1
        xmin = xmax-self.vsize
        self.plot_widget.setXRange(xmin, xmax)

    def update(self):
        num = len(self.data[0])
        if num <= self.psize:
            self.plot_item.setData(*self.data)
        else:
            self.plot_item.setData(
                self.data[0][-self.psize:],
                self.data[1][-self.psize:]
            )

        if num == self.vsize:
            self.scroll.setEnabled(True)
        self.data[0].append(next(self.xTime))
        self.data[1].append(random.randint(0,9))
        if num > self.vsize :
            self.upd_scroll()
     
    def start(self):
        self.sbutton.setEnabled(False)
        self.ebutton.setEnabled(True)
        self.timer.start(100)

    def stop(self):
        self.sbutton.setEnabled(True)
        self.ebutton.setEnabled(False)
        self.timer.stop()
        self.upd_scroll()
        
    def closeEvent(self, event):
        self.timer.stop()
        event.accept()

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    window = MyApp()
    window.show()
    sys.exit(app.exec_())

它可能看起来像这样: