Matplotlib ArtistAnimation: Plot entire figure in each step - python

I have an existing function I use for plotting, which I call repeatedly in my program.
I want to use matplotlib's ArtistAnimation to save each plot as an "artist" that is shown in one step of the animation.
I know how to use ArtistAnimation to show individual elements of the plot in the animation, but not the entire plot.
Here's a simplified example:
import random
def my_plot():
fig, ax = plt.subplots()
ax.plot([random.randrange(10), random.randrange(10)], [random.randrange(10), random.randrange(10)])
ax.plot([random.randrange(10), random.randrange(10)], [random.randrange(10), random.randrange(10)])
plt.show()
return ax
ims = []
fig = plt.figure()
for _ in range(5):
ax = my_plot()
ims.append((ax,))
ani = animation.ArtistAnimation(fig, ims, repeat=False)
ani.save('im.mp4', metadata={'artist':'Guido'})
This runs without error, but the resulting video is just blank. The same happens if I return a list of the artists created by ax.plot().
I assume the problem is that I'm calling plt.figure/plt.subfigure multiple times. But I'm not sure how to avoid that. Do I need to create one figure up front and pass that to each call of my_plot? Seems a bit ugly.

Instead of saving the axes, you need to save the plots as a list. (Or maybe you don't want to do this and want to save the axes? If that's the case, let me know and I'll delete this. I don't think saving the axes will work though, since the animation works by setting the saved items within a figure visible and invisible, and neither the axes nor the figure will hide/reveal a subset of the plots for each frame in this way.)
import matplotlib.pyplot as plt
from matplotlib import animation
import random
def my_plot(ax):
p0, = ax.plot([random.randrange(10), random.randrange(10)], [random.randrange(10), random.randrange(10)])
p1, = ax.plot([random.randrange(10), random.randrange(10)], [random.randrange(10), random.randrange(10)])
return [p0, p1] # return a list of the new plots
ims = []
fig = plt.figure()
ax = fig.add_subplot(111) # fig and axes created once
for _ in range(10):
ps = my_plot(ax)
ims.append(ps) # append the new list of plots
ani = animation.ArtistAnimation(fig, ims, repeat=False)
ani.save('im.mp4', metadata={'artist':'Guido'})
GIF below, but here is some vertical spacing so you can scroll the annoying flashing lines of the page while reading the code
. . . . . . . . . . . . . .

Thanks to tom's answer, I found the main reasons why my animations didn't work and only showed the first frame: I called plt.show() in each iteration. Apparently, after the first call, the animations stop working. Removing plt.show() and only creating one figure solved the problem:
import matplotlib.pyplot as plt
from matplotlib import animation
import random
def my_plot():
patch = []
patch.extend(plot([random.randrange(10), random.randrange(10)], [random.randrange(10), random.randrange(10)]))
patch.extend(plt.plot([random.randrange(10), random.randrange(10)], [random.randrange(10), random.randrange(10)]))
# no plt.show() here!
return patch
ims = []
fig = plt.figure() # fig created only once
for _ in range(10):
patch = my_plot()
ims.append(patch)
ani = animation.ArtistAnimation(fig, ims, repeat=False)
ani.save('im.mp4', metadata={'artist':'Guido'})
Not sure how I could both plot and show the plots directly and create an animation. Maybe using plt.draw() instead? But that doesn't show anything in my PyCharm IDE...
Anyways, I can live with either or.

Related

Python Matplotlib Update Plot in the Background

