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]:
