17

In matplotlib, is there a simple way of plotting a figure without interrupting the control flow of the script?

Using pseudocode for clarity, here's what I'm trying to achieve:

fig1 = figure()
fig1.plot_a_figure(datasets)

for dataset in datasets:
   results = analyze(dataset)    # this takes several minutes
   update(fig1)
   pop_up_another_figure(results) # would like to have a look at this one
                                  # while the next dataset is being processed

Of course, I can just savefig() these intermediate figures, but I only need a quick glance at a them and it would be the best to have them just pop up on the screen in real time.

EDIT: A runnable example:

#!/usr/bin/python
import pylab as plb
import matplotlib.pyplot as plt

fig1=plt.figure(1)
ax = fig1.add_subplot(1,1,1)

ax.plot([1,2,3],[4,5,6],'ro-')

#fig1.show()  # this does not show a figure if uncommented
plt.show()    # until the plot window is closed, the next line is not executed

print "doing something else now"

Am I missing something very very basic?

ev-br
  • 24,968
  • 9
  • 65
  • 78
  • I thought `show()` did that already. You can create as many figures as you want, show() each of them and they will remain displayed while your code keeps running. Can you post a runnable example that demonstrates the problem? – Paul Feb 19 '11 at 13:31
  • @ Paul: added an example. Am I missing something trivial? – ev-br Feb 19 '11 at 13:49
  • Nope, you are definitely not missing anything trivial. I've just seen so many plots pop up simultaneously by accident, I figured there was some threading going on and that show() would not acquire a lock. Read this post: http://stackoverflow.com/questions/4659680/matplotlib-simultaneous-plotting-in-multiple-threads/4662511#4662511 – Paul Feb 20 '11 at 16:16
  • I can think of three ways to go: first, if you don't care that the plots are interactive, you can, like you suggested, save an image, but then quite easily launch a viewer of that image with `os.startfile()`. If you want it interactive and you don't mind writing the data to disk, you can `subprocess.Popen()` another python script. The third would be to try to truly understand how matplotlib manages threads.. – Paul Feb 20 '11 at 16:22

2 Answers2

17

First things first, don't forget a simple alternative is to just make new figure windows with plt.figure(2), plt.figure(3) etc. If you really want to update the existing figure window, you had better keep a handle on your lines object with

h = ax.plot([1,2,3],[4,5,6],'ro-')

And then later you would be doing something like:

h[0].set_data(some_new_results)
ax.figure.canvas.draw()

As for the real meat of the question, if you're still battling with this read on..


You need to enable interactive mode if you want plt.show() to be non-blocking. To modify your runnable example so that "doing something else now" would print immediately, as opposed to waiting for the figure window to be closed, the following would do:

#!/usr/bin/python
import pylab as plb
import matplotlib.pyplot as plt

fig1=plt.figure(1)
ax = fig1.add_subplot(1,1,1)

ax.plot([1,2,3],[4,5,6],'ro-')

#fig1.show()  # this does not show a figure if uncommented
plt.ion()     # turns on interactive mode
plt.show()    # now this should be non-blocking

print "doing something else now"
raw_input('Press Enter to continue...')

However, this is just scratching the surface of things - there are many complications once you start wanting to do background work while interacting with the plots. This is a natural consequence of painting with what's essentially a state machine, it doesn't rub well with threading and programming in an object-oriented environment.

  • Expensive calculations will have to go into worker threads (or alternatively into subprocesses) to avoid freezing the GUI.
  • Queue should be used to pass input data and get results out of the worker functions in a thread-safe way.
  • In my experience, it is not safe to call draw() in the worker thread so you also need to set up a way to schedule a repaint.
  • Different backends may start to do strange things and TkAgg seems to be the only one which works 100% (see here).

The easiest and best solution is not to use the vanilla python interpreter, but to use ipython -pylab (as ianalis has rightly suggested), because they have already figured out most of the tricks needed to get interactive stuff working smoothly. It can be done without ipython/pylab but it's a significant amount of extra work.

Note: I still often like to farm off worker threads whilst using ipython and pyplot GUI windows, and to get threading working smoothly I also need to use another commandline argument ipython -pylab -wthread. I'm on python 2.7.1+ with matplotlib v1.1.0, your mileage may vary. Hope this helps!

Note for Ubuntu users: The repositories are still back on v0.99 for quite some time now, it is worth upgrading your matplotlib because there were many improvements coming up to the v1.0 release including a Bugfix marathon, and major changes to the behaviour of show().

wim
  • 338,267
  • 99
  • 616
  • 750
  • tried your example with plt.ion(). Doesn't work for me, plt.show() is still blocking it. I'm on Ubuntu 10.04, python 2.6.5, matplotlib 0.99. Is that just way too old? – ev-br Sep 12 '11 at 17:02
  • Perhaps it is way too old: they have changed the implementation of `show()` quite significantly back for v1.0 ( see here http://matplotlib.sourceforge.net/users/whats_new.html#multiple-calls-to-show-supported ). What do you get if you move the `plt.ion()` line up to the top of the script, i.e. after the `import matplotlib.pyplot as plt`? And what is the result of calling function `matplotlib.get_backend()`? – wim Sep 12 '11 at 23:55
  • get_backend() returns TkAgg, and there's no change if the the plt.ion() is the first line of the script. – ev-br Sep 13 '11 at 12:29
  • Is `ax.figure.canvas.draw()` a blocking/synchronous call? I'm unable to find that information anywhere. If you know or have a reference that explains this, would appreciate it. – The Quantum Physicist Nov 30 '17 at 16:28
3

Probably the easiest solution is to use IPython as your python shell. Run it with the -pylab option.

ipython -pylab
Christian Alis
  • 6,556
  • 5
  • 31
  • 29