A blocking, interactive plot in Jupyter notebook - python

I am trying to get an interactive, blocking matplotlib window out of Jupyter notebook. That is, I want the matplotlib window to come up and for execution in the notebook to pause until it closes. But various, reasonable-seeming permutations of my code don't seem to work.
The following produces the expected result:
%matplotlib
import matplotlib.pyplot as plt
a=[1,2,3]
b=[4,5,6]
plt.figure()
plt.plot(a,b)
plt.show(block=True)
print("hi")
But only once. If the code is run the second time, the kernel seems to lock up and I have to restart it, which is a no-go for my application.
The following alternatives produce an interactive window, but the code proceeds directly to the print statement without waiting for the window to be closed:
%matplotlib
import matplotlib.pyplot as plt
a=[1,2,3]
b=[4,5,6]
plt.figure()
plt.plot(a,b)
plt.show()
print("hi")
I get the same result from:
%matplotlib
import matplotlib.pyplot as plt
a=[1,2,3]
b=[4,5,6]
plt.figure()
plt.plot(a,b)
plt.ioff()
plt.show()
print("hi")
and
%matplotlib
import matplotlib.pyplot as plt
a=[1,2,3]
b=[4,5,6]
plt.figure()
plt.plot(a,b)
plt.ion()
plt.show()
print("hi")
How can I accomplish this goal? (The goal being to have print("hi") not execute until after the interactive matplotlib window closes.)
(I'm using Python 3.5.3 and Jupyter notebook server 5.0.0.)

I guess you cannot block the execution of a cell in the middle. However the usecase described in the comments seems to allow to process everything within the event loop of the figure itself.
# cell 1:
%matplotlib
import matplotlib.pyplot as plt
import numpy as np
class InterAct():
def __init__(self):
self.fig, self.ax = plt.subplots()
self.ax.axis([0,1,0,1])
self.ax.set_title("Click to choose points, close to proceed")
self.plot, = self.ax.plot([],[], color="crimson", ls="", marker="o")
self.points = []
self.fig.canvas.mpl_connect("button_press_event", self.select_point)
self.fig.canvas.mpl_connect("close_event", self.process)
plt.show()
def select_point(self, event):
self.points.append((event.xdata,event.ydata))
x,y = list(zip(*self.points))
self.plot.set_data(x,y)
self.fig.canvas.draw_idle()
def process(self, event):
points = np.array(self.points)
mean = points.mean(axis=0)
r = np.sqrt(np.sum((points-mean)**2, axis=1)).max()
self.fig2, self.ax2 = plt.subplots()
self.ax2.axis([0,1,0,1])
self.ax2.set_title("The result is:")
poly = plt.Polygon(points, edgecolor="C0", fill=True, alpha=0.5)
circ = plt.Circle(mean, r, color="crimson", fill=False)
self.ax2.add_patch(circ)
self.ax2.add_patch(poly)
self.fig2.show()
#plt.show()
And then
# cell 2
i = InterAct()
This would first show a matplotlib figure, where the user can interactively do something (in this case click to select points). Then when the user closes the figure, the points are processed and a new figure with the result is shown.

In case anybody looks for "blocking execution" until window is closed, here is a simple solution. Put this code in a cell.
# Provided the backend is interactive
# e.g. %matplotlib qt5
fig = plt.figure()
plt.show()
try:
while fig.number in plt.get_fignums():
plt.pause(0.1)
except:
plt.close(fig.number)
raise
print("hi!")
The loop in this snippet waits until the current figure is in the list of active figures. When a user closes the figure's window, its number disappears from the list returned by plt.get_fignums().
To make the cell interruptable the snippet catches exceptions. When the user stops the cell by Interrupt the kernel (aka Stop) button then KeyboardInterrupt error is injected into the Python event loop. The snippet catches it and closes the figure fig.
The duration in plt.pause defines delay in the script reaction. The bigger this value is, the longer is the delay between closing the window and printing hi!
The cell could be re-executed any number of times.

Related

FuncAnimation doesn't display animation

I have code that is running on a different machine.
%matplotlib widget
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
n = 100
x = np.random.randn(n)
def update(curr):
if curr == n:
a.event_source.stop()
plt.cla()
bins = np.arange(-4, 4, 0.5)
plt.hist(x[:curr], bins=bins)
plt.axis([-4,4,0,30])
plt.gca().set_title('Sampling the Normal Distribution')
plt.gca().set_ylabel('Frequency')
plt.gca().set_xlabel('Value')
plt.annotate('n = {}'.format(curr), [3,27])
a = animation.FuncAnimation(plt.figure(), update, interval=100)
plt.show()
However, it gives me this every time
And some times: "UserWarning: Animation was deleted without rendering anything. This is most likely not intended. To prevent deletion, assign the Animation to a variable, e.g. anim, that exists until you output the Animation using plt.show() or anim.save()."
I installed ipympl, restarted kernel, IDE, computer, removed "%matplotlib widget", but all this didn't help.
I hope you'll give me a hand
UPDATE:
I checked several examples of working code and found out 2 things:
All code including this one generates the correct animation, if you save it in any format (mp4, html), you can see it
If the animation is saved, then plt.show() will show the last frame, if not, then as in the picture above
I was having the same problem, and the only workaround I've found is to save the animation and then read it back from the file:
from IPython.display import Image
f = r'test_animation.gif'
writergif = animation.PillowWriter(fps=10)
a.save(f,writer=writergif)
plt.close()
Image(open('test_animation.gif','rb').read())

update matplotlib scatter data [duplicate]

I am trying to automatically update a scatter plot.
The source of my X and Y values is external, and the data is pushed automatically into my code in a non-predicted time intervals (rounds).
I have only managed to plot all the data when the whole process ended, whereas I am trying to constantly add and plot data into my canvas.
What I DO get (at the end of the whole run) is this:
Whereas, what I am after is this:
A simplified version of my code:
import matplotlib.pyplot as plt
def read_data():
#This function gets the values of xAxis and yAxis
xAxis = [some values] #these valuers change in each run
yAxis = [other values] #these valuers change in each run
plt.scatter(xAxis,yAxis, label = 'myPlot', color = 'k', s=50)
plt.xlabel('x')
plt.ylabel('y')
plt.show()
There are several ways to animate a matplotlib plot. In the following let's look at two minimal examples using a scatter plot.
(a) use interactive mode plt.ion()
For an animation to take place we need an event loop. One way of getting the event loop is to use plt.ion() ("interactive on"). One then needs to first draw the figure and can then update the plot in a loop. Inside the loop, we need to draw the canvas and introduce a little pause for the window to process other events (like the mouse interactions etc.). Without this pause the window would freeze. Finally we call plt.waitforbuttonpress() to let the window stay open even after the animation has finished.
import matplotlib.pyplot as plt
import numpy as np
plt.ion()
fig, ax = plt.subplots()
x, y = [],[]
sc = ax.scatter(x,y)
plt.xlim(0,10)
plt.ylim(0,10)
plt.draw()
for i in range(1000):
x.append(np.random.rand(1)*10)
y.append(np.random.rand(1)*10)
sc.set_offsets(np.c_[x,y])
fig.canvas.draw_idle()
plt.pause(0.1)
plt.waitforbuttonpress()
(b) using FuncAnimation
Much of the above can be automated using matplotlib.animation.FuncAnimation. The FuncAnimation will take care of the loop and the redrawing and will constantly call a function (in this case animate()) after a given time interval. The animation will only start once plt.show() is called, thereby automatically running in the plot window's event loop.
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
fig, ax = plt.subplots()
x, y = [],[]
sc = ax.scatter(x,y)
plt.xlim(0,10)
plt.ylim(0,10)
def animate(i):
x.append(np.random.rand(1)*10)
y.append(np.random.rand(1)*10)
sc.set_offsets(np.c_[x,y])
ani = matplotlib.animation.FuncAnimation(fig, animate,
frames=2, interval=100, repeat=True)
plt.show()
From what I understand, you want to update interactively your plot. If so, you can use plot instead of scatter plot and update the data of your plot like this.
import numpy
import matplotlib.pyplot as plt
fig = plt.figure()
axe = fig.add_subplot(111)
X,Y = [],[]
sp, = axe.plot([],[],label='toto',ms=10,color='k',marker='o',ls='')
fig.show()
for iter in range(5):
X.append(numpy.random.rand())
Y.append(numpy.random.rand())
sp.set_data(X,Y)
axe.set_xlim(min(X),max(X))
axe.set_ylim(min(Y),max(Y))
raw_input('...')
fig.canvas.draw()
If this is the behaviour your are looking for, you just need to create a function appending the data of sp, and get in that function the new points you want to plot (either with I/O management or whatever the communication process you're using).
I hope it helps.

python matplotlib update scatter plot from a function

I am trying to automatically update a scatter plot.
The source of my X and Y values is external, and the data is pushed automatically into my code in a non-predicted time intervals (rounds).
I have only managed to plot all the data when the whole process ended, whereas I am trying to constantly add and plot data into my canvas.
What I DO get (at the end of the whole run) is this:
Whereas, what I am after is this:
A simplified version of my code:
import matplotlib.pyplot as plt
def read_data():
#This function gets the values of xAxis and yAxis
xAxis = [some values] #these valuers change in each run
yAxis = [other values] #these valuers change in each run
plt.scatter(xAxis,yAxis, label = 'myPlot', color = 'k', s=50)
plt.xlabel('x')
plt.ylabel('y')
plt.show()
There are several ways to animate a matplotlib plot. In the following let's look at two minimal examples using a scatter plot.
(a) use interactive mode plt.ion()
For an animation to take place we need an event loop. One way of getting the event loop is to use plt.ion() ("interactive on"). One then needs to first draw the figure and can then update the plot in a loop. Inside the loop, we need to draw the canvas and introduce a little pause for the window to process other events (like the mouse interactions etc.). Without this pause the window would freeze. Finally we call plt.waitforbuttonpress() to let the window stay open even after the animation has finished.
import matplotlib.pyplot as plt
import numpy as np
plt.ion()
fig, ax = plt.subplots()
x, y = [],[]
sc = ax.scatter(x,y)
plt.xlim(0,10)
plt.ylim(0,10)
plt.draw()
for i in range(1000):
x.append(np.random.rand(1)*10)
y.append(np.random.rand(1)*10)
sc.set_offsets(np.c_[x,y])
fig.canvas.draw_idle()
plt.pause(0.1)
plt.waitforbuttonpress()
(b) using FuncAnimation
Much of the above can be automated using matplotlib.animation.FuncAnimation. The FuncAnimation will take care of the loop and the redrawing and will constantly call a function (in this case animate()) after a given time interval. The animation will only start once plt.show() is called, thereby automatically running in the plot window's event loop.
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
fig, ax = plt.subplots()
x, y = [],[]
sc = ax.scatter(x,y)
plt.xlim(0,10)
plt.ylim(0,10)
def animate(i):
x.append(np.random.rand(1)*10)
y.append(np.random.rand(1)*10)
sc.set_offsets(np.c_[x,y])
ani = matplotlib.animation.FuncAnimation(fig, animate,
frames=2, interval=100, repeat=True)
plt.show()
From what I understand, you want to update interactively your plot. If so, you can use plot instead of scatter plot and update the data of your plot like this.
import numpy
import matplotlib.pyplot as plt
fig = plt.figure()
axe = fig.add_subplot(111)
X,Y = [],[]
sp, = axe.plot([],[],label='toto',ms=10,color='k',marker='o',ls='')
fig.show()
for iter in range(5):
X.append(numpy.random.rand())
Y.append(numpy.random.rand())
sp.set_data(X,Y)
axe.set_xlim(min(X),max(X))
axe.set_ylim(min(Y),max(Y))
raw_input('...')
fig.canvas.draw()
If this is the behaviour your are looking for, you just need to create a function appending the data of sp, and get in that function the new points you want to plot (either with I/O management or whatever the communication process you're using).
I hope it helps.

Plotting to browser continuously using serve_figure

I want to see plots in progress continuously driven by the plot program using browser whenever it is connected. I searched and found serve_figure.py examples that are similar to what I need. But I cant get the following test code to work. Serve_figure.py holds up the for-loop after the first plot. At the browser only the first plot is shown. I don't need the mouse event in serve_figure.py. If there is another way to do this will be most welcome.
#!/usr/bin/env pythonnter
import serve_figure
import time
import numpy as np
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
def animate():
x = np.arange(0, 2*np.pi, 0.01)
line, = ax.plot(x, np.sin(x))
for i in np.arange(1,200):
line.set_ydata(np.sin(x+i/10.0))
fig.canvas.draw()
time.sleep(1)
serve_figure.serve_figure(fig, port=8888)
win = fig.canvas.manager.window
fig.canvas.manager.window.after(200, animate)
plt.show()
BTW, the link to serve_figure.py is
https://github.com/mdboom/mpl_browser_experiments/blob/master/serve_figure.py

Is there a way to use the pan/zoom tool when a matplotlib script is running?

I am running a Python script that updates a plot in matplotlib every few seconds. The calculations take several minutes and I would like to be able to pan and zoom the plot in the usual way while it is updating. Is this possible?
Failing that, is it possible to interrupt the script (canceling the rest of the calculation) and then pan/zoom the plot?
I have made the following example. The plot updates very nicely, but you cannot use the pan/zoom tool.
import numpy as np
import matplotlib.pyplot as plt
import time
def time_consuming_calculation():
time.sleep(0.001)
return np.random.normal()
ax = plt.subplot(111)
plt.ion()
plt.show(block=False)
bins = np.linspace(-4,4,100)
data = []
for i in range(0,10000):
print 'Iteration % 4i'%i
data.append(time_consuming_calculation())
if i%1000==0:
n,bin_edges = np.histogram(data,bins=bins)
if i == 0:
line, = plt.plot(bin_edges[:-1],n)
else:
line.set_data(bin_edges[:-1],n)
ax.relim() # Would need to disable this if we can use pan/zoom tool
ax.autoscale()
plt.draw()
plt.ioff()
plt.show()

Categories