Search code examples
pythonuser-interfacepyqtpyqt5pyqtgraph

update PyqtGraph plot in PyQt5


I'm writing a small interface in PyQT5 that has a graph that I use PyQtGraph to create. The graph starts to be drawn by pressing the "Start" button, and at first everything looks fine:enter image description here

But over time and an increase in the number of points, the entire graph shrinks to the width of the screen and becomes not informative: enter image description here

In this regard, there are two questions:

How can I make the window not try to fit the whole graph at once, squeezing it, but rather move behind it, showing the latest data? Now the data comes in once a second and I redraw the graph every time. Is it possible to make it a partial update so that I just pass only new data into it?

from pyqtgraph import PlotWidget
import pyqtgraph
from PyQt5 import QtCore
from PyQt5.QtCore import Qt, QThread, QTimer, QObject, pyqtSignal, QTimer
from PyQt5.QtWidgets import QHBoxLayout, QMainWindow,  QPushButton, QVBoxLayout, QWidget, QApplication
import sys
import random


def get_kl_test():
    choices = [50, 50, 50, 51, 51, 51, 52, 52, 52]
    list = [random.choice(choices) for i in range(11)]
    return list


def get_iopd_test():
    choices = [40, 40, 40, 50, 50, 50, 60, 60, 60]
    return random.choice(choices)


class Graph(PlotWidget):
    def __init__(self):
        super().__init__()
        self.setBackground('white')
        self.addLegend()
        self.showGrid(x=True, y=True)
        self.setYRange(0, 255, padding=0)


class ReadingWorker(QObject):
    update_graph = pyqtSignal(list, list, list, list)

    def __init__(self):
        super().__init__()
        self.time_from_start = 0
        self.time_values = []
        self.green_values = []
        self.blue_values = []
        self.red_values = []

    def run(self):
        self.read()
        self.update_time()

    def read(self):
        ipd_values = get_kl_test()
        iopd_value = get_iopd_test()

        self.green_values.append(ipd_values[0])
        self.blue_values.append(ipd_values[1])
        self.red_values.append(iopd_value)
        self.time_values.append(self.time_from_start)

        self.update_graph.emit(
            self.green_values, self.blue_values, self.red_values, self.time_values)
        QTimer.singleShot(1000, self.read)

    def update_time(self):
        self.time_from_start += 1
        QTimer.singleShot(1000, self.update_time)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.central_widget = QWidget(self)
        self.setGeometry(50, 50, 1300, 700)
        self.setCentralWidget(self.central_widget)
        self.layout_main_window = QVBoxLayout()
        self.central_widget.setLayout(self.layout_main_window)

        # конфигурация тулбара
        self.layout_toolbar = QHBoxLayout()
        self.layout_toolbar.addStretch(1)
        self.btn_start = QPushButton("Старт")
        self.btn_start.clicked.connect(self.start)
        self.layout_toolbar.addWidget(self.btn_start)
        self.layout_main_window.addLayout(self.layout_toolbar)

        # конфигурация графика
        self.graph = Graph()
        self.layout_main_window.addWidget(self.graph)

    def start(self):
        self.reading_thread = QThread(parent=self)
        self.reading_widget = ReadingWorker()
        self.reading_widget.moveToThread(self.reading_thread)
        self.reading_widget.update_graph.connect(self.draw_graph)
        self.reading_thread.started.connect(self.reading_widget.run)
        self.reading_thread.start()

    @QtCore.pyqtSlot(list, list, list, list)
    def draw_graph(self, ipd_1_values, ipd_2_values, iopd_values, time_values):
        
        self.graph.plotItem.clearPlots()
        pen_ipd_1 = pyqtgraph.mkPen(color='green', width=4)
        pen_ipd_2 = pyqtgraph.mkPen(color='blue', width=4, style=Qt.DashDotLine)
        pen_iopd = pyqtgraph.mkPen(color='red', width=4, style=Qt.DashLine)

        line_ipd_1 = self.graph.plotItem.addItem(pyqtgraph.PlotCurveItem(
            time_values, 
            ipd_1_values,
            pen=pen_ipd_1,
            name='1'
        ))
        line_ipd_2 = self.graph.plotItem.addItem(pyqtgraph.PlotCurveItem(
            time_values, 
            ipd_2_values,
            pen=pen_ipd_2,
            name='2'
        ))
        line_iopd = self.graph.plotItem.addItem(pyqtgraph.PlotCurveItem(
            time_values, 
            iopd_values,
            pen=pen_iopd,
            name='3'
        ))


