2

I am trying to create a real time data plot using a PyQt plot widget. I read that PyQt is the best option for plotting real time graphs but so far I am not having any success.

I have tried to plot random data using the method followed here however it seems that this method does not apply to the PyQt plot widget.

I have compiled the following code to generate a GUI which plots random points on the x and y axis; however I get the error:

PlotWidget object is not callable

from PyQt4.QtGui import *
from PyQt4.QtCore import *

import numpy as np
import pyqtgraph as pg 
import sys


class Window(QMainWindow):

    def __init__(self):
        super(Window, self).__init__()
        self.setWindowIcon(QIcon('pythonlogo.png'))
        self.setGeometry(50,50,700,300)
        self.home()

    def home(self):

        #Timer for Plot calls the update function

        self.plot = pg.PlotWidget(self)
        self.timer2 = pg.QtCore.QTimer()
        self.timer2.timeout.connect(self.update)
        self.timer2.start(16)

        #Plot widget postion
        self.plot.move(200,50)
        self.plot.resize(450,200)

        self.show()

    def update(self):
        x = np.random.normal(size=1000)
        y = np.random.normal(size=1000)
        self.plot(x,y,clear=True) 

def run():    
        app=QApplication(sys.argv)
        GUI = Window()
        sys.exit(app.exec_())

run()
Community
  • 1
  • 1
alkey
  • 986
  • 4
  • 16
  • 33

2 Answers2

2

I have been confronted with similar issues. But in the end I got my realtime plot working!

I have taken a look at my code, and thrown out all the things that are not relevant for you. So what you will find here is the basic code that you need to display a live graph:

###################################################################
#                                                                 #
#                     PLOTTING A LIVE GRAPH                       #
#                  ----------------------------                   #
#            EMBED A MATPLOTLIB ANIMATION INSIDE YOUR             #
#            OWN GUI!                                             #
#                                                                 #
###################################################################


import sys
import os
from PyQt4 import QtGui
from PyQt4 import QtCore
import functools
import numpy as np
import random as rd
import matplotlib
matplotlib.use("Qt4Agg")
from matplotlib.figure import Figure
from matplotlib.animation import TimedAnimation
from matplotlib.lines import Line2D
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
import time
import threading



def setCustomSize(x, width, height):
    sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
    sizePolicy.setHorizontalStretch(0)
    sizePolicy.setVerticalStretch(0)
    sizePolicy.setHeightForWidth(x.sizePolicy().hasHeightForWidth())
    x.setSizePolicy(sizePolicy)
    x.setMinimumSize(QtCore.QSize(width, height))
    x.setMaximumSize(QtCore.QSize(width, height))

''''''

class CustomMainWindow(QtGui.QMainWindow):

    def __init__(self):

        super(CustomMainWindow, self).__init__()

        # Define the geometry of the main window
        self.setGeometry(300, 300, 800, 400)
        self.setWindowTitle("my first window")

        # Create FRAME_A
        self.FRAME_A = QtGui.QFrame(self)
        self.FRAME_A.setStyleSheet("QWidget { background-color: %s }" % QtGui.QColor(210,210,235,255).name())
        self.LAYOUT_A = QtGui.QGridLayout()
        self.FRAME_A.setLayout(self.LAYOUT_A)
        self.setCentralWidget(self.FRAME_A)

        # Place the zoom button
        self.zoomBtn = QtGui.QPushButton(text = 'zoom')
        setCustomSize(self.zoomBtn, 100, 50)
        self.zoomBtn.clicked.connect(self.zoomBtnAction)
        self.LAYOUT_A.addWidget(self.zoomBtn, *(0,0))

        # Place the matplotlib figure
        self.myFig = CustomFigCanvas()
        self.LAYOUT_A.addWidget(self.myFig, *(0,1))

        # Add the callbackfunc to ..
        myDataLoop = threading.Thread(name = 'myDataLoop', target = dataSendLoop, daemon = True, args = (self.addData_callbackFunc,))
        myDataLoop.start()

        self.show()

    ''''''


    def zoomBtnAction(self):
        print("zoom in")
        self.myFig.zoomIn(5)

    ''''''

    def addData_callbackFunc(self, value):
        # print("Add data: " + str(value))
        self.myFig.addData(value)



''' End Class '''


class CustomFigCanvas(FigureCanvas, TimedAnimation):

    def __init__(self):

        self.addedData = []
        print(matplotlib.__version__)

        # The data
        self.xlim = 200
        self.n = np.linspace(0, self.xlim - 1, self.xlim)
        a = []
        b = []
        a.append(2.0)
        a.append(4.0)
        a.append(2.0)
        b.append(4.0)
        b.append(3.0)
        b.append(4.0)
        self.y = (self.n * 0.0) + 50

        # The window
        self.fig = Figure(figsize=(5,5), dpi=100)
        self.ax1 = self.fig.add_subplot(111)


        # self.ax1 settings
        self.ax1.set_xlabel('time')
        self.ax1.set_ylabel('raw data')
        self.line1 = Line2D([], [], color='blue')
        self.line1_tail = Line2D([], [], color='red', linewidth=2)
        self.line1_head = Line2D([], [], color='red', marker='o', markeredgecolor='r')
        self.ax1.add_line(self.line1)
        self.ax1.add_line(self.line1_tail)
        self.ax1.add_line(self.line1_head)
        self.ax1.set_xlim(0, self.xlim - 1)
        self.ax1.set_ylim(0, 100)


        FigureCanvas.__init__(self, self.fig)
        TimedAnimation.__init__(self, self.fig, interval = 50, blit = True)

    def new_frame_seq(self):
        return iter(range(self.n.size))

    def _init_draw(self):
        lines = [self.line1, self.line1_tail, self.line1_head]
        for l in lines:
            l.set_data([], [])

    def addData(self, value):
        self.addedData.append(value)

    def zoomIn(self, value):
        bottom = self.ax1.get_ylim()[0]
        top = self.ax1.get_ylim()[1]
        bottom += value
        top -= value
        self.ax1.set_ylim(bottom,top)
        self.draw()


    def _step(self, *args):
        # Extends the _step() method for the TimedAnimation class.
        try:
            TimedAnimation._step(self, *args)
        except Exception as e:
            self.abc += 1
            print(str(self.abc))
            TimedAnimation._stop(self)
            pass

    def _draw_frame(self, framedata):
        margin = 2
        while(len(self.addedData) > 0):
            self.y = np.roll(self.y, -1)
            self.y[-1] = self.addedData[0]
            del(self.addedData[0])


        self.line1.set_data(self.n[ 0 : self.n.size - margin ], self.y[ 0 : self.n.size - margin ])
        self.line1_tail.set_data(np.append(self.n[-10:-1 - margin], self.n[-1 - margin]), np.append(self.y[-10:-1 - margin], self.y[-1 - margin]))
        self.line1_head.set_data(self.n[-1 - margin], self.y[-1 - margin])
        self._drawn_artists = [self.line1, self.line1_tail, self.line1_head]



''' End Class '''


# You need to setup a signal slot mechanism, to 
# send data to your GUI in a thread-safe way.
# Believe me, if you don't do this right, things
# go very very wrong..
class Communicate(QtCore.QObject):
    data_signal = QtCore.pyqtSignal(float)

''' End Class '''



def dataSendLoop(addData_callbackFunc):
    # Setup the signal-slot mechanism.
    mySrc = Communicate()
    mySrc.data_signal.connect(addData_callbackFunc)

    # Simulate some data
    n = np.linspace(0, 499, 500)
    y = 50 + 25*(np.sin(n / 8.3)) + 10*(np.sin(n / 7.5)) - 5*(np.sin(n / 1.5))
    i = 0

    while(True):
        if(i > 499):
            i = 0
        time.sleep(0.1)
        mySrc.data_signal.emit(y[i]) # <- Here you emit a signal!
        i += 1
    ###
###




if __name__== '__main__':
    app = QtGui.QApplication(sys.argv)
    QtGui.QApplication.setStyle(QtGui.QStyleFactory.create('Plastique'))
    myGUI = CustomMainWindow()


    sys.exit(app.exec_())

''''''

Just try it out. Copy-paste this code in a new python-file, and run it. You should get a beautiful smooth live graph:

enter image description here

