236

I am having problems trying to make matplotlib plot a function without blocking execution.

I have tried using show(block=False) as some people suggest, but all I get is a frozen window. If I simply call show(), the result is plotted properly but execution is blocked until the window is closed. From other threads I've read, I suspect that whether show(block=False) works or not depends on the backend. Is this correct? My backend is Qt4Agg. Could you have a look at my code and tell me if you see something wrong? Here is my code.

from math import *
from matplotlib import pyplot as plt
print(plt.get_backend())


def main():
    x = range(-50, 51, 1)
    for pow in range(1,5):   # plot x^1, x^2, ..., x^4

        y = [Xi**pow for Xi in x]
        print(y)

        plt.plot(x, y)
        plt.draw()
        #plt.show()             #this plots correctly, but blocks execution.
        plt.show(block=False)   #this creates an empty frozen window.
        _ = raw_input("Press [enter] to continue.")


if __name__ == '__main__':
    main()

PS. I forgot to say that I would like to update the existing window every time I plot something, instead of creating a new one.

tdy
  • 36,675
  • 19
  • 86
  • 83
opetroch
  • 3,929
  • 2
  • 22
  • 24
  • 5
    have you try matplotlib interactive mode with `plt.ion()` before `plt.show()`? It should then be non-blocking as each plot is spawned into a child thread. – Anzel Feb 01 '15 at 23:41
  • @Anzel I just tried it, but it seems to make no difference. – opetroch Feb 01 '15 at 23:52
  • 3
    How are you running your script? If I run your example code from the terminal/command prompt, it seems to work fine, but I think I've had trouble in the past when trying to do things like this from the IPython QtConsole or IDEs. – Marius Feb 02 '15 at 00:10
  • 1
    @Marius Aha!! You are right. Indeed I am running it from the console of my IDE (PyCharm). When running it from the cmd prompt, plt.show(block=False), works fine! Will I be asking too much if I ask you if you have found any idea/solution to that? Thanks a lot! – opetroch Feb 02 '15 at 00:19
  • I don't really know sorry. I don't really understand the details of how matplotlib interacts with the console, so I generally just switch to running from the command prompt if I need to do this stuff with `matplotlib`. – Marius Feb 02 '15 at 00:29
  • That's all right, I will be running from cmd prompt, thanks for your help again. – opetroch Feb 02 '15 at 00:35
  • Does this answer your question? [Is there a way to detach matplotlib plots so that the computation can continue?](https://stackoverflow.com/questions/458209/is-there-a-way-to-detach-matplotlib-plots-so-that-the-computation-can-continue) – ArchLinuxTux May 09 '20 at 10:08
  • @Arch Linux Tux, the answer is i don't know, haven't tried it. I had searched quite a bit before posting the question five years ago though. I have tried the accepted answer and it does work. – opetroch May 09 '20 at 10:31

9 Answers9

250

I spent a long time looking for solutions, and found this answer.

It looks like, in order to get what you (and I) want, you need the combination of plt.ion(), plt.show() (not with block=False) and, most importantly, plt.pause(.001) (or whatever time you want). The pause is needed because the GUI events happen while the main code is sleeping, including drawing. It's possible that this is implemented by picking up time from a sleeping thread, so maybe IDEs mess with that—I don't know.

Here's an implementation that works for me on python 3.5:

import numpy as np
from matplotlib import pyplot as plt

def main():
    plt.axis([-50,50,0,10000])
    plt.ion()
    plt.show()

    x = np.arange(-50, 51)
    for pow in range(1,5):   # plot x^1, x^2, ..., x^4
        y = [Xi**pow for Xi in x]
        plt.plot(x, y)
        plt.draw()
        plt.pause(0.001)
        input("Press [enter] to continue.")

if __name__ == '__main__':
    main()
ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
krs013
  • 2,799
  • 1
  • 17
  • 17
  • 6
    Your answer helped me a lot in solving a similar issue I was having. Previously, I had `plt.draw` followed by `plt.show(block = False)` but then it stopped working: Figure not responding, closing it crashed iPython. My solution was removing every instance of `plt.draw()` and replacing it with `plt.pause(0.001)`. Instead of having it followed by `plt.show(block = False)` like `plt.draw` was before, it was preceded by `plt.ion()` and `plt.show()`. I now have a `MatplotlibDeprecationWarning` but it let me plot my figures, so I'm happy with this solution. – blue_chip Mar 08 '16 at 06:06
  • 3
    Note that in python 2.7, you need to use `raw_input` not `input`. See [here](http://stackoverflow.com/questions/21122540/python-input-error-nameerror-name-is-not-defined) – Chris Aug 17 '16 at 23:11
  • Really useful workaround when the reactive "animate" approach is not possible! Anyone know how to get rid of the deprecation warning? – Frederic Fortier Sep 05 '17 at 00:22
  • Please can someone tell me why i get a freezen command prompt when I try to add plt.ion before plt.show? – Gabriel Augusto Nov 23 '17 at 17:54
  • @GabrielAugusto I'm not sure what could cause that, and I'm not sure quite what you mean. I just tested this example in Python 3.6 and it still works. If you used the same pattern and it freezes, there might be something wrong with your installation. You should check if normal plotting works first. If you tried something different, there's not a lot to do about it in the comments. In either case, you might consider asking a separate question. – krs013 Nov 27 '17 at 23:23
  • @krs013 How should I edit this code such that the figure does not close at the end and that at the same time the rest of the code is executed while the matplotlib figure window is still open? – Adriaan May 23 '18 at 10:00
  • @Adriaan The line `input("Press [enter] to continue.")` will pause the program before continuing; you can put that at the end of the program to stop it before closing. The window will close when the program exits. If you want to do something more elaborate than that allows, you may want to consider asking a new question. – krs013 May 27 '18 at 05:51
  • 3
    I had to increase the pause to 0.1 seconds for this to work – Eddy Nov 15 '19 at 10:06
  • 2
    @krs013: I remember this worked for me in the past. But, now this works without using either `plt.ion()` or `plt.draw()`. I don't know if something has changed in matplotlib versions (I am using the latest version `3.1.2`) – s.ouchene Dec 24 '19 at 11:36
  • If you're looping and want each successive plot to replace the previous one, put ```fig = plt.figure()``` in the outer scope – Patrick Gorman Apr 30 '22 at 04:55
45

A simple trick that works for me is the following:

  1. Use the block = False argument inside show: plt.show(block = False)
  2. Use another plt.show() at the end of the .py script.

Example:

import matplotlib.pyplot as plt

plt.imshow(add_something)
plt.xlabel("x")
plt.ylabel("y")

plt.show(block=False)

#more code here (e.g. do calculations and use print to see them on the screen

plt.show()

Note: plt.show() is the last line of my script.

seralouk
  • 30,938
  • 9
  • 118
  • 133
  • 18
    This produces (for me, on Linux, Anaconda, Python 2.7, default backend) a **blank** window which remains blank until the very end of execution, when it finally gets filled in. Not useful for updating a plot in the midst of execution. :-( – sh37211 May 04 '17 at 03:05
  • @sh37211 Not sure what your goal. In some cases that you try to plot something but after the plot command you have other commands, then this is useful since it allows you to plot and get the other commands executed. See this post for more on this: http://stackoverflow.com/questions/458209/is-there-a-way-to-detach-matplotlib-plots-so-that-the-computation-can-continue/42674200#comment74050888_42674200. If you want to update a plot then it should be another way. – seralouk May 04 '17 at 13:48
  • 1
    @sh37211 I get the same symptom (windows10/python 3.8.7, & Linux mint/python 3.8.5) using just plt.show(block=False). In both cases I get the desired plot update using plt.show(block=False);plt.pause(0.01). – marzetti Mar 31 '21 at 01:53
  • only method worked for me like a charm – Albert G Lieu Dec 17 '21 at 00:34
  • Same as @sh37211 on Windows(10). I didn't need the last `plt.show()` Still, an interesting solution. Thank you. – El Bachir Dec 20 '21 at 00:47
  • Initially this did not work for me. I had the first screen of charts show but then nothing. I had to repeat code setting any variables again after plt.show(block=False), then it worked and a second screen with charts was produced. I suspect plt.show(block=False) clears any previous variables relating to the plot set in the code so you have to set them again (Though I did not have to reload the yfinance imported data variables). – mdkb Jul 05 '22 at 05:39
21

You can avoid blocking execution by writing the plot to an array, then displaying the array in a different thread. Here is an example of generating and displaying plots simultaneously using pf.screen from pyformulas 0.2.8:

import pyformulas as pf
import matplotlib.pyplot as plt
import numpy as np
import time

fig = plt.figure()

canvas = np.zeros((480,640))
screen = pf.screen(canvas, 'Sinusoid')

start = time.time()
while True:
    now = time.time() - start

    x = np.linspace(now-2, now, 100)
    y = np.sin(2*np.pi*x) + np.sin(3*np.pi*x)
    plt.xlim(now-2,now+1)
    plt.ylim(-3,3)
    plt.plot(x, y, c='black')

    # If we haven't already shown or saved the plot, then we need to draw the figure first...
    fig.canvas.draw()

    image = np.fromstring(fig.canvas.tostring_rgb(), dtype=np.uint8, sep='')
    image = image.reshape(fig.canvas.get_width_height()[::-1] + (3,))

    screen.update(image)

#screen.close()

Result:

Sine animation

Disclaimer: I'm the maintainer for pyformulas.

Reference: Matplotlib: save plot to numpy array

Default picture
  • 710
  • 5
  • 12
15

Live Plotting

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0, 2 * np.pi, 100)
# plt.axis([x[0], x[-1], -1, 1])      # disable autoscaling
for point in x:
    plt.plot(point, np.sin(2 * point), '.', color='b')
    plt.draw()
    plt.pause(0.01)
# plt.clf()                           # clear the current figure

if the amount of data is too much you can lower the update rate with a simple counter

cnt += 1
if (cnt == 10):       # update plot each 10 points
    plt.draw()
    plt.pause(0.01)
    cnt = 0

Holding Plot after Program Exit

This was my actual problem that couldn't find satisfactory answer for, I wanted plotting that didn't close after the script was finished (like MATLAB),

If you think about it, after the script is finished, the program is terminated and there is no logical way to hold the plot this way, so there are two options

  1. block the script from exiting (that's plt.show() and not what I want)
  2. run the plot on a separate thread (too complicated)

this wasn't satisfactory for me so I found another solution outside of the box

SaveToFile and View in external viewer

For this the saving and viewing should be both fast and the viewer shouldn't lock the file and should update the content automatically

Selecting Format for Saving

vector based formats are both small and fast

  • SVG is good but coudn't find good viewer for it except the web browser which by default needs manual refresh
  • PDF can support vector formats and there are lightweight viewers which support live updating

Fast Lightweight Viewer with Live Update

For PDF there are several good options

  • On Windows I use SumatraPDF which is free, fast and light (only uses 1.8MB RAM for my case)

  • On Linux there are several options such as Evince (GNOME) and Ocular (KDE)

Sample Code & Results

Sample code for outputing plot to a file

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0, 2 * np.pi, 100)
y = np.sin(2 * x)
plt.plot(x, y)
plt.savefig("fig.pdf")

after first run, open the output file in one of the viewers mentioned above and enjoy.

Here is a screenshot of VSCode alongside SumatraPDF, also the process is fast enough to get semi-live update rate (I can get near 10Hz on my setup just use time.sleep() between intervals) pyPlot,Non-Blocking

Ali80
  • 6,333
  • 2
  • 43
  • 33
13

A lot of these answers are super inflated and from what I can find, the answer isn't all that difficult to understand.

You can use plt.ion() if you want, but I found using plt.draw() just as effective

For my specific project I'm plotting images, but you can use plot() or scatter() or whatever instead of figimage(), it doesn't matter.

plt.figimage(image_to_show)
plt.draw()
plt.pause(0.001)

Or

fig = plt.figure()
...
fig.figimage(image_to_show)
fig.canvas.draw()
plt.pause(0.001)

If you're using an actual figure.
I used @krs013, and @Default Picture's answers to figure this out
Hopefully this saves someone from having launch every single figure on a separate thread, or from having to read these novels just to figure this out

iggy12345
  • 1,233
  • 12
  • 31
  • When using this in a loop ( to update the image constantly) I noticed bad executions times. Adding plt.clf() , clearing all the drawn images at each iterations seems to fix the problem. – Axl Apr 13 '21 at 02:18
  • 2
    That is because not clearing the image is a memory leak and will cause matplotlib to try and plot all of your existing iterations which will slow down your execution – iggy12345 Apr 13 '21 at 12:10
  • This worked for me in VS Code. Though, it wouldn't let me close any of the plots by clicking on the X for some reason. – swimfar2 Mar 06 '23 at 17:45
8

I figured out that the plt.pause(0.001) command is the only thing needed and nothing else.

plt.show() and plt.draw() are unnecessary and / or blocking in one way or the other. So here is a code that draws and updates a figure and keeps going. Essentially plt.pause(0.001) seems to be the closest equivalent to matlab's drawnow.

Unfortunately those plots will not be interactive (they freeze), except you insert an input() command, but then the code will stop.

The documentation of the plt.pause(interval) command states:

If there is an active figure, it will be updated and displayed before the pause...... This can be used for crude animation.

and this is pretty much exactly what we want. Try this code:

import numpy as np
from matplotlib import pyplot as plt

x = np.arange(0, 51)               # x coordinates  
         
for z in range(10, 50):

    y = np.power(x, z/10)          # y coordinates of plot for animation

    plt.cla()                      # delete previous plot
    plt.axis([-50, 50, 0, 10000])  # set axis limits, to avoid rescaling
    plt.plot(x, y)                 # generate new plot
    plt.pause(0.1)                 # pause 0.1 sec, to force a plot redraw

Jim
  • 1,579
  • 1
  • 11
  • 18
3

Iggy's answer was the easiest for me to follow, but I got the following error when doing a subsequent subplot command that was not there when I was just doing show:

MatplotlibDeprecationWarning: Adding an axes using the same arguments as a previous axes currently reuses the earlier instance. In a future version, a new instance will always be created and returned. Meanwhile, this warning can be suppressed, and the future behavior ensured, by passing a unique label to each axes instance.

In order to avoid this error, it helps to close (or clear) the plot after the user hits enter.

Here's the code that worked for me:

def plt_show():
    '''Text-blocking version of plt.show()
    Use this instead of plt.show()'''
    plt.draw()
    plt.pause(0.001)
    input("Press enter to continue...")
    plt.close()
Pro Q
  • 4,391
  • 4
  • 43
  • 92
2

The Python package drawnow allows to update a plot in real time in a non blocking way.
It also works with a webcam and OpenCV for example to plot measures for each frame.
See the original post.

Ismael EL ATIFI
  • 1,939
  • 20
  • 16
0

Substitute the backend of matplotlib can solve my problem.
Write the bellow command before import matplotlib.pyplot as plt.
Substitute backend command should run first.

import matplotlib
matplotlib.use('TkAgg')

My answer come from Pycharm does not show plot

Green_John
  • 11
  • 1