How to add a live (updating) graph to my PyQt5 Window using Serial?

ぃ、小莉子 提交于 2021-02-11 12:34:07

问题


I am writting a program that's supposed to receive 4 random values from my Arduino every second and create an updating bar chart with them. I then want to create a PyQt5 Window to show that bar chart. I was sucessful at reading the values coming from my Arduino and using them to create the bar chart. However, since I'm a beginner when it comes to GUI/Arduino, I have no clue how to add that bar chart to my PyQt Window. I'd appreciate if you could help me with that.

Here's my Arduino code:

void setup() {
  Serial.begin(115200);
}

void loop() {
  float nb1 = random(0, 100);
  float nb2 = random(0, 100);
  float nb3 = random(0, 100);
  float nb4 = random(0, 100);

  Serial.print(nb1);
  Serial.print(" , ");
  Serial.print(nb2);
  Serial.print(" , ");
  Serial.print(nb3);
  Serial.print(" , ");
  Serial.println(nb4);

  delay(1000);    
}

and here's what the graph should look like (the titles and labels are in french, but don't mind that):

import serial
import numpy
import matplotlib.pyplot as plt
from drawnow import *

plt.ion()

def make_figure():
    plt.ylim(0, 100)
    plt.title("Titre que tu veux")
    plt.tick_params(axis = 'x', which = 'both', bottom = False, top = False, labelbottom = False)
    plt.hlines(16, -0.5, 0.5, color = 'g')
    plt.ylabel("Nom de la variable")
    plt.bar(0, nb1, color = 'b')

    plt.bar(2, nb2, color = 'b')
    plt.hlines(42, 1.5, 2.5, color = 'g')

    plt.bar(4, nb3, color = 'b')
    plt.hlines(32, 3.5, 4.5, color = 'g')

    plt.bar(6, nb4, color = 'b')
    plt.hlines(80, 5.5, 6.5, color = 'g')



with serial.Serial('COM3', baudrate = 115200) as ser:
    while True:
        while (ser.inWaiting() == 0):
            pass
        string_received = ser.readline().decode().strip('\r\n')
        dataArray = string_received.split(" , ")
        nb1 = float(dataArray[0])
        nb2 = float(dataArray[1])
        nb3 = float(dataArray[2])
        nb4 = float(dataArray[3])

        drawnow(make_figure)
        plt.pause(0.000001)

Now, here is what my Window should look like, but replace the static chart with a dynamic one (don't mind the buttons, they're not meant to do anything right now):


import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
from matplotlib.figure import Figure


class MplCanvas(FigureCanvasQTAgg):
    def __init__(self, parent=None, width=5, height=4, dpi=100):
        fig = Figure(figsize=(width, height), dpi=dpi)
        self.axes = fig.add_subplot(111)
        super(MplCanvas, self).__init__(fig)

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("Titre que tu veux")
        MainWindow.resize(1510, 600)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.pushgetMax = QtWidgets.QPushButton(self.centralwidget)
        self.pushgetMax.setGeometry(QtCore.QRect(1240, 30, 93, 28))
        self.pushgetMax.setObjectName("pushgetMax")
        self.pushAll = QtWidgets.QPushButton(self.centralwidget)
        self.pushAll.setGeometry(QtCore.QRect(1350, 30, 93, 28))
        self.pushAll.setObjectName("pushAll")
        self.pushButton1 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton1.setGeometry(QtCore.QRect(1240, 70, 93, 28))
        self.pushButton1.setObjectName("pushButton1")
        self.pushButton2 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton2.setGeometry(QtCore.QRect(1350, 70, 93, 28))
        self.pushButton2.setObjectName("pushButton2")
        self.pushButton3 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton3.setGeometry(QtCore.QRect(1240, 110, 93, 28))
        self.pushButton3.setObjectName("pushButton3")
        self.pushButton_6 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_6.setGeometry(QtCore.QRect(1350, 110, 93, 28))
        self.pushButton_6.setObjectName("pushButton_6")
        self.horizontalLayoutWidget = QtWidgets.QWidget(self.centralwidget)
        self.horizontalLayoutWidget.setGeometry(QtCore.QRect(20, 10, 1191, 531))
        self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget")
        self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget)
        self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
        self.horizontalLayout.setObjectName("horizontalLayout")


        barre1 = MplCanvas(self, width=5, height=4, dpi=100)
        barre1.axes.set_ylim([0, 100])
        barre1.axes.set_title('Titre 1')
        barre1.axes.tick_params(axis = 'x', which = 'both', bottom = False, top = False, labelbottom = False)
        barre1.axes.bar(0, 22, color = 'b')
        barre1.axes.hlines(25, -0.5, 0.5, color = 'g')

        barre1.axes.bar(2, 50, color = 'b')
        barre1.axes.hlines(60, 1.5, 2.5, color = 'g')

        barre1.axes.bar(4, 32, color = 'b')
        barre1.axes.hlines(50, 3.5, 4.5, color = 'g')

        barre1.axes.bar(6, 81, color = 'b')
        barre1.axes.hlines(70, 5.5, 6.5, color = 'g')


        self.horizontalLayout.addWidget(barre1)


        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 1510, 26))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

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



    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.pushgetMax.setText(_translate("MainWindow", "GetMax"))
        self.pushAll.setText(_translate("MainWindow", "All"))
        self.pushButton1.setText(_translate("MainWindow", "1"))
        self.pushButton2.setText(_translate("MainWindow", "2"))
        self.pushButton3.setText(_translate("MainWindow", "3"))
        self.pushButton_6.setText(_translate("MainWindow", "4"))


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())


