尝试使用 PyQt5 获得 GUI 的实时图

Posted

技术标签:

【中文标题】尝试使用 PyQt5 获得 GUI 的实时图【英文标题】:Trying get a live plot for a GUI with PyQt5 【发布时间】:2018-04-13 03:31:27 【问题描述】:

现在通过阅读和提问,我已经可以在 LCD 上显示数值,但现在想制作一个图表。

在下面的图片中,我将有一张图片作为图表的背景。对于测功机,收集的重要信息是扭矩和马力。这些我是用我的 Python 3.5 代码计算的,数据是通过使用 arduino 收集的。

对于我的图表,我实际上想在数据输入的同时绘制两条线。我想同时绘制扭矩和马力。两者都与使用 Dyno 的时间相比。然而,这可能很难,因为它们需要用不同的 y 轴绘制。从我一直在阅读的内容来看,使用 pyqtGraph 是这项工作的最佳选择,但由于我对这类工作的经验,我真的不知道该怎么做。

下面是我根据我发现的一些东西尝试运行的代码。运行它不会使我的代码出错,但是它也不会与图形区域交互。我试图让它以与 LCD 工作类似的方式工作,但我仍然没有任何工作。

"""
SCSU DYNO GUI PROGRAM

created 10/20/2017
"""


import sys
import time
import csv
import numpy as np
import warnings
import serial
import serial.tools.list_ports
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QThread,QTimer, pyqtSignal
from PyQt5.QtWidgets import QMessageBox,QWidget, QApplication
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
import random
from DynoTest1 import Ui_DynoTest1


__author__ = 'Matt Munn'

class GetData(QThread):
    dataChanged = pyqtSignal(float, float, float, float, float, float, float, float)

    #Distance = 0.5 #This is dependent on the lever arm.

    def __init__(self, parent=None):
        QThread.__init__(self, parent)

        arduino_ports = [  # automatically searches for an Arduino and selects the port it's on
            p.device
            for p in serial.tools.list_ports.comports()
            if 'Arduino' in p.description
        ]

        if not arduino_ports:
            raise IOError("No Arduino found - is it plugged in? If so, restart computer.")
        if len(arduino_ports) > 1:
            warnings.warn('Multiple Arduinos found - using the first')
        self.Arduino = serial.Serial(arduino_ports[0], 9600, timeout=1)

    def __del__(self):  # part of the standard format of a QThread
        self.wait()

    def run(self):  # also a required QThread function, the working part
        self.Arduino.close()
        self.Arduino.open()

        self.Arduino.flush()
        self.Arduino.reset_input_buffer()
        start_time = time.time()

        Distance = 0.5 #This is dependent on the lever arm.
        Max_RPM = 0
        Max_HorsePower = 0
        Max_Torque = 0

        while True:
            while self.Arduino.inWaiting() == 0:
                pass
            try:
                data = self.Arduino.readline()
                dataarray = data.decode().rstrip().split(',')
                self.Arduino.reset_input_buffer()
                Force = round(float(dataarray[0]), 3)
                RPM = round(float(dataarray[1]), 3)
                if Max_RPM < RPM:
                    Max_RPM = RPM
                Torque = round(Force * Distance, 3)
                if Max_Torque < Torque:
                    Max_Torque = Torque
                HorsePower = round(Torque * RPM / 5252, 3)
                if Max_HorsePower < HorsePower:
                    Max_HorsePower = HorsePower
                Run_Time = round(time.time() - start_time, 3)
                print(Force, 'Grams', ",", RPM, 'RPMs', ",", Torque, "ft-lbs", ",", HorsePower, "hp", Run_Time,
                      "Time Elasped")
                self.dataChanged.emit(Force, RPM, Max_RPM, Torque, Max_Torque, HorsePower, Max_HorsePower, Run_Time)
            except (KeyboardInterrupt, SystemExit, IndexError, ValueError):
                pass
