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秒样本:
- 50s-60s 之间的数据将在当前 window
中查看
- 30s-50s之间的数据可以通过鼠标向后拖动x轴
查看
- 0-30s之间的数据将不会显示
我的代码
我目前的代码如下,它只能显示最新的 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_())
它可能看起来像这样:
我在使用 pygtgraph 滚动图时遇到问题
预期结果
预期结果与pyqtgraph-examples-scrolling plots-plot5
非常相似X值是时间,可以通过一个简单的函数生成。 Y 值是随机值。
每 10 秒样本作为一个块,每个图最多可以有。 30 秒样本,这意味着 3 个块。 当前图window只显示最近10秒的样本
例如,现在共有60秒样本:
- 50s-60s 之间的数据将在当前 window 中查看
- 30s-50s之间的数据可以通过鼠标向后拖动x轴 查看
- 0-30s之间的数据将不会显示
我的代码
我目前的代码如下,它只能显示最新的 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_())
它可能看起来像这样: