当前有效matplotlib
版本为:3.4.1
。
FuncAnimation
是matplotlib
生成逐帧动画的常用类,它的工作原理就是按照一定时间间隔不断调用参数func
对应的函数生成动画。
FuncAnimation
的继承关系如下:
matplotlib.animation.Animation→matplotlib.animation.TimedAnimation→matplotlib.animation.FuncAnimation
基类matplotlib.animation.Animation
定义了三种动画的输出方法。
save()
:通过绘制每帧数据将动画保存为文件。to_html5_video()
:将动画转换为HTML5 标签。to_jshtml()
:以HTML形式生成动画,HTML页面中自带动画控制组件。save()
方法功能为通过绘制每帧数据将动画保存为文件。
save()
方法签名为save(self, filename, writer=None, fps=None, dpi=None, codec=None, bitrate=None, extra_args=None, metadata=None, extra_anim=None, savefig_kwargs=None, *, progress_callback=None)
save()
方法的参数为:
embed_limit
:嵌入动画的大小限制,单位为MB。默认值为rcParams['animation.embed_limit']
即20
。
filename
: 输出文件的名称。字符串。
writer
: 用于输出文件的写入器类,MovieWriter
实例或可以标识类的字符串。MovieWriter
或字符串,默认值为rcParams['animation.writer']
,即ffmpeg
。
fps
: 动画的帧率(每秒)。整数。可选参数。默认值为None
,如果没有设置该参数,动画帧率根据动画的帧间隔时间推断。
dpi
: 控制动画每帧的分辨率(每英寸的点数)。配合图形的尺寸控制动画的大小。浮点数。默认值为rcParams['savefig.dpi']
即'figure'
。
codec
: 动画使用的视频解码器字符串。默认值为rcParams['animation.codec']
即'h264'
。
bitrate
: 动画的比特率,单位为KB/s。整数,值越大说明动画图像质量越高。-1
代表让编码器选择比特率。默认值为rcParams['animation.bitrate']
即-1
。
fps
、codec
、bitrate
用于构造MovieWriter
实例,仅当writer
参数值为字符串时有效。
源码为:
def save(self, filename, writer=None, fps=None, dpi=None, codec=None,
bitrate=None, extra_args=None, metadata=None, extra_anim=None,
savefig_kwargs=None, *, progress_callback=None):
if writer is None:
writer = mpl.rcParams['animation.writer']
elif (not isinstance(writer, str) and
any(arg is not None
for arg in (fps, codec, bitrate, extra_args, metadata))):
raise RuntimeError('Passing in values for arguments '
'fps, codec, bitrate, extra_args, or metadata '
'is not supported when writer is an existing '
'MovieWriter instance. These should instead be '
'passed as arguments when creating the '
'MovieWriter instance.')
if savefig_kwargs is None:
savefig_kwargs = {}
if fps is None and hasattr(self, '_interval'):
# Convert interval in ms to frames per second
fps = 1000. / self._interval
# Re-use the savefig DPI for ours if none is given
if dpi is None:
dpi = mpl.rcParams['savefig.dpi']
if dpi == 'figure':
dpi = self._fig.dpi
writer_kwargs = {}
if codec is not None:
writer_kwargs['codec'] = codec
if bitrate is not None:
writer_kwargs['bitrate'] = bitrate
if extra_args is not None:
writer_kwargs['extra_args'] = extra_args
if metadata is not None:
writer_kwargs['metadata'] = metadata
all_anim = [self]
if extra_anim is not None:
all_anim.extend(anim
for anim
in extra_anim if anim._fig is self._fig)
# If we have the name of a writer, instantiate an instance of the
# registered class.
if isinstance(writer, str):
try:
writer_cls = writers[writer]
except RuntimeError: # Raised if not available.
writer_cls = PillowWriter # Always available.
_log.warning("MovieWriter %s unavailable; using Pillow "
"instead.", writer)
writer = writer_cls(fps, **writer_kwargs)
_log.info('Animation.save using %s', type(writer))
if 'bbox_inches' in savefig_kwargs:
_log.warning("Warning: discarding the 'bbox_inches' argument in "
"'savefig_kwargs' as it may cause frame size "
"to vary, which is inappropriate for animation.")
savefig_kwargs.pop('bbox_inches')
# Create a new sequence of frames for saved data. This is different
# from new_frame_seq() to give the ability to save 'live' generated
# frame information to be saved later.
# TODO: Right now, after closing the figure, saving a movie won't work
# since GUI widgets are gone. Either need to remove extra code to
# allow for this non-existent use case or find a way to make it work.
if mpl.rcParams['savefig.bbox'] == 'tight':
_log.info("Disabling savefig.bbox = 'tight', as it may cause "
"frame size to vary, which is inappropriate for "
"animation.")
# canvas._is_saving = True makes the draw_event animation-starting
# callback a no-op; canvas.manager = None prevents resizing the GUI
# widget (both are likewise done in savefig()).
with mpl.rc_context({'savefig.bbox': None}), \
writer.saving(self._fig, filename, dpi), \
cbook._setattr_cm(self._fig.canvas,
_is_saving=True, manager=None):
for anim in all_anim:
anim._init_draw() # Clear the initial frame
frame_number = 0
# TODO: Currently only FuncAnimation has a save_count
# attribute. Can we generalize this to all Animations?
save_count_list = [getattr(a, 'save_count', None)
for a in all_anim]
if None in save_count_list:
total_frames = None
else:
total_frames = sum(save_count_list)
for data in zip(*[a.new_saved_frame_seq() for a in all_anim]):
for anim, d in zip(all_anim, data):
# TODO: See if turning off blit is really necessary
anim._draw_next_frame(d, blit=False)
if progress_callback is not None:
progress_callback(frame_number, total_frames)
frame_number += 1
writer.grab_frame(**savefig_kwargs)
to_html5_video()
方法功能为将动画转换为HTML5 标签,默认生成base64编码动画。
to_html5_video()
方法签名为to_html5_video(self, embed_limit=None)
to_html5_video()
方法的参数为:
embed_limit
:嵌入动画的大小限制,单位为MB。默认值为rcParams['animation.embed_limit']
即20
。返回值为嵌入base64编码动画的元素字符串。
源码为:
def to_html5_video(self, embed_limit=None):
"""
Convert the animation to an HTML5 ``<video>`` tag.
This saves the animation as an h264 video, encoded in base64
directly into the HTML5 video tag. This respects :rc:`animation.writer`
and :rc:`animation.bitrate`. This also makes use of the
``interval`` to control the speed, and uses the ``repeat``
parameter to decide whether to loop.
Parameters
----------
embed_limit : float, optional
Limit, in MB, of the returned animation. No animation is created
if the limit is exceeded.
Defaults to :rc:`animation.embed_limit` = 20.0.
Returns
-------
str
An HTML5 video tag with the animation embedded as base64 encoded
h264 video.
If the *embed_limit* is exceeded, this returns the string
"Video too large to embed."
"""
VIDEO_TAG = r'''<video {size} {options}>
<source type="video/mp4" src="data:video/mp4;base64,{video}">
Your browser does not support the video tag.
</video>'''
# Cache the rendering of the video as HTML
if not hasattr(self, '_base64_video'):
# Save embed limit, which is given in MB
if embed_limit is None:
embed_limit = mpl.rcParams['animation.embed_limit']
# Convert from MB to bytes
embed_limit *= 1024 * 1024
# Can't open a NamedTemporaryFile twice on Windows, so use a
# TemporaryDirectory instead.
with TemporaryDirectory() as tmpdir:
path = Path(tmpdir, "temp.m4v")
# We create a writer manually so that we can get the
# appropriate size for the tag
Writer = writers[mpl.rcParams['animation.writer']]
writer = Writer(codec='h264',
bitrate=mpl.rcParams['animation.bitrate'],
fps=1000. / self._interval)
self.save(str(path), writer=writer)
# Now open and base64 encode.
vid64 = base64.encodebytes(path.read_bytes())
vid_len = len(vid64)
if vid_len >= embed_limit:
_log.warning(
"Animation movie is %s bytes, exceeding the limit of %s. "
"If you're sure you want a large animation embedded, set "
"the animation.embed_limit rc parameter to a larger value "
"(in MB).", vid_len, embed_limit)
else:
self._base64_video = vid64.decode('ascii')
self._video_size = 'width="{}" height="{}"'.format(
*writer.frame_size)
# If we exceeded the size, this attribute won't exist
if hasattr(self, '_base64_video'):
# Default HTML5 options are to autoplay and display video controls
options = ['controls', 'autoplay']
# If we're set to repeat, make it loop
if hasattr(self, 'repeat') and self.repeat:
options.append('loop')
return VIDEO_TAG.format(video=self._base64_video,
size=self._video_size,
options=' '.join(options))
else:
return 'Video too large to embed.'
to_jshtml()
方法功能为以HTML形式生成动画,HTML页面中自带动画控制组件。
底层调用HTMLWriter
类实现。
to_jshtml()
方法签名为to_jshtml(self, fps=None, embed_frames=True, default_mode=None)
to_jshtml()
方法的参数为:
fps
:每秒钟传输的帧数。默认值为None
,即根据interval
参数确定值。default_mode
:动画是否为循环模式。取值为{'loop','once'}
,默认值为None
,即根据repeat
参数确定值。返回值为嵌入动画的HTML字符串。
源码如下:
def to_jshtml(self, fps=None, embed_frames=True, default_mode=None):
"""Generate HTML representation of the animation"""
if fps is None and hasattr(self, '_interval'):
# Convert interval in ms to frames per second
fps = 1000 / self._interval
# If we're not given a default mode, choose one base on the value of
# the repeat attribute
if default_mode is None:
default_mode = 'loop' if self.repeat else 'once'
if not hasattr(self, "_html_representation"):
# Can't open a NamedTemporaryFile twice on Windows, so use a
# TemporaryDirectory instead.
with TemporaryDirectory() as tmpdir:
path = Path(tmpdir, "temp.html")
writer = HTMLWriter(fps=fps,
embed_frames=embed_frames,
default_mode=default_mode)
self.save(str(path), writer=writer)
self._html_representation = path.read_text()
return self._html_representation
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots()
x = np.arange(0, 2*np.pi, 0.01)
line, = ax.plot(x, np.sin(x))
def animate(i):
line.set_ydata(np.sin(x + i / 50))
return line,
ani = animation.FuncAnimation(
fig, animate, interval=20, blit=True, save_count=50)
# 形式1:利用pillow生成gif文件
ani.save("test.gif",writer='pillow')
# 形式2:输出HTML5 video元素
print(ani.to_html5_video())
# 形式3:将HTML5 video元素保存为html文件
with open("myvideo.html", "w") as f:
print(ani.to_html5_video(), file=f)
# 形式4:输出html
print(ani.to_jshtml())
# 形式5:以HTML形式在jupyter notebook中显示动画
from IPython.display import HTML
HTML(ani.to_jshtml())
# 形式6:将html保存为文件
with open("myhtml.html", "w") as f:
print(ani.to_jshtml(), file=f)