Is there a way to detach matplotlib plots so that the computation can continue?

Viewed 322373

After these instructions in the Python interpreter one gets a window with a plot:

from matplotlib.pyplot import *
plot([1,2,3])
show()
# other code

Unfortunately, I don't know how to continue to interactively explore the figure created by show() while the program does further calculations.

Is it possible at all? Sometimes calculations are long and it would help if they would proceed during examination of intermediate results.

21 Answers

Use matplotlib's calls that won't block:

Using draw():

from matplotlib.pyplot import plot, draw, show
plot([1,2,3])
draw()
print('continue computation')

# at the end call show to ensure window won't close.
show()

Using interactive mode:

from matplotlib.pyplot import plot, ion, show
ion() # enables interactive mode
plot([1,2,3]) # result shows immediatelly (implicit draw())

print('continue computation')

# at the end call show to ensure window won't close.
show()

It is better to always check with the library you are using if it supports usage in a non-blocking way.

But if you want a more generic solution, or if there is no other way, you can run anything that blocks in a separated process by using the multprocessing module included in python. Computation will continue:

from multiprocessing import Process
from matplotlib.pyplot import plot, show

def plot_graph(*args):
    for data in args:
        plot(data)
    show()

p = Process(target=plot_graph, args=([1, 2, 3],))
p.start()

print 'yay'
print 'computation continues...'
print 'that rocks.'

print 'Now lets wait for the graph be closed to continue...:'
p.join()

That has the overhead of launching a new process, and is sometimes harder to debug on complex scenarios, so I'd prefer the other solution (using matplotlib's nonblocking API calls)

In my case, I wanted to have several windows pop up as they are being computed. For reference, this is the way:

from matplotlib.pyplot import draw, figure, show
f1, f2 = figure(), figure()
af1 = f1.add_subplot(111)
af2 = f2.add_subplot(111)
af1.plot([1,2,3])
af2.plot([6,5,4])
draw() 
print 'continuing computation'
show()

PS. A quite useful guide to matplotlib's OO interface.

The OP asks about detatching matplotlib plots. Most answers assume command execution from within a python interpreter. The use-case presented here is my preference for testing code in a terminal (e.g. bash) where a file.py is run and you want the plot(s) to come up but the python script to complete and return to a command prompt.

This stand-alone file uses multiprocessing to launch a separate process for plotting data with matplotlib. The main thread exits using the os._exit(1) mentioned in this post. The os._exit() forces main to exit but leaves the matplotlib child process alive and responsive until the plot window is closed. It's a separate process entirely.

This approach is a bit like a Matlab development session with figure windows that come up with a responsive command prompt. With this approach, you have lost all contact with the figure window process, but, that's ok for development and debugging. Just close the window and keep testing.

multiprocessing is designed for python-only code execution which makes it perhaps better suited than subprocess. multiprocessing is cross-platform so this should work well in Windows or Mac with little or no adjustment. There is no need to check the underlying operating system. This was tested on linux, Ubuntu 18.04LTS.

#!/usr/bin/python3

import time
import multiprocessing
import os

def plot_graph(data):
    from matplotlib.pyplot import plot, draw, show
    print("entered plot_graph()")
    plot(data)
    show() # this will block and remain a viable process as long as the figure window is open
    print("exiting plot_graph() process")

if __name__ == "__main__":
    print("starting __main__")
    multiprocessing.Process(target=plot_graph, args=([1, 2, 3],)).start()
    time.sleep(5)
    print("exiting main")
    os._exit(0) # this exits immediately with no cleanup or buffer flushing

Running file.py brings up a figure window, then __main__ exits but the multiprocessing + matplotlib figure window remains responsive with zoom, pan, and other buttons because it is an independent process.

Check the processes at the bash command prompt with:

ps ax|grep -v grep |grep file.py

Here is the simplest solution I found (thread blocking code)

plt.show(block=False) # this avoids blocking your thread

plt.pause(1) # comment this if you do not want a time delay

# do more stuff

plt.show(block=True) # this prevents the window from closing on you

While not directly answering OPs request, Im posting this workaround since it may help somebody in this situation:

  • Im creating an .exe with pyinstaller since I cannot install python where I need to generate the plots, so I need the python script to generate the plot, save it as .png, close it and continue with the next, implemented as several plots in a loop or using a function.

for this Im using:

import matplotlib.pyplot as plt
#code generating the plot in a loop or function
#saving the plot
plt.savefig(var+'_plot.png',bbox_inches='tight', dpi=250) 
#you can allways reopen the plot using
os.system(var+'_plot.png') # unfortunately .png allows no interaction.
#the following avoids plot blocking the execution while in non-interactive mode
plt.show(block=False) 
#and the following closes the plot while next iteration will generate new instance.
plt.close() 

Where "var" identifies the plot in the loop so it wont be overwritten.

What I have found as the best solution so the program does not wait for you to close the figure and have all your plots together so you can examine them side by side is to show all the plots at the end.
But this way you cannot examine the plots while program is running.

# stuff

numFig = 1

plt.figure(numFig)
numFig += 1
plt.plot(x1, y1)

# other stuff

plt.figure(numFig)
numFig += 1
plt.plot(x2, y2)

# more stuff

plt.show()

Use plt.show(block=False), and at the end of your script call plt.show().

This will ensure that the window won't be closed when the script is finished.

Related