0

I encounter a problem when I try to make a self updating QLineSeries through the help of QThread.

from PySide2.QtCore import *
from PySide2 import *
from PySide2.QtWidgets import *
from PySide2.QtCharts import *
from PySide2.QtGui import *

import time


baseValuesList = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

randomValuesList = [256, 14, 89, 100, 150, 500, 50, 34, 67, 90, 102, 12, 19, 89, 34, 145, 71, 4, 89, 47]

rangeList = list(range(len(baseValuesList))) 

def listUpdatingFunction(): 
    baseValuesList.pop(0)
    baseValuesList.append(randomValuesList[0])
    randomValuesList.pop(0)

class Worker(QObject):
    def __init__(self, function, interval):
        super(Worker, self).__init__()
        self._step = 0
        self._isRunning = True
        self.function = function
        self.interval = interval

    def task(self):
        if not self._isRunning:
            self._isRunning = True
            self._step = 0

        while self._isRunning == True:
            self.function()
            time.sleep(self.interval)

    def stop(self):
        self._isRunning = False
        
class minimalMonitor(QtWidgets.QWidget):
    def __init__(self):
        QtWidgets.QWidget.__init__(self)
          
        # Creating QChart
        self.layout = QVBoxLayout()
        self.setLayout(self.layout)
        self.chart = QtCharts.QChart()
        self.chart.setAnimationOptions(QtCharts.QChart.AllAnimations)
        self.series = QtCharts.QLineSeries()
        for i in rangeList:
            self.series.append(i,baseValuesList[i])
        self.chart.addSeries(self.series)
        self.chart_view = QtCharts.QChartView(self.chart)
        self.chart_view.setRenderHint(QPainter.Antialiasing)
        self.layout.addWidget(self.chart_view)
        self.axis_x = QtCharts.QValueAxis()
        self.chart.addAxis(self.axis_x, Qt.AlignBottom)
        self.axis_y = QtCharts.QValueAxis()
        self.chart.addAxis(self.axis_y, Qt.AlignLeft)
        self.axis_x.setRange(0, 20)
        self.axis_y.setRange(0, 300)
        self.series.attachAxis(self.axis_x)
        self.series.attachAxis(self.axis_y)
        self.thread = QThread()
        self.thread.start()

        self.worker = Worker(self.update, 1)
        self.worker.moveToThread(self.thread)

        self.autoUpdateStart = QPushButton("Start Auto-Update")
        self.autoUpdateStart.setCheckable(False)
        self.autoUpdateStart.toggle()
        self.autoUpdateStart.clicked.connect(self.worker.task)
        
        self.autoUpdateStop = QPushButton("Stop Auto-Update")
        self.autoUpdateStop.setCheckable(False)
        self.autoUpdateStop.toggle()
        self.autoUpdateStop.clicked.connect(lambda: self.worker.stop())
        self.layout.addWidget(self.autoUpdateStart)
        self.layout.addWidget(self.autoUpdateStop)
        self.manualUpdateButton = QPushButton("Manual Update")
        self.manualUpdateButton.setCheckable(False)
        self.manualUpdateButton.toggle()
        self.manualUpdateButton.clicked.connect(self.update)
        self.layout.addWidget(self.manualUpdateButton)
    def update(self):
            listUpdatingFunction()
            self.series.clear()

            for i in rangeList:
                self.series.append(i,baseValuesList[i])

            self.chart_view.update()
            
if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    widget = minimalMonitor()
    widget.show()
    sys.exit(app.exec_())

In a nutshell, if I call directly the update function or press manual update, the QLineSeries will correctly append, but as soon as I use the QThread or Auto Update button it goes haywire and will always try to join with the Axis Origin. Does any one have an idea why it is doing this?

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
hugodurmer
  • 11
  • 3
  • Why is it necessary for you to use threads? I don't see any time-consuming tasks worth threading. And please do not delete your post and create a new one, but edit the existing one because if you continue with this behavior, the point will come that you cannot publish a post. – eyllanesc Nov 07 '20 at 21:16
  • This panel is meant to be embedded within an application that uses PySide2. The whole idea was to run the loop until stopped by the user, in the background. Hence why I tried to make a thread. Sorry about the deleting of the post, but as the previous was already getting downvoted and it went against StackOverflow's etiquette, I thought it would be better to simply repost it from scratch. – hugodurmer Nov 07 '20 at 21:22
  • 1) I mean, only the thread to generate a kind of loop that runs every T seconds, if so then it is better to use a QTimer since the threads can generate more problems than solutions as I see in your case. 2) Rejection in SO is routine so don't give it too much importance, we reject bad posts (for example, I asked you for an MRE) so that the OP (I mean you) improves your post by editing it, not creating new posts. – eyllanesc Nov 07 '20 at 21:26
  • Thanks for the QTimer tip, I'll look at it. And have no worries, this won't happen again. – hugodurmer Nov 07 '20 at 21:29

1 Answers1

1

In this case I see the following errors:

  • It is not necessary to use threads to do a repetitive task since a QTImer is enough, for example with a QThread you cannot and should not update the GUI from a secondary thread as you do now, unlike QTimer that allows it.
  • I don't understand why you delete elements from randomValuesList since at one point that list will be empty causing problems.
import random

from PySide2 import QtCore, QtGui, QtWidgets
from PySide2.QtCharts import QtCharts


baseValuesList = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
randomValuesList = [256, 14, 89, 100, 150, 500, 50, 34, 67, 90, 102, 12, 19, 89, 34, 145, 71, 4, 89, 47]


def listUpdatingFunction():
    baseValuesList.pop(0)
    value = random.choice(randomValuesList)
    baseValuesList.append(value)


class Worker(QtCore.QObject):
    def __init__(self, function, interval):
        super(Worker, self).__init__()
        self._funcion = function
        self._timer = QtCore.QTimer(self, interval=interval, timeout=self.execute)

    @property
    def running(self):
        return self._timer.isActive()

    def start(self):
        self._timer.start()

    def stop(self):
        self._timer.stop()

    def execute(self):
        self._funcion()


class minimalMonitor(QtWidgets.QWidget):
    def __init__(self):
        QtWidgets.QWidget.__init__(self)

        # Creating QChart
        layout = QtWidgets.QVBoxLayout(self)
        self.chart = QtCharts.QChart()
        self.chart.setAnimationOptions(QtCharts.QChart.AllAnimations)
        self.series = QtCharts.QLineSeries()
        self.chart.addSeries(self.series)
        self.chart_view = QtCharts.QChartView(self.chart)
        self.chart_view.setRenderHint(QtGui.QPainter.Antialiasing)
        layout.addWidget(self.chart_view)
        self.axis_x = QtCharts.QValueAxis()
        self.chart.addAxis(self.axis_x, QtCore.Qt.AlignBottom)
        self.axis_y = QtCharts.QValueAxis()
        self.chart.addAxis(self.axis_y, QtCore.Qt.AlignLeft)
        self.axis_x.setRange(0, 20)
        self.axis_y.setRange(0, 300)
        self.series.attachAxis(self.axis_x)
        self.series.attachAxis(self.axis_y)

        self.worker = Worker(self.update_chart, 1000)

        self.autoUpdateStart = QtWidgets.QPushButton("Start Auto-Update")
        self.autoUpdateStart.setCheckable(False)
        self.autoUpdateStart.toggle()
        self.autoUpdateStart.clicked.connect(self.worker.start)

        self.autoUpdateStop = QtWidgets.QPushButton("Stop Auto-Update")
        self.autoUpdateStop.setCheckable(False)
        self.autoUpdateStop.toggle()
        self.autoUpdateStop.clicked.connect(self.worker.stop)
        layout.addWidget(self.autoUpdateStart)
        layout.addWidget(self.autoUpdateStop)
        self.manualUpdateButton = QtWidgets.QPushButton("Manual Update")
        self.manualUpdateButton.setCheckable(False)
        self.manualUpdateButton.toggle()
        self.manualUpdateButton.clicked.connect(self.update_chart)
        layout.addWidget(self.manualUpdateButton)

        self.update_chart()

    def update_chart(self):
        listUpdatingFunction()
        self.series.clear()

        for i, value in enumerate(baseValuesList):
            self.series.append(i, value)

        self.chart_view.update()


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    widget = minimalMonitor()
    widget.show()
    sys.exit(app.exec_())
eyllanesc
  • 235,170
  • 19
  • 170
  • 241