The result is:
This page shows an example of the animation.FuncAnimation function. The theme is the animation of the cycloid.
This code is based on the following web sites:
In [1]:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
Documents of the animation.FuncAnimation
In [2]:
animation.FuncAnimation?
Init signature: animation.FuncAnimation(fig, func, frames=None, init_func=None, fargs=None, save_count=None, **kwargs) Docstring: Makes an animation by repeatedly calling a function ``func``. Parameters ---------- fig : matplotlib.figure.Figure The figure object that is used to get draw, resize, and any other needed events. func : callable The function to call at each frame. The first argument will be the next value in ``frames``. Any additional positional arguments can be supplied via the ``fargs`` parameter. The required signature is:: def func(frame, *fargs) -> iterable_of_artists: frames : iterable, int, generator function, or None, optional Source of data to pass ``func`` and each frame of the animation If an iterable, then simply use the values provided. If the iterable has a length, it will override the ``save_count`` kwarg. If an integer, then equivalent to passing ``range(frames)`` If a generator function, then must have the signature:: def gen_function() -> obj: If ``None``, then equivalent to passing ``itertools.count``. In all of these cases, the values in *frames* is simply passed through to the user-supplied *func* and thus can be of any type. init_func : callable, optional A function used to draw a clear frame. If not given, the results of drawing from the first item in the frames sequence will be used. This function will be called once before the first frame. If ``blit == True``, ``init_func`` must return an iterable of artists to be re-drawn. The required signature is:: def init_func() -> iterable_of_artists: fargs : tuple or None, optional Additional arguments to pass to each call to *func*. save_count : int, optional The number of values from *frames* to cache. interval : number, optional Delay between frames in milliseconds. Defaults to 200. repeat_delay : number, optional If the animation in repeated, adds a delay in milliseconds before repeating the animation. Defaults to ``None``. repeat : bool, optional Controls whether the animation should repeat when the sequence of frames is completed. Defaults to ``True``. blit : bool, optional Controls whether blitting is used to optimize drawing. Defaults to ``False``. File: ****lib/python3.6/site-packages/matplotlib/animation.py Type: type
In [3]:
animation.FuncAnimation??
Init signature: animation.FuncAnimation(fig, func, frames=None, init_func=None, fargs=None, save_count=None, **kwargs) Source: class FuncAnimation(TimedAnimation): ''' Makes an animation by repeatedly calling a function ``func``. Parameters ---------- fig : matplotlib.figure.Figure The figure object that is used to get draw, resize, and any other needed events. func : callable The function to call at each frame. The first argument will be the next value in ``frames``. Any additional positional arguments can be supplied via the ``fargs`` parameter. The required signature is:: def func(frame, *fargs) -> iterable_of_artists: frames : iterable, int, generator function, or None, optional Source of data to pass ``func`` and each frame of the animation If an iterable, then simply use the values provided. If the iterable has a length, it will override the ``save_count`` kwarg. If an integer, then equivalent to passing ``range(frames)`` If a generator function, then must have the signature:: def gen_function() -> obj: If ``None``, then equivalent to passing ``itertools.count``. In all of these cases, the values in *frames* is simply passed through to the user-supplied *func* and thus can be of any type. init_func : callable, optional A function used to draw a clear frame. If not given, the results of drawing from the first item in the frames sequence will be used. This function will be called once before the first frame. If ``blit == True``, ``init_func`` must return an iterable of artists to be re-drawn. The required signature is:: def init_func() -> iterable_of_artists: fargs : tuple or None, optional Additional arguments to pass to each call to *func*. save_count : int, optional The number of values from *frames* to cache. interval : number, optional Delay between frames in milliseconds. Defaults to 200. repeat_delay : number, optional If the animation in repeated, adds a delay in milliseconds before repeating the animation. Defaults to ``None``. repeat : bool, optional Controls whether the animation should repeat when the sequence of frames is completed. Defaults to ``True``. blit : bool, optional Controls whether blitting is used to optimize drawing. Defaults to ``False``. ''' def __init__(self, fig, func, frames=None, init_func=None, fargs=None, save_count=None, **kwargs): if fargs: self._args = fargs else: self._args = () self._func = func # Amount of framedata to keep around for saving movies. This is only # used if we don't know how many frames there will be: in the case # of no generator or in the case of a callable. self.save_count = save_count # Set up a function that creates a new iterable when needed. If nothing # is passed in for frames, just use itertools.count, which will just # keep counting from 0. A callable passed in for frames is assumed to # be a generator. An iterable will be used as is, and anything else # will be treated as a number of frames. if frames is None: self._iter_gen = itertools.count elif callable(frames): self._iter_gen = frames elif iterable(frames): self._iter_gen = lambda: iter(frames) if hasattr(frames, '__len__'): self.save_count = len(frames) else: self._iter_gen = lambda: iter(xrange(frames)) self.save_count = frames if self.save_count is None: # If we're passed in and using the default, set save_count to 100. self.save_count = 100 else: # itertools.islice returns an error when passed a numpy int instead # of a native python int (http://bugs.python.org/issue30537). # As a workaround, convert save_count to a native python int. self.save_count = int(self.save_count) self._init_func = init_func # Needs to be initialized so the draw functions work without checking self._save_seq = [] TimedAnimation.__init__(self, fig, **kwargs) # Need to reset the saved seq, since right now it will contain data # for a single frame from init, which is not what we want. self._save_seq = [] def new_frame_seq(self): # Use the generating function to generate a new frame sequence return self._iter_gen() def new_saved_frame_seq(self): # Generate an iterator for the sequence of saved data. If there are # no saved frames, generate a new frame sequence and take the first # save_count entries in it. if self._save_seq: # While iterating we are going to update _save_seq # so make a copy to safely iterate over self._old_saved_seq = list(self._save_seq) return iter(self._old_saved_seq) else: return itertools.islice(self.new_frame_seq(), self.save_count) def _init_draw(self): # Initialize the drawing either using the given init_func or by # calling the draw function with the first item of the frame sequence. # For blitting, the init_func should return a sequence of modified # artists. if self._init_func is None: self._draw_frame(next(self.new_frame_seq())) else: self._drawn_artists = self._init_func() if self._blit: if self._drawn_artists is None: raise RuntimeError('The init_func must return a ' 'sequence of Artist objects.') for a in self._drawn_artists: a.set_animated(self._blit) self._save_seq = [] def _draw_frame(self, framedata): # Save the data for potential saving of movies. self._save_seq.append(framedata) # Make sure to respect save_count (keep only the last save_count # around) self._save_seq = self._save_seq[-self.save_count:] # Call the func with framedata and args. If blitting is desired, # func needs to return a sequence of any artists that were modified. self._drawn_artists = self._func(framedata, *self._args) if self._blit: if self._drawn_artists is None: raise RuntimeError('The animation function must return a ' 'sequence of Artist objects.') for a in self._drawn_artists: a.set_animated(self._blit) File: ****/lib/python3.6/site-packages/matplotlib/animation.py Type: type
In [4]:
# radius of the circle
R = 1
Define the function used in the FuncAnimation
In [5]:
def circle(a, b, r):
# (a,b): the center of the circle
# r: the radius of the circle
# T: The number of the segments
T = 100
x, y = [0]*T, [0]*T
for i,theta in enumerate(np.linspace(0,2*np.pi,T)):
x[i] = a + r*np.cos(theta)
y[i] = b + r*np.sin(theta)
return x, y
def gen():
for theta in np.linspace(0,4*np.pi,100):
yield R*(theta-np.sin(theta)), R*(1-np.cos(theta)), R*theta
Define the figure
In [6]:
fig = plt.figure(figsize=(6,3))
ax = fig.add_subplot(111)
ax.set_ylim(0, 3)
ax.set_xlim(0, 15)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_aspect('equal')
ax.grid()
time_text = ax.text(0.05, 0.8, '', transform=ax.transAxes)
cycloid, = ax.plot([], [], 'r-', lw=2)
line, = ax.plot([], [], 'y-', lw=2)
circle_line, = ax.plot([], [], 'g', lw=2)
point, = ax.plot([], [], 'bo', ms=4)
xx, yy = [], []
def func(data):
x, y, Rt = data
time_text.set_text(r'$\theta$ = %.2f $\pi$' % (Rt/np.pi))
xx.append(x)
yy.append(y)
cx, cy = circle(Rt, R, R)
cycloid.set_data(xx, yy)
line.set_data((x,Rt), (y,R))
circle_line.set_data(cx, cy)
point.set_data(x, y)
Build the animation
In [7]:
ani = animation.FuncAnimation(fig, func, gen, blit=False, interval=50)
Save the animation in mp4 format
In [8]:
fn = 'cycloid_FuncAnimation'
ani.save('%s.mp4'%(fn), writer='ffmpeg', fps=1000/50)
Save the animation in gif format (define xx, yy again in order to avoid unexpected behavior)
In [9]:
xx, yy = [], []
ani.save('%s.gif'%(fn), writer='imagemagick', fps=1000/50)
Reduce the size of the GIF image using Imagemagick
In [10]:
import subprocess
cmd = 'magick convert %s.gif -fuzz 10%% -layers Optimize %s_r.gif'%(fn,fn)
subprocess.check_output(cmd)
Out[10]:
Show the animation in the jupyter notebook
In [11]:
plt.rcParams['animation.html'] = 'html5'
xx, yy = [], []
ani
Out[11]: