How to force custom GraphicsObject to update

3,897 views
Skip to first unread message

Julio Trevisan

unread,
Mar 5, 2013, 7:46:03 PM3/5/13
to pyqt...@googlegroups.com
Hi,

I recently found pyqtgraph and I think that it is an amazing project! The API is clean, the documentation is good and the examples are really helpful.

I am already using pyqtgraph in my new project, but I am having a difficulty to get my GraphicsObject descendant to update when I set new data. The only way i can get it to replot is resizing the window manuyally. I posted my code below. I would like the plot to be updated at the end of the SetQuotes() method.

Any help would be very much appreciated.

Julio

class VolumeCurve(pg.GraphicsObject):
    def __init__(self, *args):
        NavalaCurve.__init__(self, *args)
        self.picture = None
       
    def setColor(self, color):
        c = Qt.QColor(color)
        c.setAlpha(150)
        self.pen = Qt.QPen(c)
        self.brush = Qt.QBrush(c)

    def SetQuotes(self, quotes):
        """
        Arguments:
          quotes -- a timedata.Quotes object.
         
        """
        self.quotes = quotes
        self.update()   <---- does nothing!

    def paint(self, painter, *args):
        if not self.HasQuotes():
            return
        x = self.quotes.timestamp
        y = self.quotes.volume
        points = [QtCore.QPointF(x_, y_) for (x_, y_) in zip(x, y)]
        points.append(QtCore.QPointF(x[-1], 0))
        points.append(QtCore.QPointF(x[0], 0))
        p = QtGui.QPolygonF(points)
        painter.setPen(self.pen)
        painter.setBrush(self.brush)
        painter.drawPolygon(p)
   
    def boundingRect(self):
        """Returns the bounding rectangle of the data.
       
        For the y boundaries, uses low/high if they are used, or open/close
        otherwise.
        """       
        if not self.HasQuotes():
            return Qt.QRectF(0, 0, 0, 0)
        x = self.quotes.timestamp
        xmin = np.min(x)
        xmax = np.max(x)
        ymax = np.max(self.quotes.volume)
        return Qt.QRectF(xmin, 0, xmax-xmin, ymax)


Luke Campagnola

unread,
Mar 5, 2013, 9:19:57 PM3/5/13
to pyqt...@googlegroups.com
On Tue, Mar 5, 2013 at 7:46 PM, Julio Trevisan <juliot...@gmail.com> wrote:
I am already using pyqtgraph in my new project, but I am having a difficulty to get my GraphicsObject descendant to update when I set new data. The only way i can get it to replot is resizing the window manuyally. I posted my code below. I would like the plot to be updated at the end of the SetQuotes() method.


Hmmm..  This looks mostly ok to me. Calling update() should cause Qt to schedule a repaint. If you put a print statement in the paint() method, can you see whether it is being called? 

There is one issue I can see that might cause this: whenever the bounding rectangle is changing, you must call prepareGeometryChange() to let Qt know this is about to happen. In some cases, failure to do this can cause Qt to skip over your object since it doesn't know about the new bounds. (in the worst cases, failure to call prepareGeometryChange can cause crashes, so either way it's important to correct that). In your case, it should be called from the setQuotes method. 

I'll also mention that it looks like you're trying to fill the area under a curve, and PlotCurveItem provides this via its fillLevel and brush arguments (although there are plenty of reasons you might want to do this yourself).


Luke





 

Julio Trevisan

unread,
Mar 5, 2013, 9:36:14 PM3/5/13
to pyqt...@googlegroups.com
Hi Luke, thank you for your quick reply. I tried to add a self.prepareGeometryChange() after self.update(), but it is still the same. The paint() method is definitely being called only when I resize the window. I use the debugger tools in Eric and I placed a breakpoint inside paint(). Any other suggestions maybe??


True, I did start using PlotCurveItem, but there were a few reasons why I decided to do it myself. I also have other classes implementing more complicated Items, so I cannot get away with the updating thing by using PlotCurveItem anyway.
 


Luke





 

--
-- [ You are subscribed to pyqt...@googlegroups.com. To unsubscribe, send email to pyqtgraph+...@googlegroups.com ]
---
You received this message because you are subscribed to the Google Groups "pyqtgraph" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pyqtgraph+...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Luke Campagnola