K.Mulier
  • 8,069
  • 15
  • 79
  • 141
  • thank you so much. You have built a really neat app :) I noticed that you have opted to use Matplotlib canvas over the PyQt4 plot widget. Is this because you had no success with PyQt4? I was under the impression one of the main draws to PyQt4 is the ability to plot real time data but it appears this is not the case with the widget. I also noticed in your code that you said things go very very wrong if you don't use a thread safe way? What are the risks? I will be receiving data signals from a temperature sensor connected to a Rpi. Thanks again for this answer, it is very useful :) – alkey Jul 20 '16 at 22:21
  • Hi Allan Key. The matplotlib library has amazing support for making all sorts of animations. And its code is optimized such that the graphs are smooth. For me, that's important :-) . Well, about the thread unsafe ways, I had once very strange error messages before I made my code "thread safe". I can look it up for you, if you're interested. Anyway, the code I presented in my answer is 100% thread safe. Do you notice that a new data point is sent to the graph every 0.1 seconds? It is 'emitted' through the signal-slot mechanism. If you send the temperature data through (instead of that .. – K.Mulier Jul 20 '16 at 23:03
  • .. sinus function that I generate), you will see your temperature graph live! Trust me, I did it with twelve signals coming from a microcontroller simultaneously. All twelve signals move smoothly, as if you're looking at the control panel of a nuclear power plant ;-) – K.Mulier Jul 20 '16 at 23:06
  • Please don't hesitate if you've got further questions. I'm always happy to help! – K.Mulier Jul 20 '16 at 23:07
  • Thanks @K.Mulier this is a really good alternative to the PyQt 4 and would be perfect for the app I am going to develop. I am a noob though and it will take me sometime to digest but I will send you my adapted version when I am finished :). To help those working in PyQt4 who find this post I have added an answer below that solves the PyQt realtime plot problem I was originally having. It is very basic though and I would really like to develop it further to plot against time, with a similar functionality to your example. Over time I hope to evolve the answers to help others :) – alkey Jul 21 '16 at 22:13
  • Hi allankey. I'm happy to hear that my code will be useful to you. If you got any questions, don't hesitate to ask :-) – K.Mulier Jul 21 '16 at 22:16
  • Hi @allankey , I came back to this code recently, and discovered that the `dataSendLoop` thread keeps running in the background after you close down the window. That's a real pain. You can solve it by adding the `daemon = True` keyword when creating the thread. I have updated my answer. – K.Mulier Sep 26 '16 at 09:29
0

Ok so I was able to solve the error I was getting and was able to update the plot widget in real time. The code below gives some basic examples. I hope to improve this answer overtime to show the functionality of real time plotting in PyQt.

from PyQt4.QtGui import *
from PyQt4.QtCore import *

import numpy as np
import pyqtgraph as pg
import random
import sys
import datetime


class Window(QMainWindow):

    def __init__(self):
        super(Window, self).__init__()
        self.setWindowIcon(QIcon('pythonlogo.png'))
        self.setGeometry(50,50,700,900)
        self.home()

    def home(self):    

        #Labels

        staticLbl = QLabel("Static Plot",self)
        staticLbl.move(10,50)

        dynamicLbl = QLabel("Random Plot",self)
        dynamicLbl.move(10,300)

        conLbl = QLabel("Continuous Plot",self)
        conLbl.move(10,550)

        #Static plot widget:

        staticPlt = pg.PlotWidget(self)
        x = np.random.normal(size=10)
        y = np.random.normal(size=10)

        staticPlt.plot(x,y,clear=True)

        staticPlt.move(200,50)
        staticPlt.resize(450,200)

        #Code to run to random plot using timer:

        self.dynamicPlt = pg.PlotWidget(self)

        self.dynamicPlt.move(200,300)
        self.dynamicPlt.resize(450,200)

        self.timer2 = pg.QtCore.QTimer()
        self.timer2.timeout.connect(self.update)
        self.timer2.start(200)

        #Code to run to get continous plot using timer:

        self.continuousPlt = pg.PlotWidget(self)

        self.continuousPlt.move(200,550)
        self.continuousPlt.resize(450,200)

        self.timer3 = pg.QtCore.QTimer()
        self.timer3.timeout.connect(self.cUpdate)
        self.timer3.start(200)

        self.show()

    def update(self):
        z = np.random.normal(size=1)
        u = np.random.normal(size=1)
        self.dynamicPlt.plot(z,u,pen=None, symbol='o')

    def cUpdate(self):
        now = datetime.datetime.now()
        s = np.array([now.second])

        self.continuousPlt.plot(s,s,pen=None, symbol='o')


def run():    
        app=QApplication(sys.argv)
        GUI = Window()
        sys.exit(app.exec_())

run()
alkey
  • 986
  • 4
  • 16
  • 33