maynull
maynull

Reputation: 2046

How can I automatically update data in pyqtgraph?

I'm trying to plot candlesticks. I referred to this question and answer( The fastest way to add a new data bar with pyqtgraph )

I want my program to update and plot new candlesticks by receiving new values from a server, through calling update().

A problem that I have is that this example doesn't work without using QtCore.QTimer(). For example, if I manually call update() on the prompt or by apscheduler, candlesticks show no difference, but once I select the plot window, it shows new candlesticks at the same time. I don't understand why this happens.

Could anyone try testing this and tell me how to fix this problem?

import pyqtgraph as pg
from pyqtgraph import QtCore, QtGui
import random
import numpy as np 
from apscheduler.schedulers.background import BackgroundScheduler

class CandlestickItem(pg.GraphicsObject):
    def __init__(self):
        pg.GraphicsObject.__init__(self)
        self.flagHasData = False

    def set_data(self, data):
        self.data = data 
        self.flagHasData = True
        self.generatePicture()
        self.informViewBoundsChanged()

    def generatePicture(self):
        self.picture = QtGui.QPicture()
        p = QtGui.QPainter(self.picture)
        p.setPen(pg.mkPen('w'))
        w = (self.data[1][0] - self.data[0][0]) / 3.
        for (t, open, close, min, max) in self.data:
            p.drawLine(QtCore.QPointF(t, min), QtCore.QPointF(t, max))
            if open > close:
                p.setBrush(pg.mkBrush('r'))
            else:
                p.setBrush(pg.mkBrush('g'))
            p.drawRect(QtCore.QRectF(t-w, open, w*2, close-open))
        p.end()

    def paint(self, p, *args):
        if self.flagHasData:
            p.drawPicture(0, 0, self.picture)

    def boundingRect(self):
        return QtCore.QRectF(self.picture.boundingRect())

app = QtGui.QApplication([])

data = [  
    [1., 10, 13, 5, 15],
    [2., 13, 17, 9, 20],
    [3., 17, 14, 11, 23],
    [4., 14, 15, 5, 19],
    [5., 15, 9, 8, 22],
    [6., 9, 15, 8, 16],
]
item = CandlestickItem()
item.set_data(data)

plt = pg.plot()
plt.addItem(item)
plt.setWindowTitle('pyqtgraph example: customGraphicsItem')


def update():
    global item, data
    data_len = len(data)
    rand = random.randint(0, len(data)-1)
    new_bar = data[rand][:]
    new_bar[0] = data_len
    data.append(new_bar)
    item.set_data(data)
    app.processEvents() 


## DOESN'T SHOW NEW CANDLESTICKS UNLESS YOU SELECT THE PLOT WINDOW
#sched = BackgroundScheduler()
#sched.start()
#sched.add_job(update, trigger='cron', second='*/1')

## WORKS FINE WITH THIS PARAGRAPH.
timer = QtCore.QTimer()
timer.timeout.connect(update)
timer.start(1000)


if __name__ == '__main__':
    import sys
    if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
        QtGui.QApplication.instance().exec_()

Upvotes: 0

Views: 2986

Answers (1)

eyllanesc
eyllanesc

Reputation: 243975

Many times non-native elements for Qt generate that behavior, Qt is a framework that has libraries for many tasks like the ones you want to do, a Qt-style solution would be to use QThreads and in that case we would use signals to update the data, but another solution but it is simple to use QRunnable and QThreadPool as I show below:

class PlotRunnable(QtCore.QRunnable):
    def __init__(self, it):
        QtCore.QRunnable.__init__(self)
        self.it = it

    def run(self):
        while True:
            data = self.it.data
            data_len = len(data)
            rand = random.randint(0, len(data)-1)
            new_bar = data[rand][:]
            new_bar[0] = data_len
            data.append(new_bar)

            QtCore.QMetaObject.invokeMethod(self.it, "set_data",
                                     QtCore.Qt.QueuedConnection,
                                     QtCore.Q_ARG(list, data))
            QtCore.QThread.msleep(1000)


class CandlestickItem(pg.GraphicsObject):
    def __init__(self):
        pg.GraphicsObject.__init__(self)
        self.flagHasData = False

    @QtCore.pyqtSlot(list)
    def set_data(self, data):
        self.data = data 
        self.flagHasData = True
        self.generatePicture()
        self.informViewBoundsChanged()

    def generatePicture(self):
        [...]

app = QtGui.QApplication([])

data = [  
    [1., 10, 13, 5, 15],
    [2., 13, 17, 9, 20],
    [3., 17, 14, 11, 23],
    [4., 14, 15, 5, 19],
    [5., 15, 9, 8, 22],
    [6., 9, 15, 8, 16],
]
item = CandlestickItem()
item.set_data(data)

plt = pg.plot()
plt.addItem(item)
plt.setWindowTitle('pyqtgraph example: customGraphicsItem')


runnable = PlotRunnable(item)
QtCore.QThreadPool.globalInstance().start(runnable)

if __name__ == '__main__':
    import sys
    if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
        QtGui.QApplication.instance().exec_()

Upvotes: 2

Related Questions