shashashamti2008
shashashamti2008

Reputation: 2327

Dynamically updating a QChart

I have a class that plots a candlestick for given data. I am trying to dynamically update the plot as soon a new data is received. Data is received at irregular intervals.

What mechanism can I use in order to let the class know it's time to update the plot and act upon updating the plot with the newly received data point?

Class function append_data_and_plot() appends the data but the plot never gets updated. Could someone kindly shed light on what the issue is?

import sys
from PyQt5.QtChart import QCandlestickSeries, QChart, QChartView, QCandlestickSet
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtCore import Qt, QPointF
from PyQt5 import QtChart as qc
import time

class myCandlestick():
    def __init__(self, data):
        self.data = data
        self.app = QApplication(sys.argv)

        self.series = QCandlestickSeries()
        self.series.setDecreasingColor(Qt.red)
        self.series.setIncreasingColor(Qt.green)

        self.ma5 = qc.QLineSeries() 
        self.tm = []

        for num, o, h, l, c, m in self.data:
            self.series.append(QCandlestickSet(o, h, l, c))
            self.ma5.append(QPointF(num, m))
            self.tm.append(str(num))

        self.chart = QChart()

        self.chart.addSeries(self.series)  # candle
        self.chart.addSeries(self.ma5)  # ma5 line

        self.chart.createDefaultAxes()
        self.chart.legend().hide()

        self.chart.axisX(self.series).setCategories(self.tm)
        self.chart.axisX(self.ma5).setVisible(False)

        self.chartview = QChartView(self.chart)
        self.ui = QMainWindow()
        self.ui.setGeometry(50, 50, 500, 300)
        self.ui.setCentralWidget(self.chartview)
        self.ui.show()
        sys.exit(self.app.exec_())

    def append_data_and_plot(self, d):
        '''Append and update the plot'''
        num, o, h, l, c, m = d
        self.series.append(QCandlestickSet(o, h, l, c))
        self.ui.show()
        #sys.exit(self.app.exec_())


data = ((1, 7380, 7520, 7380, 7510, 7324),
        (2, 7520, 7580, 7410, 7440, 7372),
        (3, 7440, 7650, 7310, 7520, 7434),
        (4, 7450, 7640, 7450, 7550, 7480),
        (5, 7510, 7590, 7460, 7490, 7502),
        (6, 7500, 7590, 7480, 7560, 7512),
        (7, 7560, 7830, 7540, 7800, 7584))

m = myCandlestick(data)

# Data is received at irregular intervals
time.sleep(1)
m.append_data_and_plot((8, 7560, 7830, 7540, 7800, 7584))

# Data is received at irregular intervals
time.sleep(0.1)
m.append_data_and_plot((9, 7450, 7640, 7450, 7550, 7480))

# Data is received at irregular intervals
time.sleep(2.5)
m.append_data_and_plot((10, 7380, 7520, 7380, 7510, 7324))

Upvotes: 4

Views: 3302

Answers (1)

eyllanesc
eyllanesc

Reputation: 243983

Qt uses an event loop to handle the events and tasks of the gui, and that is the code "app.exec_ ()" so all the code after that line will not be executed, and if it is added to that it is used sys.exit () will make the application terminate when the Qt eventloop ends.

The solution is to use the eventloop to update the gui, for example using a QTimer:

import sys
from PyQt5.QtChart import QCandlestickSeries, QChart, QChartView, QCandlestickSet
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtCore import Qt, QPointF, QObject, pyqtSignal, QTimer
from PyQt5 import QtChart as qc

import random


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

        self.series = QCandlestickSeries()
        self.series.setDecreasingColor(Qt.red)
        self.series.setIncreasingColor(Qt.green)

        self.ma5 = qc.QLineSeries()
        self.tm = []

        self.chart = QChart()

        self.chart.addSeries(self.series)  # candle
        self.chart.addSeries(self.ma5)  # ma5 line

        self.chart.createDefaultAxes()
        self.chart.legend().hide()

        self.chart.axisX(self.series).setCategories(self.tm)
        self.chart.axisX(self.ma5).setVisible(False)

        self.chartview = QChartView(self.chart)
        self.setGeometry(50, 50, 500, 300)
        self.setCentralWidget(self.chartview)

    def append_data_and_plot(self, d):
        """Append and update the plot"""
        num, o, h, l, c, m = d

        ax1 = self.chart.axisX(self.ma5)
        ay1 = self.chart.axisY(self.ma5)

        xmin = xmax = num
        ymin = ymax = m

        step = 10
        offset = 100

        for p in self.ma5.pointsVector()[-step:]:
            xmin = min(p.x(), xmin)
            xmax = max(p.x(), xmax)

            ymin = min(p.y(), ymin) - offset
            ymax = max(p.y(), ymax) + offset

        xmin = max(0, xmax - step)

        ax1.setMin(xmin)
        ax1.setMax(xmax)
        ay1.setMin(ymin)
        ay1.setMax(ymax)

        self.ma5.append(QPointF(num, m))
        self.tm.append(str(num))

        self.series.append(QCandlestickSet(o, h, l, c))
        ax2 = self.chart.axisX(self.series)
        ax2.setCategories(self.tm)
        ax2.setMin(str(xmin))
        ax2.setMax(str(xmax))

        ay2 = self.chart.axisY(self.series)
        ay2.setMin(ymin)
        ay2.setMax(ymax)


def create_data():


    i = 1
    while True:
        i += 1
        yield (
            i,
            random.randint(7000, 8000),
            random.randint(7000, 8000),
            random.randint(7000, 8000),
            random.randint(7000, 8000),
            random.randint(7000, 8000),
        )


class Producer(QObject):
    dataChanged = pyqtSignal(list)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.iter = create_data()
        QTimer.singleShot(random.randint(0, 1500), self.send_data)

    def send_data(self):
        d = list(next(self.iter))
        self.dataChanged.emit(d)
        QTimer.singleShot(random.randint(0, 1500), self.send_data)


def main():
    app = QApplication(sys.argv)

    data = ((1, 7380, 7520, 7380, 7510, 7324),)
    w = MainWindow(data)
    w.show()

    p = Producer()
    p.dataChanged.connect(w.append_data_and_plot)

    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

Upvotes: 3

Related Questions