class GUI(QWidget, Ui_DynoTest1):


    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.setupUi(self)
        self.thread = GetData(self)
        self.thread.dataChanged.connect(self.onDataChanged)
        self.thread.start()
    """
        layout = QtGui.QHBoxLayout()
        self.plot = pg.PlotWidget()
        layout.addWidget(self.plot)
        self.setLayout(layout)

    def plotter(self):

        self.data = [0]
        self.curve = self.plot.getPlotItem().plot()
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.updater)
        self.timer.start(0)

    def updater(self):
        self.data.append(self.data[-1]+0.2*(0.5-random.random()))
        self.curve.setData(self.data)
    """
    def onDataChanged(self, Force, RPM, Max_RPM, Torque,Max_Torque, HorsePower, Max_HorsePower, Run_Time):
        self.lcdNumber.display(Max_RPM)
        self.lcdNumber_2.display(Max_Torque)
        self.lcdNumber_3.display(Max_HorsePower)
        self.lcdNumber_4.display(RPM)
        self.lcdNumber_5.display(Torque)
        self.lcdNumber_6.display(HorsePower)
        self.lcdNumber_7.display(Run_Time)
        #self.graphicsView.display(Tourque,Run_Time)


if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    Dyno = GUI()
    Dyno.show()
    sys.exit(app.exec_())

