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))
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()