if __name__ == '__main__':
    app = QApplication(sys.argv)
    app.setStyle('Fusion')
    main_window = MainWindow()
    main_window.show()
    sys.exit(app.exec_())

Solution

    • step 1: add the PlotCurveItems as members of Mainwindow, set them up in the constructor, so you can access them later
    • step 2: in the draw_graph function use the getData() and setData() functions of the PlotcurveItems, update them
    • step 3: if you have enough x-values set the xRange, so not all data is shown, I use a maximal xRange of 20 here (self.window_size)

    In the code below I only use the last entry in your lists (e.g. ipd_1_values[-1]), you can just pass scalars and remove the [-1].

    Also I used import numpy as np for the np.append().

        class MainWindow(QMainWindow):
            def __init__(self):
                super().__init__()
                self.central_widget = QWidget(self)
                self.setGeometry(50, 50, 1300, 700)
                self.setCentralWidget(self.central_widget)
                self.layout_main_window = QVBoxLayout()
                self.central_widget.setLayout(self.layout_main_window)
    
                # конфигурация тулбара
                self.layout_toolbar = QHBoxLayout()
                self.layout_toolbar.addStretch(1)
                self.btn_start = QPushButton("Старт")
                self.btn_start.clicked.connect(self.start)
                self.layout_toolbar.addWidget(self.btn_start)
                self.layout_main_window.addLayout(self.layout_toolbar)
    
                # конфигурация графика
                self.graph = Graph()
                self.layout_main_window.addWidget(self.graph)
    
                self.setup_graphs() # step 1
                self.window_size = 20 # step 3
    
            def start(self):
                self.reading_thread = QThread(parent=self)
                self.reading_widget = ReadingWorker()
                self.reading_widget.moveToThread(self.reading_thread)
                self.reading_widget.update_graph.connect(self.draw_graph)
                self.reading_thread.started.connect(self.reading_widget.run)
                self.reading_thread.start()
    
            def setup_graphs(self):
                pen_ipd_1 = pyqtgraph.mkPen(color='green', width=4)
                pen_ipd_2 = pyqtgraph.mkPen(color='blue', width=4, style=Qt.DashDotLine)
                pen_iopd = pyqtgraph.mkPen(color='red', width=4, style=Qt.DashLine)
                self.line_ipd_1 = pyqtgraph.PlotCurveItem([], [], pen=pen_ipd_1, name='1')
                self.line_ipd_2 = pyqtgraph.PlotCurveItem([], [], pen=pen_ipd_2, name='2')
                self.line_iopd = pyqtgraph.PlotCurveItem([], [], pen=pen_iopd, name='3')
                self.graph.plotItem.addItem(self.line_ipd_1)
                self.graph.plotItem.addItem(self.line_ipd_2)
                self.graph.plotItem.addItem(self.line_iopd)
    
            @QtCore.pyqtSlot(list, list, list, list)
            def draw_graph(self, ipd_1_values, ipd_2_values, iopd_values, time_values): # step 2
                x, y = self.line_ipd_1.getData()
                x = np.append(x, time_values[-1])
                self.line_ipd_1.setData(y=np.append(y, ipd_1_values[-1]), x=x)
                _, y = self.line_ipd_2.getData()
                self.line_ipd_2.setData(y=np.append(y, ipd_2_values[-1]), x=x)
                _, y = self.line_iopd.getData()
                self.line_iopd.setData(y=np.append(y, iopd_values[-1]), x=x)
                if (len(x)>0 and x[-1]-x[0]>self.window_size): # step 3
                    self.graph.plotItem.setXRange(x[-1]-self.window_size, x[-1])