这是 QTDesigner 生成的代码。

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'dynotest1.ui'
#
# Created by: PyQt5 UI code generator 5.9
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_DynoTest1(object):
    def setupUi(self, DynoTest1):
        DynoTest1.setObjectName("DynoTest1")
        DynoTest1.resize(1001, 695)
        self.verticalLayout_4 = QtWidgets.QVBoxLayout(DynoTest1)
        self.verticalLayout_4.setObjectName("verticalLayout_4")
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.verticalLayout_3 = QtWidgets.QVBoxLayout()
        self.verticalLayout_3.setObjectName("verticalLayout_3")
        self.gridLayout = QtWidgets.QGridLayout()
        self.gridLayout.setObjectName("gridLayout")
        self.pushButton_2 = QtWidgets.QPushButton(DynoTest1)
        self.pushButton_2.setObjectName("pushButton_2")
        self.gridLayout.addWidget(self.pushButton_2, 1, 0, 1, 1)
        self.pushButton_4 = QtWidgets.QPushButton(DynoTest1)
        self.pushButton_4.setObjectName("pushButton_4")
        self.gridLayout.addWidget(self.pushButton_4, 1, 1, 1, 1)
        self.pushButton_3 = QtWidgets.QPushButton(DynoTest1)
        self.pushButton_3.setObjectName("pushButton_3")
        self.gridLayout.addWidget(self.pushButton_3, 0, 1, 1, 1)
        self.pushButton = QtWidgets.QPushButton(DynoTest1)
        self.pushButton.setObjectName("pushButton")
        self.gridLayout.addWidget(self.pushButton, 0, 0, 1, 1)
        self.verticalLayout_3.addLayout(self.gridLayout)
        self.label_3 = QtWidgets.QLabel(DynoTest1)
        self.label_3.setObjectName("label_3")
        self.verticalLayout_3.addWidget(self.label_3)
        self.label_2 = QtWidgets.QLabel(DynoTest1)
        self.label_2.setObjectName("label_2")
        self.verticalLayout_3.addWidget(self.label_2)
        self.label = QtWidgets.QLabel(DynoTest1)
        self.label.setObjectName("label")
        self.verticalLayout_3.addWidget(self.label)
        self.horizontalLayout.addLayout(self.verticalLayout_3)
        self.verticalLayout = QtWidgets.QVBoxLayout()
        self.verticalLayout.setObjectName("verticalLayout")
        self.label_5 = QtWidgets.QLabel(DynoTest1)
        self.label_5.setObjectName("label_5")
        self.verticalLayout.addWidget(self.label_5)
        self.lcdNumber_4 = QtWidgets.QLCDNumber(DynoTest1)
        self.lcdNumber_4.setFrameShape(QtWidgets.QFrame.Box)
        self.lcdNumber_4.setFrameShadow(QtWidgets.QFrame.Raised)
        self.lcdNumber_4.setLineWidth(1)
        self.lcdNumber_4.setSmallDecimalPoint(True)
        self.lcdNumber_4.setDigitCount(5)
        self.lcdNumber_4.setMode(QtWidgets.QLCDNumber.Dec)
        self.lcdNumber_4.setSegmentStyle(QtWidgets.QLCDNumber.Filled)
        self.lcdNumber_4.setProperty("value", 0.0)
        self.lcdNumber_4.setObjectName("lcdNumber_4")
        self.verticalLayout.addWidget(self.lcdNumber_4)
        self.lcdNumber_5 = QtWidgets.QLCDNumber(DynoTest1)
        font = QtGui.QFont()
        font.setBold(False)
        font.setWeight(50)
        self.lcdNumber_5.setFont(font)
        self.lcdNumber_5.setSmallDecimalPoint(True)
        self.lcdNumber_5.setObjectName("lcdNumber_5")
        self.verticalLayout.addWidget(self.lcdNumber_5)
        self.lcdNumber_6 = QtWidgets.QLCDNumber(DynoTest1)
        self.lcdNumber_6.setSmallDecimalPoint(True)
        self.lcdNumber_6.setObjectName("lcdNumber_6")
        self.verticalLayout.addWidget(self.lcdNumber_6)
        self.horizontalLayout.addLayout(self.verticalLayout)
        self.verticalLayout_2 = QtWidgets.QVBoxLayout()
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.label_6 = QtWidgets.QLabel(DynoTest1)
        self.label_6.setObjectName("label_6")
        self.verticalLayout_2.addWidget(self.label_6)
        self.lcdNumber = QtWidgets.QLCDNumber(DynoTest1)
        self.lcdNumber.setSmallDecimalPoint(True)
        self.lcdNumber.setObjectName("lcdNumber")
        self.verticalLayout_2.addWidget(self.lcdNumber)
        self.lcdNumber_2 = QtWidgets.QLCDNumber(DynoTest1)
        self.lcdNumber_2.setSmallDecimalPoint(True)
        self.lcdNumber_2.setObjectName("lcdNumber_2")
        self.verticalLayout_2.addWidget(self.lcdNumber_2)
        self.lcdNumber_3 = QtWidgets.QLCDNumber(DynoTest1)
        self.lcdNumber_3.setSmallDecimalPoint(True)
        self.lcdNumber_3.setObjectName("lcdNumber_3")
        self.verticalLayout_2.addWidget(self.lcdNumber_3)
        self.horizontalLayout.addLayout(self.verticalLayout_2)
        self.horizontalLayout_2.addLayout(self.horizontalLayout)
        self.verticalLayout_5 = QtWidgets.QVBoxLayout()
        self.verticalLayout_5.setObjectName("verticalLayout_5")
        self.label_7 = QtWidgets.QLabel(DynoTest1)
        self.label_7.setObjectName("label_7")
        self.verticalLayout_5.addWidget(self.label_7)
        self.lcdNumber_7 = QtWidgets.QLCDNumber(DynoTest1)
        self.lcdNumber_7.setObjectName("lcdNumber_7")
        self.verticalLayout_5.addWidget(self.lcdNumber_7)
        self.horizontalLayout_2.addLayout(self.verticalLayout_5)
        self.verticalLayout_4.addLayout(self.horizontalLayout_2)
        self.graphicsView = QtWidgets.QGraphicsView(DynoTest1)
        self.graphicsView.setStyleSheet("border-image: url(:/newPrefix/husky_head5.png);")
        self.graphicsView.setLineWidth(1)
        self.graphicsView.setObjectName("graphicsView")
        self.verticalLayout_4.addWidget(self.graphicsView)

        self.retranslateUi(DynoTest1)
        QtCore.QMetaObject.connectSlotsByName(DynoTest1)

    def retranslateUi(self, DynoTest1):
        _translate = QtCore.QCoreApplication.translate
        DynoTest1.setWindowTitle(_translate("DynoTest1", "DynoTest1"))
        self.pushButton_2.setText(_translate("DynoTest1", "Pause"))
        self.pushButton_4.setText(_translate("DynoTest1", "Print"))
        self.pushButton_3.setText(_translate("DynoTest1", "Stop"))
        self.pushButton.setText(_translate("DynoTest1", "Start"))
        self.label_3.setText(_translate("DynoTest1", "<html><head/><body><p align=\"center\"><span style=\" font-size:18pt; font-weight:600;\">RPMs</span></p></body></html>"))
        self.label_2.setText(_translate("DynoTest1", "<html><head/><body><p align=\"center\"><span style=\" font-size:18pt; font-weight:600;\">Torque (ft-lbs)</span></p></body></html>"))
        self.label.setText(_translate("DynoTest1", "<html><head/><body><p align=\"center\"><span style=\" font-size:18pt; font-weight:600;\">Horse Power</span></p></body></html>"))
        self.label_5.setText(_translate("DynoTest1", "<html><head/><body><p align=\"center\"><span style=\" font-size:18pt; font-weight:600;\">Now</span></p></body></html>"))
        self.label_6.setText(_translate("DynoTest1", "<html><head/><body><p align=\"center\"><span style=\" font-size:18pt; font-weight:600;\">Max</span></p></body></html>"))
        self.label_7.setText(_translate("DynoTest1", "<html><head/><body><p align=\"center\"><span style=\" font-size:18pt; font-weight:600;\">Run Time</span></p><p align=\"center\"><span style=\" font-size:18pt; font-weight:600;\">(Seconds)</span></p></body></html>"))