unread,
Mar 6, 2013, 5:29:55 AM3/6/13
to pyqt...@googlegroups.com
On Tue, Mar 5, 2013 at 9:36 PM, Julio Trevisan <juliot...@gmail.com> wrote:
Hi Luke, thank you for your quick reply. I tried to add a self.prepareGeometryChange() after self.update(), but it is still the same. The paint() method is definitely being called only when I resize the window. I use the debugger tools in Eric and I placed a breakpoint inside paint(). Any other suggestions maybe??

Reasons paint() might not be called:
1. The item is not added to a scene (item.scene() returns None) 
2. The item's boundingRect does not occupy any visible part of the scene, so Qt is skipping over it
3. The item has its ItemHasNoContents flag set
(and maybe others?) 

If you can post a simple example, I'll have a look.

Luke

Julio Trevisan

unread,
Mar 12, 2013, 10:13:59 AM3/12/13
to pyqt...@googlegroups.com
Hi Luke

Thanks for your help again. I was taking care of other part of the project, so took a while to reply.

I made something that actually made paint be called, but it still didn't show anything!

I tried to develop another example based on your customGraphicItem.py example. I need to add the item to the plot first, without any data, and only later set the data. So, I modified your example a bit to try to make it behave like this. Please see below:

"""
Demonstrate creation of a custom graphic (a candlestick plot)

"""
#import initExample ## Add path to library (just for examples; you do not need this)

import pyqtgraph as pg
from pyqtgraph import QtCore, QtGui

## Create a subclass of GraphicsObject.
## The only required methods are paint() and boundingRect()
## (see QGraphicsItem documentation)
class CandlestickItem(pg.GraphicsObject):
    def __init__(self):
        pg.GraphicsObject.__init__(self)
        self.flagHasData = False
       
    def SetData(self, data):
        self.data = data  ## data must have fields: time, open, close, min, max
        self.flagHasData = True
        self.generatePicture()
   
    def generatePicture(self):
        ## pre-computing a QPicture object allows paint() to run much more quickly,
        ## rather than re-drawing the shapes every time.
        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):
        ## boundingRect _must_ indicate the entire area that will be drawn on
        ## or else we will get artifacts and possibly crashing.
        ## (in this case, QPicture does all the work of computing the bouning rect for us)
        if self.flagHasData:
            return QtCore.QRectF(self.picture.boundingRect())
        else:
            return QtCore.QRectF(0, 0, 0, 0)

data1 = [  ## fields are (time, open, close, min, max).
    (1., 10, 13, 5, 15),
    (12., 13, 17, 9, 2.0),
    (13., 17, 14, 111, 23),
    (4., 114, 15, 5, 1.9),
    (5., 15, 39, 8, 22),
    (6., 9, 15, 28, 16),
]
data2 = [  ## fields are (time, open, close, min, max).
    (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.SetData(data1)
plt = pg.plot()
plt.addItem(item)
item.SetData(data2)
plt.setWindowTitle('pyqtgraph example: customGraphicsItem')

## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
    import sys
    if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
        QtGui.QApplication.instance().exec_()


If I uncomment the bold line above (i.e., the item is added to the plot with data already), data2 gets plotted. Otherwise, only the axes are plotted. Please, what is missing from here?

Julio

Luke Campagnola

unread,
Mar 12, 2013, 1:50:04 PM3/12/13
to pyqt...@googlegroups.com
I see. The trouble here is that, after calling setData, the ViewBox does not know that the boundaries of the candlestick item have changed, and thus does not adjust its range automatically. Notice that if you manually adjust the range of the ViewBox, the item is displayed once the range is correct. If you add "self.informViewBoundsChanged()" to the end of SetData(), then this problem is fixed.  Does that solve your original problem too? 

Thanks for writing this up. I'll update the CustomGraphicsItem example to make this more clear.

Luke





Julio Trevisan

unread,
Mar 12, 2013, 4:33:48 PM3/12/13
to pyqt...@googlegroups.com
Hi Luke

Yes, it solved my original problem! Thank you so much for this!!!

May I ask you a related question? I saw your comment inside CustomGraphicsItem saying that generating a QPicture makes rendering faster. I believe that this is not the case if I want to update my plot in realtime. In such case, it would be better to draw the primitives when paint is requested. What do you think?

Thank you

Julio



Luke





Luke Campagnola

unread,
Mar 12, 2013, 5:09:20 PM3/12/13
to pyqt...@googlegroups.com
On Tue, Mar 12, 2013 at 4:33 PM, Julio Trevisan <juliot...@gmail.com> wrote:
Hi Luke

Yes, it solved my original problem! Thank you so much for this!!!

May I ask you a related question? I saw your comment inside CustomGraphicsItem saying that generating a QPicture makes rendering faster. I believe that this is not the case if I want to update my plot in realtime. In such case, it would be better to draw the primitives when paint is requested. What do you think?

That's correct; if you expect each data set to be drawn only once, then using QPicture will probably slow you down. Note that if you get rid of QPicture, then you'll also have to do your own bounding rectangle calculations. The boundingRect() methods are called quite frequently, so make sure to optimize that as much as you can. If you have trouble with performance, I'd be happy to discuss the tricks I've used. 

Luke

Julio Trevisan

unread,
Mar 12, 2013, 5:11:29 PM3/12/13
to pyqt...@googlegroups.com
Cool, thanks a lot :))


