283

I am trying to plot some data from a camera in real time using OpenCV. However, the real-time plotting (using matplotlib) doesn't seem to be working.

I've isolated the problem into this simple example:

fig = plt.figure()
plt.axis([0, 1000, 0, 1])

i = 0
x = list()
y = list()

while i < 1000:
    temp_y = np.random.random()
    x.append(i)
    y.append(temp_y)
    plt.scatter(i, temp_y)
    i += 1
    plt.show()

I would expect this example to plot 1000 points individually. What actually happens is that the window pops up with the first point showing (ok with that), then waits for the loop to finish before it populates the rest of the graph.

Any thoughts why I am not seeing points populated one at a time?

Velimir Mlaker
  • 9,501
  • 4
  • 38
  • 54
Chris
  • 7,745
  • 13
  • 39
  • 60

12 Answers12

356

Here's the working version of the code in question (requires at least version Matplotlib 1.1.0 from 2011-11-14):

import numpy as np
import matplotlib.pyplot as plt

plt.axis([0, 10, 0, 1])

for i in range(10):
    y = np.random.random()
    plt.scatter(i, y)
    plt.pause(0.05)

plt.show()

Note the call to plt.pause(0.05), which both draws the new data and runs the GUI's event loop (allowing for mouse interaction).

Velimir Mlaker
  • 9,501
  • 4
  • 38
  • 54
  • 3
    This worked for me in Python2. In Python3 it did not. It would pause the loop after rendering the plot window. But after moving the plt.show() method to after the loop... it resolved it for Python3, for me. – continuousqa Sep 05 '14 at 18:36
  • 1
    Weird, worked okay for me in Python 3 (ver 3.4.0) Matplotlib (ver 1.3.1) Numpy (ver 1.8.1) Ubuntu Linux 3.13.0 64-bit – Velimir Mlaker Sep 12 '14 at 14:58
  • this does not work with ipython='2.1.0', matplotlib='1.3.1'. the plot gets stuck – denfromufa Sep 25 '14 at 21:24
  • 41
    instead of plt.show() and plt.draw() just replace plt.draw() with plt.pause(0.1) – denfromufa Sep 25 '14 at 22:17
  • 5
    Did not work on Win64/Anaconda matplotlib.__version__ 1.5.0. An initial figure window opened, but did not display anything, it remained in a blocked state until I closed it – isti_spl Feb 04 '16 at 08:39
  • 1
    regarding `plt.pause()` vs `plt.draw()` and `time.sleep(0.05)`: draw/sleep still draws the plot but using draw/sleep prevents things like mouse resize window, mouse zoom, button events, any mouse interaction, etc. – Trevor Boyd Smith Apr 13 '16 at 17:43
  • 7
    This answer requires a-priori knowledge of the x/y data... which is not needed: I prefer 1. don't call `plt.axis()` but instead create two lists x and y and call `plt.plot(x,y)` 2. in your loop, append new data values to the two lists 3. call `plt.gca().lines[0].set_xdata(x); plt.gca().lines[0].set_ydata(y); plt.gca().relim(); plt.gca().autoscale_view(); plt.pause(0.05);` – Trevor Boyd Smith Apr 13 '16 at 18:09
  • with ipython 4.0.0 and matplotlib 1.5.1 copying and pasting this code doesn't work for me. – ryantuck Aug 16 '16 at 23:45
  • 1
    `plt.pause()` is not a wise choice since it triggers a full `draw()`. Use `time.sleep()` with `canvas.flush_events()` is much better. – kawing-chiu Jan 18 '17 at 06:22
  • 1
    I'd replace the last while loop, i.e. `while True: plt.pause(0.5)`, with `plt.ioff(); plt.show()`. – Causality Apr 14 '17 at 18:09
  • That's awesome. – cerebrou Apr 07 '20 at 15:54
  • and how would you add legends here ? – Madhur Yadav Sep 04 '20 at 11:21
  • You can also use `plt.clf()` after `plt.close()` to clear the previus figure to make the iteration faster – dimos geo Mar 14 '21 at 22:11
91

If you're interested in realtime plotting, I'd recommend looking into matplotlib's animation API. In particular, using blit to avoid redrawing the background on every frame can give you substantial speed gains (~10x):

#!/usr/bin/env python

import numpy as np
import time
import matplotlib
matplotlib.use('GTKAgg')
from matplotlib import pyplot as plt


def randomwalk(dims=(256, 256), n=20, sigma=5, alpha=0.95, seed=1):
    """ A simple random walk with memory """

    r, c = dims
    gen = np.random.RandomState(seed)
    pos = gen.rand(2, n) * ((r,), (c,))
    old_delta = gen.randn(2, n) * sigma

    while True:
        delta = (1. - alpha) * gen.randn(2, n) * sigma + alpha * old_delta
        pos += delta
        for ii in xrange(n):
            if not (0. <= pos[0, ii] < r):
                pos[0, ii] = abs(pos[0, ii] % r)
            if not (0. <= pos[1, ii] < c):
                pos[1, ii] = abs(pos[1, ii] % c)
        old_delta = delta
        yield pos


def run(niter=1000, doblit=True):
    """
    Display the simulation using matplotlib, optionally using blit for speed
    """

    fig, ax = plt.subplots(1, 1)
    ax.set_aspect('equal')
    ax.set_xlim(0, 255)
    ax.set_ylim(0, 255)
    ax.hold(True)
    rw = randomwalk()
    x, y = rw.next()

    plt.show(False)
    plt.draw()

    if doblit:
        # cache the background
        background = fig.canvas.copy_from_bbox(ax.bbox)

    points = ax.plot(x, y, 'o')[0]
    tic = time.time()

    for ii in xrange(niter):

        # update the xy data
        x, y = rw.next()
        points.set_data(x, y)

        if doblit:
            # restore background
            fig.canvas.restore_region(background)

            # redraw just the points
            ax.draw_artist(points)

            # fill in the axes rectangle
            fig.canvas.blit(ax.bbox)

        else:
            # redraw everything
            fig.canvas.draw()

    plt.close(fig)
    print "Blit = %s, average FPS: %.2f" % (
        str(doblit), niter / (time.time() - tic))

if __name__ == '__main__':
    run(doblit=False)
    run(doblit=True)

Output:

Blit = False, average FPS: 54.37
Blit = True, average FPS: 438.27
Nico Schlömer
  • 37,093
  • 21
  • 139
  • 189
ali_m
  • 62,795
  • 16
  • 193
  • 270
  • This looks nice, but where do you actually call "show" or display the graph? – skytaker Jan 30 '15 at 21:37
  • 1
    @bejota The original version was designed to work within an interactive matplotlib session. To make it work as a standalone script, it's necessary to 1) explicitly select a backend for matplotlib, and 2) to force the figure to be displayed and drawn before entering the animation loop using `plt.show()` and `plt.draw()`. I've added these changes to the code above. – ali_m Feb 02 '15 at 10:41
  • 3
    Is the intent/motivation of the `blit()` seems very much to be "improve real-time plotting"? If you have a matplotlib developer/blog discussing the why/purpose/intent/motivation that would be great. (seems like this new blit operation would convert Matplotlib from only use for offline or very slowly changing data to now you can use Matplotlib with very fast updating data... almost like an oscilloscope). – Trevor Boyd Smith Apr 14 '16 at 13:43
  • 1
    I have found that this approach makes the plot window unresponsive: I cannot interact with it, and doing so may crash it. – Ninjakannon Dec 29 '16 at 04:44
  • 'FigureCanvasMac' object has no attribute 'copy_from_bbox' – M.R.Karimi Apr 08 '17 at 20:19
  • 1
    For those getting "gtk not found" issue, it works fine with a different back-end (I used 'TKAgg'). To find a supported backed I used this solution: https://stackoverflow.com/questions/3285193/how-to-switch-backends-in-matplotlib-python – James Nelson Apr 26 '17 at 00:15
  • 2
    The link in this answer doesn't seem to work anymore. This might be an up-to-date link: http://scipy-cookbook.readthedocs.io/items/Matplotlib_Animations.html?highlight=animations – awelkie Jul 21 '17 at 14:00
51

I know I'm a bit late to answer this question. Nevertheless, I've made some code a while ago to plot live graphs, that I would like to share:

Code for PyQt4:

###################################################################
#                                                                 #
#                    PLOT A LIVE GRAPH (PyQt4)                    #
#                  -----------------------------                  #
#            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_())

