尝试使用 PyQt5 获取 GUI 的实时绘图

Trying get a live plot for a GUI with PyQt5

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

在下面的图片中,我将使用一张图片作为图表的背景。对于 Dyno,收集的重要信息是扭矩和马力。这些我已经用我的 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

GUI 图片

第一件事是将 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()