回答1:


If you are going to use PyQt5 then forget about pyplot since you have to use the respective canvas. On the other hand do not use pyserial since it is better to use QSerialPort since it uses the Qt eventloop.

import sys

from PyQt5 import QtCore, QtGui, QtWidgets, QtSerialPort

from matplotlib.backends.backend_qt5agg import FigureCanvas
from matplotlib.figure import Figure


class SerialPortManager(QtCore.QObject):
    dataChanged = QtCore.pyqtSignal(list)

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

        self._serial = QtSerialPort.QSerialPort(baudRate=115200)
        self.serial.setPortName("COM3")
        self.serial.readyRead.connect(self.on_ready_read)

    @property
    def serial(self):
        return self._serial

    def start(self):
        self.serial.open(QtCore.QIODevice.ReadOnly)

    @QtCore.pyqtSlot()
    def on_ready_read(self):
        if self.serial.canReadLine():
            line = self.serial.readLine().data().decode()
            values = line.strip().split(",")
            try:
                data = list(map(float, values))
            except ValueError as e:
                print("error", e)
            else:
                self.dataChanged.emit(data)


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)

        fig = Figure(figsize=(5, 4), dpi=100)
        self.canvas = FigureCanvas(fig)

        self.max_button = QtWidgets.QPushButton(self.tr("GetMax"))
        self.all_button = QtWidgets.QPushButton(self.tr("All"))
        self.one_button = QtWidgets.QPushButton(self.tr("1"))
        self.two_button = QtWidgets.QPushButton(self.tr("2"))
        self.three_button = QtWidgets.QPushButton(self.tr("3"))
        self.four_button = QtWidgets.QPushButton(self.tr("4"))

        central_widget = QtWidgets.QWidget()
        self.setCentralWidget(central_widget)
        hlay = QtWidgets.QHBoxLayout(central_widget)
        hlay.addWidget(self.canvas, stretch=1)

        grid_layout = QtWidgets.QGridLayout()
        grid_layout.addWidget(self.max_button, 0, 0)
        grid_layout.addWidget(self.all_button, 0, 1)
        grid_layout.addWidget(self.one_button, 1, 0)
        grid_layout.addWidget(self.two_button, 1, 1)
        grid_layout.addWidget(self.three_button, 2, 0)
        grid_layout.addWidget(self.four_button, 2, 1)

        vlay = QtWidgets.QVBoxLayout()
        vlay.addLayout(grid_layout)
        vlay.addStretch()

        hlay.addLayout(vlay)

        self.axes = self.canvas.figure.add_subplot(111)
        self.axes.set_ylim([0, 100])
        self.axes.set_title("Titre 1")
        self.axes.tick_params(
            axis="x", which="both", bottom=False, top=False, labelbottom=False
        )

        self.axes.hlines(25, -0.5, 0.5, color="g")
        self.axes.hlines(60, 1.5, 2.5, color="g")
        self.axes.hlines(50, 3.5, 4.5, color="g")
        self.axes.hlines(70, 5.5, 6.5, color="g")

        self.containers = []

        self.update_bars([0, 0, 0, 0])

        self.resize(640, 480)

    @QtCore.pyqtSlot(list)
    def update_bars(self, values):
        if len(values) == 4:
            [c.remove() for c in self.containers]
            self.containers = []
            for index, value in zip((0, 2, 4, 6), values):
                c = self.axes.bar(index, value, color="b")
                self.containers.append(c)
            self.canvas.draw()


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)

    w = MainWindow()
    w.show()

    manager = SerialPortManager()
    manager.dataChanged.connect(w.update_bars)
    manager.start()

    sys.exit(app.exec_())


来源:https://stackoverflow.com/questions/62031226/how-to-add-a-live-updating-graph-to-my-pyqt5-window-using-serial

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!