I am making an animation in Matplotlib where new artists (specifically patches) are added every few frames, but when I run it, every frame in which a new artist is added is completely blank. I know there is some issue with the blitting since it works when I turn that off, but I need it on. I return every shape that is created or modified in each frame, just like the documentation requires. I am using the MacOSX backend.
My code looks similar to this:
from random import random
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
fig = plt.figure()
axe = fig.add_axes([0, 0, 1, 1], frameon=False)
circles = []
def update(i):
if not i % 10:
new_circle = plt.Circle((random(), random()), 0.05, color='black')
axe.add_patch(new_circle)
circles.append(new_circle)
for circle in circles:
circle.center = (random(), random())
return circles
animation = FuncAnimation(fig, update, frames=60, interval=1000/30, repeat=False, blit=True)
plt.show()
This appears to be a bug with matplotlib in the MacOSX backend, so the solution is just to work around it by using a different backend or not blitting if possible.
Related
I am making a simple animated scatter plot in a Jupyter notebook, and I want it to repeat, but with a delay before it loops. This is supposed to be set with the repeat_delay parameter, but it has no effect when I use it in a Jupyter notebook using HTML(ani.to_html5_video()) to show the animation.
Here is a simple example of repositioning 20 points every 200 ms, but trying to add a 2 second delay before repeating the animation:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
from IPython.display import HTML
frame_interval = 200
vid_repeat = True
repeat_delay = 2000 # set long so it would be obvious
def update_xy(i, scat):
scat.set_offsets(np.random.random((20,2)))
return scat,
fig = plt.figure()
init_data = np.random.random((20,3))
scat = plt.scatter(init_data[:,0], init_data[:,1], c=init_data[:,2], s=50, cmap = "hot")
ani = animation.FuncAnimation(fig,
update_xy,
frames=numframes,
interval = frame_interval,
repeat = vid_repeat,
repeat_delay = repeat_delay,
fargs=(scat,))
plt.close(ani._fig)
HTML(ani.to_html5_video())
At the end, it just loops around at frame_interval no matter what value I put for repeat_delay. I get the same result when I save the animation ani.save('foo.mp4'), or try to play it using HTML(ani.to_jshtml()).
Related Questions
Animation in iPython notebook
How to animate a scatter plot?
I'm trying to animate a figure using matplotlib->FuncAnimate function. However, I'm having trouble understanding how Blit works. With each frame, I want to draw only the new data point on top of the old one. It says that using Blit it should automatically update only the values that changed. Thus, if I turn it on (blit=True) the previous data points should remain in my figure. But this is not the case. The previous data get deleted and the figure gets redraw from scratch.
In the documentation, it says that I have to return "iterable_of_artists" and the algorithm will know which data has changed. I want to just pass the new data and just plot on top of the old one. By the way, what is an "iterable_of_artists", is that just a list of objects that can be drawn? if someone could point me out to the definition, I would appreciate it.
Anyway, I have worked several base examples that show the odd behavior. In the first example, I'm turning Blit=True and drawing only the new data using the animate function. This in theory should draw on top of the old ones, but is not the case, only the new data is drawn.
import time
import random
import numpy
import matplotlib
import matplotlib.pyplot as pyplot
from matplotlib.animation import FuncAnimation
def livePlot():
fig, ax = pyplot.subplots(1,1)
ax = pyplot.axes(xlim=(0, 2), ylim=(0, 100))
line, = ax.plot([], [], 'ro') #ax.plot will return a tupple
def init():
line.set_data(0, 50)
return line, #Return is not necessary when blit=False
def animate(frame):
x = frame
y = random.randint(0, 100)
line.set_data(x,y)
return line, #Return is not necessary when blit=False
animation = FuncAnimation(
fig, animate,
init_func = init,
frames= [0.5, 1, 1.5, 2.0],
interval=1000,
repeat=False,
blit=True, # Turning on Blit
cache_frame_data = True)
pyplot.show()
if __name__ == "__main__":
livePlot()
I was able to achieve my goal by tricking the FuncAnimate method. I can use the ax and plot in each frame the new data. If I do that, the old data remains and only the new data is drawn. However, I can do that with Blit=True or Blit=False, it has no effect. So, I'm so confused on how Blit works and what would be the correct way to plot only the new data without having to create a list with all the data to plot. Passing a large list will create a large variable in memory if I have a long set of data points. Here is my workaround but I'm not sure if this is the correct way to do it or if there is a better ways of using Blit=True and just redraw the new data.
import time
import random
import numpy
import matplotlib
import matplotlib.pyplot as pyplot
from matplotlib.animation import FuncAnimation
def livePlot():
fig, ax = pyplot.subplots(1,1)
ax = pyplot.axes(xlim=(0, 2), ylim=(0, 100))
def init():
ax.plot(0, 50, 'ro')
return []
def animate(frame):
x = frame
y = random.randint(0, 100)
ax.plot(x, y, 'ro') # plotting directly on the axis. This keeps the old data
return [] # fooling the blit algorithm with an empty stream
animation = FuncAnimation(
fig, animate,
init_func = init,
frames= [0.5, 1, 1.5, 2.0],
interval=1000,
repeat=False,
blit=True,
cache_frame_data = True)
pyplot.show()
if __name__ == "__main__":
livePlot()
I have an external service that, every now and then, triggers the function callback(points) — check the code below — in order to update a Polygon patch being plotted with matplotlib.
fig, ax = plt.subplots()
polygon = Polygon([[0, 0]])
patches = []
patches.append(polygon)
collection = PatchCollection(patches, animated=True, alpha=0.4)
ax.add_collection(collection)
ax.autoscale_view(True)
plt.show()
def callback(points):
polygon.set_xy(points)
fig.canvas.draw()
fig.canvas.flush_events()
However, this is not working... The plot is not being updated. Are canvas.draw and canvas.flush_events not the right things to call?
I have seen other examples on the web which use ArtistAnimation but I cannot use this since I cannot simulate my inputs; rather, they are coming from the external service that calls callback().
How can I make this work? Thanks!
OK, I modified this example slightly (which makes use of an UpdatablePatchCollection) and it works now.
Here is a working minimal example:
%matplotlib notebook # Use this on Jupyter Notebooks
import matplotlib.collections as mcollections
import matplotlib.pyplot as plt
import matplotlib as mpl
import time
class UpdatablePatchCollection(mcollections.PatchCollection):
def __init__(self, patches, *args, **kwargs):
self.patches = patches
mcollections.PatchCollection.__init__(self, patches, *args, **kwargs)
def get_paths(self):
self.set_paths(self.patches)
return self._paths
plt.ion()
rect = mpl.patches.Rectangle((0,0),1,1)
collection = UpdatablePatchCollection([rect])
fig, ax = plt.subplots()
ax.set_xlim(0,5)
ax.set_ylim(0,3)
ax.add_artist(collection)
def update_plot(i):
rect.set_xy((i,1))
fig.canvas.draw()
fig.canvas.flush_events()
for i in range(5):
time.sleep(0.5)
update_plot(i)
This part is important to "render" each frame.
fig.canvas.draw()
fig.canvas.flush_events()
And notice the important plt.ion() call. Without it, the plot will not interactively animate the changes to the patch collection.
Some seaborn methods like JointPlot create new figures on each call. This makes it impossible to create a simple animation like in matplotlib where iterative calls to plt.cla() or plt.clf() allow to update the contents of a figure without closing/opening the window each time.
The only solution I currently see is:
for t in range(iterations):
# .. update your data ..
if 'jp' in locals():
plt.close(jp.fig)
jp = sns.jointplot(x=data[0], y=data[1])
plt.pause(0.01)
This works because we close the previous window right before creating a new one. But of course, this is far from ideal.
Is there a better way? Can the plot somehow be done directly on a previously generated Figure object? Or is there a way to prevent these methods to generate new figures on each call?
sns.jointplot creates a figure by itself. In order to animate the jointplot, one might therefore reuse this created figure instead of recreating a new one in each iteration.
jointplot internally creates a JointGrid, so it makes sense to directly use this and plot the joint axes and the marginals individually. In each step of the animation one would then update the data, clear the axes and set them up just as during creation of the grid. Unfortunately, this last step involves a lot of code lines.
The final code may then look like:
import matplotlib.pyplot as plt
import matplotlib.animation
import seaborn as sns
import numpy as np
def get_data(i=0):
x,y = np.random.normal(loc=i,scale=3,size=(2, 260))
return x,y
x,y = get_data()
g = sns.JointGrid(x=x, y=y, size=4)
lim = (-10,10)
def prep_axes(g, xlim, ylim):
g.ax_joint.clear()
g.ax_joint.set_xlim(xlim)
g.ax_joint.set_ylim(ylim)
g.ax_marg_x.clear()
g.ax_marg_x.set_xlim(xlim)
g.ax_marg_y.clear()
g.ax_marg_y.set_ylim(ylim)
plt.setp(g.ax_marg_x.get_xticklabels(), visible=False)
plt.setp(g.ax_marg_y.get_yticklabels(), visible=False)
plt.setp(g.ax_marg_x.yaxis.get_majorticklines(), visible=False)
plt.setp(g.ax_marg_x.yaxis.get_minorticklines(), visible=False)
plt.setp(g.ax_marg_y.xaxis.get_majorticklines(), visible=False)
plt.setp(g.ax_marg_y.xaxis.get_minorticklines(), visible=False)
plt.setp(g.ax_marg_x.get_yticklabels(), visible=False)
plt.setp(g.ax_marg_y.get_xticklabels(), visible=False)
def animate(i):
g.x, g.y = get_data(i)
prep_axes(g, lim, lim)
g.plot_joint(sns.kdeplot, cmap="Purples_d")
g.plot_marginals(sns.kdeplot, color="m", shade=True)
frames=np.sin(np.linspace(0,2*np.pi,17))*5
ani = matplotlib.animation.FuncAnimation(g.fig, animate, frames=frames, repeat=True)
plt.show()
using the celluloid package (https://github.com/jwkvam/celluloid) I was able to animate seaborn plots without much hassle:
import numpy as np
from celluloid import Camera
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
fig = plt.figure()
camera = Camera(fig)
# animation draws one data point at a time
for i in range(0, data.shape[0]):
plot = sns.scatterplot(x=data.x[:i], y=data.y[:i])
camera.snap()
anim = camera.animate(blit=False)
anim.save('animation.mp4')
I'm sure similar code could be written for jointplots
As per the title, I'm wondering if it is possible to pause a matplotlib ArtistAnimation. I know it is possible to pause when using FuncAnimation, but I am not sure that that method can be applied to an ArtistAnimation.
An example of a working ArtistAnimation without pausing is
import matplotlib.pyplot as plt
from matplotlib.animation import ArtistAnimation
import numpy as np
fig, ax = plt.subplots()
ax.set(xlim=(0, 2*np.pi), ylim=(-1, 1))
x = np.linspace(0, 2*np.pi, 100)
ims = [] # Blank list that will contain all frames
for frame in range(50):
line, = ax.plot(x, np.sin(x + 0.1*frame), color='k')
# Add new element to list with everything that changes between frames
ims.append([line])
anim = ArtistAnimation(fig, ims, interval=100)
The following is not a complete solution, but maybe some way toward one. It requires IPython be used.
Using anim as defined in the question, I can enter anim._stop() to pause the animation. I can also use anim._step() as needed to see the next frames.
I'm not sure if it's possible to get the animation to start running again after these calls.