I am using Matplotlib to plot a real time event in Anaconda prompt.
When I update plot by plt.draw() or plt.show(), I loose control of the thing I am doing. Plot window acts like its clicked and this blocks my other control on the command prompt.
I tried adding
plt.show(block=False)
but it didnt help.
The code is like below,
fig, ax = plt.subplots()
plt.ion()
plt.show(block=False)
while(True):
ax.plot(y_plt_points,x_plt_points,'ro')
plt.draw()
plt.pause(0.01)
This link has an example of real time plotting with matplotlib. I think the main takeaway is that you don't need to use plt.show() or plt.draw() on every call to plot. The example uses set_ydata instead. Simalarly set_xdata can be used to update your x_axis variables. Code below
import matplotlib.pyplot as plt
import numpy as np
# use ggplot style for more sophisticated visuals
plt.style.use('ggplot')
def live_plotter(x_vec,y1_data,line1,identifier='',pause_time=0.1):
if line1==[]:
# this is the call to matplotlib that allows dynamic plotting
plt.ion()
fig = plt.figure(figsize=(13,6))
ax = fig.add_subplot(111)
# create a variable for the line so we can later update it
line1, = ax.plot(x_vec,y1_data,'-o',alpha=0.8)
#update plot label/title
plt.ylabel('Y Label')
plt.title('Title: {}'.format(identifier))
plt.show()
# after the figure, axis, and line are created, we only need to update the y-data
line1.set_ydata(y1_data)
# adjust limits if new data goes beyond bounds
if np.min(y1_data)<=line1.axes.get_ylim()[0] or np.max(y1_data)>=line1.axes.get_ylim()[1]:
plt.ylim([np.min(y1_data)-np.std(y1_data),np.max(y1_data)+np.std(y1_data)])
# this pauses the data so the figure/axis can catch up - the amount of pause can be altered above
plt.pause(pause_time)
# return line so we can update it again in the next iteration
return line1
When I run this function on the example below I don't have any trouble using other applications on my computer
size = 100
x_vec = np.linspace(0,1,size+1)[0:-1]
y_vec = np.random.randn(len(x_vec))
line1 = []
i=0
while i<1000:
i=+1
rand_val = np.random.randn(1)
y_vec[-1] = rand_val
line1 = live_plotter(x_vec,y_vec,line1)
y_vec = np.append(y_vec[1:],0.0)
I think this is what you are looking for.
I had a similar issue, fixed it by replacing:
plt.pause(0.01)
with
fig.canvas.flush_events()
A more detailed explanation found here:
How to keep matplotlib (python) window in background?

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.

matplotlib - plt.figure() freezes

I have function that renders some plot and then saves it to png file. Simplified code:
def render_plot(self, parameter1, parameter2):
dates = get_my_dates()
values = get_my_values()
fig = plt.figure() # freezes here when calling render_plot for the 2nd or 3rd time!
ax = fig.add_subplot(111)
... # performing some calculations and drawing plots
ax.plot_date(dates, values, '-', marker='o')
plt.savefig("media/plot.png")
plt.cla()
plt.clf()
plt.close()
Function freezes at line "fig = plt.figure()" (100% CPU usage - infinite loop?) but only when calling function 2nd or 3rd time, works fine for the first time and rendering good looking plot. What could be the reason?
fig = plt.figure() will cause the freeze for my PyQt5 as well.
I don't exactly know the reasons, but I have found a nice workaround that works for me.
Workaround:
from matplotlib. Figure import Figure
fig1 = Figure()
ax1 = fig1.add_subplot()
This is probably not the reason but first, you do not need
ax=fig.add_subplot(111)
Try just
ax = plt.gca()
Then, comment
plt.close()
It may help. Just a guess.

Updating the x-axis values using matplotlib animation