''''''

 
I recently rewrote the code for PyQt5.
Code for PyQt5:

###################################################################
#                                                                 #
#                    PLOT A LIVE GRAPH (PyQt5)                    #
#                  -----------------------------                  #
#            EMBED A MATPLOTLIB ANIMATION INSIDE YOUR             #
#            OWN GUI!                                             #
#                                                                 #
###################################################################

import sys
import os
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import functools
import numpy as np
import random as rd
import matplotlib
matplotlib.use("Qt5Agg")
from matplotlib.figure import Figure
from matplotlib.animation import TimedAnimation
from matplotlib.lines import Line2D
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import time
import threading

class CustomMainWindow(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 = QFrame(self)
        self.FRAME_A.setStyleSheet("QWidget { background-color: %s }" % QColor(210,210,235,255).name())
        self.LAYOUT_A = QGridLayout()
        self.FRAME_A.setLayout(self.LAYOUT_A)
        self.setCentralWidget(self.FRAME_A)
        # Place the zoom button
        self.zoomBtn = QPushButton(text = 'zoom')
        self.zoomBtn.setFixedSize(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()
        return

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

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

''' 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)
        return

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

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

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

    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
        return

    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]
        return

''' 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(QObject):
    data_signal = 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 = QApplication(sys.argv)
    QApplication.setStyle(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, smoothly moving graph:

enter image description here

K.Mulier
  • 6,430
  • 9
  • 58
  • 110
  • I noticed that the `dataSendLoop` thread kept running in the background when you close the window. So I added the `daemon = True` keyword to solve that issue. – K.Mulier Sep 26 '16 at 09:21
  • 1
    The virtual environment for this took a bit of work. Finally, `conda install pyqt=4` did the trick. – Reb.Cabin Jun 29 '18 at 04:20
  • 3
    Thanks a lot for the basic code. It helped me to build up some simple UI by modifying and adding features around based on your code. It saved my time = ] – Isaac Sim Dec 18 '18 at 00:30
  • Hi @IsaacSim, thank you very much for your kind message. I'm happy this code was helpful :-) – K.Mulier Dec 18 '18 at 15:24
  • So i've taken this script and added timestamps to the x-axis by modifying the signal slot mechanism to use a np.ndarry type and emitting a np.array of the relative timestamp and signal. I'm updating the xlim() on each frame draw which does fine for displaying the signal with the new axis but not the x-labels/ticks only briefly update when I change the window size. @K.Mulier I'm basically after a sliding xtick axis like the data is and was wondering if you had any success on something like this? – nimig18 Nov 25 '19 at 03:47
  • Nvm, I found a pseudo fix just by calling the resize_event(). – nimig18 Nov 25 '19 at 04:05
  • @nimig18 Can you explain how you updated the xlim() on each frame? Can you please provide some code example? @ K.Mulier Can you please provide a screenshot of your QtDesigner GUI? It would be much appreciated! – Dipok Feb 10 '21 at 16:09
  • Hi @Dipok. I've never used **QtDesigner**, my aplogies. – K.Mulier Feb 12 '21 at 12:37
35

None of the methods worked for me. But I have found this Real time matplotlib plot is not working while still in a loop

All you need is to add

plt.pause(0.0001)

and then you could see the new plots.

So your code should look like this, and it will work

import matplotlib.pyplot as plt
import numpy as np
plt.ion() ## Note this correction
fig=plt.figure()
plt.axis([0,1000,0,1])

i=0
x=list()
y=list()

while i <1000:
    temp_y=np.random.random();
    x.append(i);
    y.append(temp_y);
    plt.scatter(i,temp_y);
    i+=1;
    plt.show()
    plt.pause(0.0001) #Note this correction
Oren
  • 2,355
  • 3
  • 23
  • 49
  • 8
    This opens a new figure / plot window every time for me is there a way to just update the existing figure ? maybe its becuase I am using imshow ? – Francisco Vargas Jan 26 '16 at 02:27
  • @FranciscoVargas if you are using imshow, you need to use set_data, look here: http://stackoverflow.com/questions/17835302/how-to-update-matplotlibs-imshow-window-interactively – Oren Dec 11 '16 at 07:14
34

show is probably not the best choice for this. What I would do is use pyplot.draw() instead. You also might want to include a small time delay (e.g., time.sleep(0.05)) in the loop so that you can see the plots happening. If I make these changes to your example it works for me and I see each point appearing one at a time.

BrenBarn
  • 210,788
  • 30
  • 364
  • 352
  • 13
    I have very similar part of code, and when I try your solution (draw instead of show and time delay) python does not open a figure window at all, just goes throught the loop... – George Aprilis Jan 31 '16 at 22:02
29

The top (and many other) answers were built upon plt.pause(), but that was an old way of animating the plot in matplotlib. It is not only slow, but also causes focus to be grabbed upon each update (I had a hard time stopping the plotting python process).

TL;DR: you may want to use matplotlib.animation (as mentioned in documentation).

After digging around various answers and pieces of code, this in fact proved to be a smooth way of drawing incoming data infinitely for me.

Here is my code for a quick start. It plots current time with a random number in [0, 100) every 200ms infinitely, while also handling auto rescaling of the view:

from datetime import datetime
from matplotlib import pyplot
from matplotlib.animation import FuncAnimation
from random import randrange

x_data, y_data = [], []

figure = pyplot.figure()
line, = pyplot.plot_date(x_data, y_data, '-')

def update(frame):
    x_data.append(datetime.now())
    y_data.append(randrange(0, 100))
    line.set_data(x_data, y_data)
    figure.gca().relim()
    figure.gca().autoscale_view()
    return line,

animation = FuncAnimation(figure, update, interval=200)

pyplot.show()

You can also explore blit for even better performance as in FuncAnimation documentation.

An example from the blit documentation:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

fig, ax = plt.subplots()
xdata, ydata = [], []
ln, = plt.plot([], [], 'ro')

def init():
    ax.set_xlim(0, 2*np.pi)
    ax.set_ylim(-1, 1)
    return ln,

def update(frame):
    xdata.append(frame)
    ydata.append(np.sin(frame))
    ln.set_data(xdata, ydata)
    return ln,

ani = FuncAnimation(fig, update, frames=np.linspace(0, 2*np.pi, 128),
                    init_func=init, blit=True)
plt.show()
Nav
  • 16,995
  • 26
  • 78
  • 120
Hai Zhang
  • 5,058
  • 40
  • 49
  • Hi, what will happen if this was all in a loop. say `for i in range(1000): x,y = some func_func()`. Here `some_func()` generates online `x,y` data pairs, which I would like to plot once they are available. Is it possible to do this with `FuncAnimation`. My goal is to build the curve defined by the data step by step with each iteration. – Alexander Cska Aug 23 '18 at 14:30
  • @Alexander Cska `pyploy.show()` should block. If you want to append data, retrieve them and update in the `update` function. – Hai Zhang Aug 23 '18 at 22:37
  • I fear that i don't really understand your reply. Would you amplify your suggestion please. – Alexander Cska Aug 24 '18 at 12:05
  • I mean, if you call `pyplot.show` in a loop, the loop will be blocked by this call and will not continue. If you want to append data to the curve step by step, put your logic in `update`, which will be called every `interval` so it's also step-by-step. – Hai Zhang Aug 24 '18 at 20:16
  • Zhang's code works from the console but not in jupyter. I just get a blank plot there. In fact, when i populate an array in jupyter in a sequential loop and print the array as it grows with a pet.plot statement, I can get a print out of the arrays individually but only one plot. see this code: https://gist.github.com/bwanaaa/12252cf36b35fced0eb3c2f64a76cb8a – aquagremlin Jan 30 '20 at 23:46
  • can it plot multiple data in the graph? – alper Dec 22 '20 at 15:20
16

I know this question is old, but there's now a package available called drawnow on GitHub as "python-drawnow". This provides an interface similar to MATLAB's drawnow -- you can easily update a figure.

An example for your use case:

import matplotlib.pyplot as plt
from drawnow import drawnow

def make_fig():
    plt.scatter(x, y)  # I think you meant this

plt.ion()  # enable interactivity
fig = plt.figure()  # make a figure

x = list()
y = list()

for i in range(1000):
    temp_y = np.random.random()
    x.append(i)
    y.append(temp_y)  # or any arbitrary update to your figure's data
    i += 1
    drawnow(make_fig)

python-drawnow is a thin wrapper around plt.draw but provides the ability to confirm (or debug) after figure display.

Scott
  • 2,308
  • 24
  • 36
8

Another option is to go with bokeh. IMO, it is a good alternative at least for real-time plots. Here is a bokeh version of the code in the question:

from bokeh.plotting import curdoc, figure
import random
import time

def update():
    global i
    temp_y = random.random()
    r.data_source.stream({'x': [i], 'y': [temp_y]})
    i += 1

i = 0
p = figure()
r = p.circle([], [])
curdoc().add_root(p)
curdoc().add_periodic_callback(update, 100)

and for running it:

pip3 install bokeh
bokeh serve --show test.py

bokeh shows the result in a web browser via websocket communications. It is especially useful when data is generated by remote headless server processes.

bokeh sample plot

Hamid Fadishei
  • 744
  • 2
  • 8
  • 16
7

The problem seems to be that you expect plt.show() to show the window and then to return. It does not do that. The program will stop at that point and only resume once you close the window. You should be able to test that: If you close the window and then another window should pop up.

To resolve that problem just call plt.show() once after your loop. Then you get the complete plot. (But not a 'real-time plotting')

You can try setting the keyword-argument block like this: plt.show(block=False) once at the beginning and then use .draw() to update.

Michael Mauderer
  • 3,361
  • 1
  • 21
  • 48
  • 1
    real-time plotting is really what I'm going for. I'm going to be running a 5 hour test on something and want to see how things are progressing. – Chris Aug 08 '12 at 23:48
  • @Chris were you able to conduct the 5 hour test? I am also looking for something similar. I am using plyplot.pause(time_duration) to update the plot. Is there any other way to do so? – Prakhar Mohan Srivastava Apr 11 '14 at 16:02
5

Here is a version that I got to work on my system.

import matplotlib.pyplot as plt
from drawnow import drawnow
import numpy as np

def makeFig():
    plt.scatter(xList,yList) # I think you meant this

plt.ion() # enable interactivity
fig=plt.figure() # make a figure

xList=list()
yList=list()

for i in np.arange(50):
    y=np.random.random()
    xList.append(i)
    yList.append(y)
    drawnow(makeFig)
    #makeFig()      The drawnow(makeFig) command can be replaced
    #plt.draw()     with makeFig(); plt.draw()
    plt.pause(0.001)

The drawnow(makeFig) line can be replaced with a makeFig(); plt.draw() sequence and it still works OK.

slehar
  • 301
  • 3
  • 6
5

An example use-case to plot CPU usage in real-time.

import time
import psutil
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(111)

i = 0
x, y = [], []

while True:
    x.append(i)
    y.append(psutil.cpu_percent())

    ax.plot(x, y, color='b')

    fig.canvas.draw()

    ax.set_xlim(left=max(0, i - 50), right=i + 50)
    fig.show()
    plt.pause(0.05)
    i += 1
Nilani Algiriyage
  • 22,706
  • 29
  • 77
  • 114
  • It really starts to slow down after about 2 minutes. What could the reason be? Perhaps earlier points, which fall outside the current view, should be dropped. – pfabri Apr 27 '20 at 12:05
  • This looks really nice, but there are a couple of problems with it: 1. it's impossible to quit 2. after just a few minutes the program consumes nearly 100 Mb of RAM and starts slowing down dramatically. – pfabri Apr 28 '20 at 14:45
1

If you want draw and not freeze your thread as more point are drawn you should use plt.pause() not time.sleep()

im using the following code to plot a series of xy coordinates.

import matplotlib.pyplot as plt 
import math


pi = 3.14159

fig, ax = plt.subplots()

x = []
y = []

def PointsInCircum(r,n=20):
    circle = [(math.cos(2*pi/n*x)*r,math.sin(2*pi/n*x)*r) for x in xrange(0,n+1)]
    return circle

circle_list = PointsInCircum(3, 50)

for t in range(len(circle_list)):
    if t == 0:
        points, = ax.plot(x, y, marker='o', linestyle='--')
        ax.set_xlim(-4, 4) 
        ax.set_ylim(-4, 4) 
    else:
        x_coord, y_coord = circle_list.pop()
        x.append(x_coord)
        y.append(y_coord)
        points.set_data(x, y)
    plt.pause(0.01)
user2672474
  • 129
  • 1
  • 5