--

Lior Weintraub

unread,
Aug 17, 2018, 4:51:56 AM8/17/18
to pyqtgraph
Hi Luke,

I love pyqtgraph and use it whenever I need to plot data.
I tried to make a live cadlestick plot but none of the options worked.
The plot doesn't update unless I zoom in/out (interact with the window).
The code I use:

import pyqtgraph as pg
from pyqtgraph import QtCore, QtGui
import collections
import numpy as np

class CandlestickItem(pg.GraphicsObject):
    data
= []
   
def __init__(self):
        pg
.GraphicsObject.__init__(self)
       
self.picture = QtGui.QPicture()
     
   
def setData(self, toclh):
       
# toclh is a tuple of (time, open, close, min, max)
       
self.data.append(toclh)

        p
= QtGui.QPainter(self.picture)
        p
.setPen(pg.mkPen('w'))

        w
= 1./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):

        p
.drawPicture(0, 0, self.picture)
   
   
def boundingRect(self):
       
## boundingRect _must_ indicate the entire area that will be drawn on
       
## or else we will get artifacts and possibly crashing.
       
## (in this case, QPicture does all the work of computing the bouning rect for us)

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

data
= [  ## fields are (time, open, close, min, max).

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


class DynamicPlotter():
   
def __init__(self, sampleinterval, timewindow, size=(600,350)):
       
# Data stuff
       
self.tick_idx = 0
       
self._bufsize = int(timewindow/sampleinterval)
       
self.databuffer = collections.deque([0.0]*self._bufsize, self._bufsize)
       
self.x = np.linspace(0.0, timewindow, self._bufsize)
       
self.y = np.zeros(self._bufsize, dtype=np.float)
       
# Candlestick data
       
self.candlestick_item = CandlestickItem()
       
# PyQtGraph stuff
       
self.app = QtGui.QApplication([])
       
self.plt = pg.plot(title='Plot Viewer')
       
self.plt.resize(*size)
       
self.plt.showGrid(x=True, y=True)
       
self.plt.setLabel('left', 'amplitude', 'V')
       
self.plt.setLabel('bottom', 'time', 's')
       
self.plt.setXRange(0.,timewindow)
       
self.plt.setYRange(0.,30)
       
self.curve = self.plt.plot(self.x, self.y, pen=(255,0,0))
       
self.plt.addItem(self.candlestick_item)


       
# QTimer
       
self.timer = QtCore.QTimer()
       
self.timer.timeout.connect(self.updateplot)
       
self.timer.start(1000)


   
def updateplot(self):
       
if(self.tick_idx < len(data)):
           
self.candlestick_item.setData(data[self.tick_idx])
           
self.databuffer.appendleft( 1.0 )
           
self.y[:] = self.databuffer
           
self.curve.setData(self.x, self.y)
           
self.tick_idx += 1
       
self.app.processEvents()


   
def run(self):
       
self.app.exec_()


if __name__ == '__main__':
    m
= DynamicPlotter(sampleinterval=1., timewindow=7.)
    m
.run()





Thanks,
Lior.

Luke Campagnola

unread,
Aug 17, 2018, 12:01:00 PM8/17/18
to pyqt...@googlegroups.com
Try putting self.update() at the d of your setDataethod?

--
You received this message because you are subscribed to the Google Groups "pyqtgraph" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pyqtgraph+...@googlegroups.com.

Lior Weintraub

unread,
Aug 17, 2018, 5:01:19 PM8/17/18
to pyqtgraph
Thanks Luke.
Worked like a charm :-)
Reply all
Reply to author
Forward
0 new messages