import Resource_rc

图形用户界面图片

【问题讨论】:

【参考方案1】:

第一件事是将 PlotWidget 添加到 GUI。为此我们使用QGraphicsView(实际上可以使用任何小部件),在QGraphicsView中我们添加一个布局,在这个布局中我们添加PlotWidget。

layout = QHBoxLayout()
self.plot = pg.PlotWidget()
layout.addWidget(self.plot)
self.graphicsView.setLayout(layout)

然后创建项目,并且由于您希望在同一个图中有 2 个轴,我们以以下 link 中的代码为例。

在您要使用的示例中,使用计时器来更新数据,但在这种情况下,每次调用 onDataChanged 方法时,时钟都必须为它读取数据。为了获得视图,我们创建了一个仅存储和显示最后 50 个元素的过程。

代码:

class GUI(QWidget, Ui_DynoTest1):
    def __init__(self, parent=None):
        [...]
        self.thread.start()
        self.torque = []
        self.horse_power = []

        layout = QHBoxLayout()
        self.plot = pg.PlotWidget()
        layout.addWidget(self.plot)
        self.graphicsView.setLayout(layout)

        self.p1 = self.plot.plotItem
        self.p1.setLabels(left='Torque')
        self.TorqueCurve = self.p1.plot()
        self.TorqueCurve.setPen(pg.mkPen(color="#ff0000", width=2))

        self.p2 = pg.ViewBox()
        self.HorsePowerCurve = pg.PlotCurveItem()
        self.HorsePowerCurve.setPen(pg.mkPen(QColor(0, 255, 0)), width=2)
        self.p2.addItem(self.HorsePowerCurve)
        self.p1.scene().addItem(self.p2)
        self.p1.showAxis('right')
        self.p1.getAxis('right').setLabel('HorsePower', color='#0000ff')
        self.p1.getAxis('right').linkToView(self.p2)
        self.p1.vb.sigResized.connect(self.updateViews)

    def updateViews(self):
        self.p2.setGeometry(self.p1.vb.sceneBoundingRect())
        self.p2.linkedViewChanged(self.p1.vb, self.p2.XAxis)

    def onDataChanged(self, Force, RPM, Max_RPM, Torque, Max_Torque, HorsePower, Max_HorsePower, Run_Time):
        [...]
        if len(self.torque) < 50:
            self.torque.append(Torque)
        else:
            self.torque = self.torque[1:] + [Torque]

        if len(self.horse_power) < 50:
            self.horse_power.append(HorsePower)
        else:
            self.horse_power = self.horse_power[1:] + [HorsePower]
        self.TorqueCurve.setData(self.torque)
        self.HorsePowerCurve.setData(self.horse_power)
        self.updateViews()

【讨论】:

由于某种原因,这似乎挂断了Layout = QHBoxLayout()。它是说它是未定义的。我厌倦了将它添加到定义类的行中,但也没有修复它。 from PyQt5.QtWidgets import QMessageBox,QWidget, QApplication 更改为from PyQt5.QtWidgets import * @MatthewEspindolaMunn 如果我的回答对您有帮助,请不要忘记将其标记为正确。 这似乎成功了,但是这张图的背景是黑色的。有没有办法让它成为我放置在图形视图中的图像? 我不记得是否可能,这也是一个坏主意,因为它消耗大量资源。如果您想这样做,我建议您自己进行搜索。我还建议不要在您的问题中添加太多代码或告诉我们您的生活,因为这会分散我们的注意力。

以上是关于尝试使用 PyQt5 获得 GUI 的实时图的主要内容,如果未能解决你的问题,请参考以下文章

如何实时更新 PyQt5 标签?

PyQT5 实时更新图

Gui(pyqt5)滞后很多,然后python崩溃

PyQt5 在不冻结 GUI 的情况下绘制数据的最佳方法 [关闭]

如何在 PyQt5 GUI python 中单击按钮时绘制方形图

将数据从散点图传输到 lineEdit - Matplotlib 和 Pyqt5