I am trying to use matplotlib.ArtistAnimation to animate two subplots. I want the x-axis to increase in value as the animation progresses, such that the total length of the animation is 100 but at any time the subplot is only presenting me with the time values from 0-24 and then iterates up to 100.
A great example is given here. The link uses FuncAnimation and updates the x-axis labels in a rolling fashion using plot().axes.set_xlim() and incrementing the x-values. The code is available via the link below the YouTube video in the link provided.
I have appended code below that shows my attempts to replicate these results but the x-limits seem to take on their final values instead of incrementing with time. I have also tried incrementing the solution (as opposed to the axis) by only plotting the values in the window that will be seen in the subplot, but that does not increment the x-axis values. I also tried to implement autoscaling but the x-axis still does not update.
I also found this question which is virtually the same problem, but the question was never answered.
Here is my code:
import matplotlib.pylab as plt
import matplotlib.animation as anim
import numpy as np
#create image with format (time,x,y)
image = np.random.rand(100,10,10)
#setup figure
fig = plt.figure()
ax1=fig.add_subplot(1,2,1)
ax2=fig.add_subplot(1,2,2)
#set up viewing window (in this case the 25 most recent values)
repeat_length = (np.shape(image)[0]+1)/4
ax2.set_xlim([0,repeat_length])
#ax2.autoscale_view()
ax2.set_ylim([np.amin(image[:,5,5]),np.amax(image[:,5,5])])
#set up list of images for animation
ims=[]
for time in xrange(np.shape(image)[0]):
im = ax1.imshow(image[time,:,:])
im2, = ax2.plot(image[0:time,5,5],color=(0,0,1))
if time>repeat_length:
lim = ax2.set_xlim(time-repeat_length,time)
ims.append([im, im2])
#run animation
ani = anim.ArtistAnimation(fig,ims, interval=50,blit=False)
plt.show()
I only want the second subplot (ax2) to update the x-axis values.
Any help would be much appreciated.
If you don't need blitting
import matplotlib.pylab as plt
import matplotlib.animation as animation
import numpy as np
#create image with format (time,x,y)
image = np.random.rand(100,10,10)
#setup figure
fig = plt.figure()
ax1 = fig.add_subplot(1,2,1)
ax2 = fig.add_subplot(1,2,2)
#set up viewing window (in this case the 25 most recent values)
repeat_length = (np.shape(image)[0]+1)/4
ax2.set_xlim([0,repeat_length])
#ax2.autoscale_view()
ax2.set_ylim([np.amin(image[:,5,5]),np.amax(image[:,5,5])])
#set up list of images for animation
im = ax1.imshow(image[0,:,:])
im2, = ax2.plot([], [], color=(0,0,1))
def func(n):
im.set_data(image[n,:,:])
im2.set_xdata(np.arange(n))
im2.set_ydata(image[0:n, 5, 5])
if n>repeat_length:
lim = ax2.set_xlim(n-repeat_length, n)
else:
# makes it look ok when the animation loops
lim = ax2.set_xlim(0, repeat_length)
return im, im2
ani = animation.FuncAnimation(fig, func, frames=image.shape[0], interval=30, blit=False)
plt.show()
will work.
If you need to run faster, you will need to play games with the bounding box used for blitting so that the axes labels are updated.
If you are using blitting, you can call pyplot.draw() to redraw the entire figure, each time you change y/x axis.
This updates whole figure, so is relatively slow, but it's acceptable if you don't call it many items.
This moves your axis, but is very slow.
import matplotlib.pylab as plt
import matplotlib.animation as anim
import numpy as np
image = np.random.rand(100,10,10)
repeat_length = (np.shape(image)[0]+1)/4
fig = plt.figure()
ax1 = ax1=fig.add_subplot(1,2,1)
im = ax1.imshow(image[0,:,:])
ax2 = plt.subplot(122)
ax2.set_xlim([0,repeat_length])
ax2.set_ylim([np.amin(image[:,5,5]),np.amax(image[:,5,5])])
im2, = ax2.plot(image[0:0,5,5],color=(0,0,1))
canvas = ax2.figure.canvas
def init():
im = ax1.imshow(image[0,:,:])
im2.set_data([], [])
return im,im2,
def animate(time):
time = time%len(image)
im = ax1.imshow(image[time,:,:])
im2, = ax2.plot(image[0:time,5,5],color=(0,0,1))
if time>repeat_length:
print time
im2.axes.set_xlim(time-repeat_length,time)
plt.draw()
return im,im2,
ax2.get_yaxis().set_animated(True)
# call the animator. blit=True means only re-draw the parts that have changed.
animate = anim.FuncAnimation(fig, animate, init_func=init,
interval=0, blit=True, repeat=True)
plt.show()

Categories