|
| 1 | +""" |
| 2 | +=========================== |
| 3 | +Animations using Matplotlib |
| 4 | +=========================== |
| 5 | +
|
| 6 | +Based on its plotting functionality, Matplotlib also provides an interface to |
| 7 | +generate animations using the `~matplotlib.animation` module. An |
| 8 | +animation is a sequence of frames where each frame corresponds to a plot on a |
| 9 | +`~matplotlib.figure.Figure`. This tutorial covers a general guideline on |
| 10 | +how to create such animations and the different options available. |
| 11 | +""" |
| 12 | + |
| 13 | +import matplotlib.pyplot as plt |
| 14 | +import matplotlib.animation as animation |
| 15 | +import numpy as np |
| 16 | + |
| 17 | +############################################################################### |
| 18 | +# Animation Classes |
| 19 | +# ================= |
| 20 | +# |
| 21 | +# The animation process in Matplotlib can be thought of in 2 different ways: |
| 22 | +# |
| 23 | +# - `~matplotlib.animation.FuncAnimation`: Generate data for first |
| 24 | +# frame and then modify this data for each frame to create an animated plot. |
| 25 | +# |
| 26 | +# - `~matplotlib.animation.ArtistAnimation`: Generate a list (iterable) |
| 27 | +# of artists that will draw in each frame in the animation. |
| 28 | +# |
| 29 | +# `~matplotlib.animation.FuncAnimation` is more efficient in terms of |
| 30 | +# speed and memory as it draws an artist once and then modifies it. On the |
| 31 | +# other hand `~matplotlib.animation.ArtistAnimation` is flexible as it |
| 32 | +# allows any iterable of artists to be animated in a sequence. |
| 33 | +# |
| 34 | +# ``FuncAnimation`` |
| 35 | +# ----------------- |
| 36 | +# |
| 37 | +# The `~matplotlib.animation.FuncAnimation` class allows us to create an |
| 38 | +# animation by passing a function that iteratively modifies the data of a plot. |
| 39 | +# This is achieved by using the *setter* methods on various |
| 40 | +# `~matplotlib.artist.Artist` (examples: `~matplotlib.lines.Line2D`, |
| 41 | +# `~matplotlib.collections.PathCollection`, etc.). A usual |
| 42 | +# `~matplotlib.animation.FuncAnimation` object takes a |
| 43 | +# `~matplotlib.figure.Figure` that we want to animate and a function |
| 44 | +# *func* that modifies the data plotted on the figure. It uses the *frames* |
| 45 | +# parameter to determine the length of the animation. The *interval* parameter |
| 46 | +# is used to determine time in milliseconds between drawing of two frames. |
| 47 | +# Animating using `.FuncAnimation` would usually follow the following |
| 48 | +# structure: |
| 49 | +# |
| 50 | +# - Plot the initial figure, including all the required artists. Save all the |
| 51 | +# artists in variables so that they can be updated later on during the |
| 52 | +# animation. |
| 53 | +# - Create an animation function that updates the data in each artist to |
| 54 | +# generate the new frame at each function call. |
| 55 | +# - Create a `.FuncAnimation` object with the `.Figure` and the animation |
| 56 | +# function, along with the keyword arguments that determine the animation |
| 57 | +# properties. |
| 58 | +# - Use `.animation.Animation.save` or `.pyplot.show` to save or show the |
| 59 | +# animation. |
| 60 | +# |
| 61 | +# The update function uses the ``set_*`` function for different artists to |
| 62 | +# modify the data. The following table shows a few plotting methods, the artist |
| 63 | +# types they return and some methods that can be used to update them. |
| 64 | +# |
| 65 | +# ================= ============================= =========================== |
| 66 | +# Plotting method Artist Set method |
| 67 | +# ================= ============================= =========================== |
| 68 | +# `.Axes.plot` `.lines.Line2D` `.lines.Line2D.set_data` |
| 69 | +# `.Axes.scatter` `.collections.PathCollection` `.collections.\ |
| 70 | +# PathCollection.set_offsets` |
| 71 | +# `.Axes.imshow` `.image.AxesImage` ``AxesImage.set_data`` |
| 72 | +# `.Axes.annotate` `.text.Annotation` `.text.Annotation.\ |
| 73 | +# update_positions` |
| 74 | +# `.Axes.barh` `.patches.Rectangle` `.Rectangle.set_angle`, |
| 75 | +# `.Rectangle.set_bounds`, |
| 76 | +# `.Rectangle.set_height`, |
| 77 | +# `.Rectangle.set_width`, |
| 78 | +# `.Rectangle.set_x`, |
| 79 | +# `.Rectangle.set_y` |
| 80 | +# `.Rectangle.set_xy` |
| 81 | +# `.Axes.fill` `.patches.Polygon` `.Polygon.set_xy` |
| 82 | +# `.patches.Circle` `.patches.Ellipse` `.Ellipse.set_angle`, |
| 83 | +# `.Ellipse.set_center`, |
| 84 | +# `.Ellipse.set_height`, |
| 85 | +# `.Ellipse.set_width` |
| 86 | +# ================= ============================= =========================== |
| 87 | +# |
| 88 | +# Covering the set methods for all types of artists is beyond the scope of this |
| 89 | +# tutorial but can be found in their respective documentations. An example of |
| 90 | +# such update methods in use for `.Axes.scatter` is as follows. |
| 91 | + |
| 92 | + |
| 93 | +fig, ax = plt.subplots() |
| 94 | +t = np.linspace(-np.pi, np.pi, 400) |
| 95 | +a, b = 3, 2 |
| 96 | +delta = np.pi / 2 |
| 97 | + |
| 98 | +scat = ax.scatter(np.sin(a * t[0] + delta), np.sin(b * t[0]), c="b", s=2) |
| 99 | +ax.set_xlim(-1.5, 1.5) |
| 100 | +ax.set_ylim(-1.5, 1.5) |
| 101 | + |
| 102 | + |
| 103 | +def update(frame): |
| 104 | + # .set_offsets replaces the offset data for the entire collection with |
| 105 | + # the new values. Therefore, to also carry forward the previously |
| 106 | + # calculated information, we use the data from the first to the current |
| 107 | + # frame to set the new offsets. |
| 108 | + x = np.sin(a * t[:frame] + delta) |
| 109 | + y = np.sin(b * t[:frame]) |
| 110 | + data = np.stack([x, y]).T |
| 111 | + scat.set_offsets(data) |
| 112 | + return (scat,) |
| 113 | + |
| 114 | + |
| 115 | +ani = animation.FuncAnimation(fig=fig, func=update, frames=400, interval=30) |
| 116 | +plt.show() |
| 117 | + |
| 118 | + |
| 119 | +############################################################################### |
| 120 | +# ``ArtistAnimation`` |
| 121 | +# ------------------- |
| 122 | +# |
| 123 | +# `~matplotlib.animation.ArtistAnimation` can be used |
| 124 | +# to generate animations if there is data stored on various different artists. |
| 125 | +# This list of artists is then converted frame by frame into an animation. For |
| 126 | +# example, when we use `.Axes.barh` to plot a bar-chart, it creates a number of |
| 127 | +# artists for each of the bar and error bars. To update the plot, one would |
| 128 | +# need to update each of the bars from the container individually and redraw |
| 129 | +# them. Instead, `.animation.ArtistAnimation` can be used to plot each frame |
| 130 | +# individually and then stitched together to form an animation. A barchart race |
| 131 | +# is a simple example for this. |
| 132 | + |
| 133 | + |
| 134 | +fig, ax = plt.subplots() |
| 135 | +rng = np.random.default_rng(19680801) |
| 136 | +data = np.array([20, 20, 20, 20]) |
| 137 | +x = np.array([1, 2, 3, 4]) |
| 138 | + |
| 139 | +artists = [] |
| 140 | +colors = ['tab:blue', 'tab:red', 'tab:green', 'tab:purple'] |
| 141 | +for i in range(20): |
| 142 | + data += rng.integers(low=0, high=10, size=data.shape) |
| 143 | + container = ax.barh(x, data, color=colors) |
| 144 | + artists.append(container) |
| 145 | + |
| 146 | + |
| 147 | +ani = animation.ArtistAnimation(fig=fig, artists=artists, interval=400) |
| 148 | +plt.show() |
| 149 | + |
| 150 | +############################################################################### |
| 151 | +# Animation Writers |
| 152 | +# ================= |
| 153 | +# |
| 154 | +# Animation objects can be saved to disk using various multimedia writers |
| 155 | +# (ex: Pillow, *ffpmeg*, *imagemagick*). Not all video formats are supported |
| 156 | +# by all writers. There are 4 major types of writers: |
| 157 | +# |
| 158 | +# - `~matplotlib.animation.PillowWriter` - Uses the Pillow library to |
| 159 | +# create the animation. |
| 160 | +# |
| 161 | +# - `~matplotlib.animation.HTMLWriter` - Used to create JavaScript-based |
| 162 | +# animations. |
| 163 | +# |
| 164 | +# - Pipe-based writers - `~matplotlib.animation.FFMpegWriter` and |
| 165 | +# `~matplotlib.animation.ImageMagickWriter` are pipe based writers. |
| 166 | +# These writers pipe each frame to the utility (*ffmpeg* / *imagemagick*) |
| 167 | +# which then stitches all of them together to create the animation. |
| 168 | +# |
| 169 | +# - File-based writers - `~matplotlib.animation.FFMpegFileWriter` and |
| 170 | +# `~matplotlib.animation.ImageMagickFileWriter` are examples of |
| 171 | +# file-based writers. These writers are slower than their pipe-based |
| 172 | +# alternatives but are more useful for debugging as they save each frame in |
| 173 | +# a file before stitching them together into an animation. |
| 174 | +# |
| 175 | +# Saving Animations |
| 176 | +# ----------------- |
| 177 | +# |
| 178 | +# ========================================== =========================== |
| 179 | +# Writer Supported Formats |
| 180 | +# ========================================== =========================== |
| 181 | +# `~matplotlib.animation.PillowWriter` .gif, .apng |
| 182 | +# `~matplotlib.animation.HTMLWriter` .htm, .html, .png |
| 183 | +# `~matplotlib.animation.FFMpegWriter` All formats supported by |
| 184 | +# *ffmpeg* |
| 185 | +# `~matplotlib.animation.ImageMagickWriter` .gif |
| 186 | +# ========================================== =========================== |
| 187 | +# |
| 188 | +# To save animations using any of the writers, we can use the |
| 189 | +# `.animation.Animation.save` method. It takes the *filename* that we want to |
| 190 | +# save the animation as and the *writer*, which is either a string or a writer |
| 191 | +# object. It also takes an *fps* argument. This argument is different than the |
| 192 | +# *interval* argument that `~.animation.FuncAnimation` or |
| 193 | +# `~.animation.ArtistAnimation` uses. *fps* determines the frame rate that the |
| 194 | +# **saved** animation uses, whereas *interval* determines the frame rate that |
| 195 | +# the **displayed** animation uses. |
| 196 | +# |
| 197 | +# Below are a few examples that show how to save an animation with different |
| 198 | +# writers. |
| 199 | +# |
| 200 | +# |
| 201 | +# Pillow writers:: |
| 202 | +# |
| 203 | +# ani.save(filename="/tmp/pillow_example.gif", writer="pillow") |
| 204 | +# ani.save(filename="/tmp/pillow_example.apng", writer="pillow") |
| 205 | +# |
| 206 | +# HTML writers:: |
| 207 | +# |
| 208 | +# ani.save(filename="/tmp/html_example.html", writer="html") |
| 209 | +# ani.save(filename="/tmp/html_example.htm", writer="html") |
| 210 | +# ani.save(filename="/tmp/html_example.png", writer="html") |
| 211 | +# |
| 212 | +# FFMpegWriter:: |
| 213 | +# |
| 214 | +# ani.save(filename="/tmp/ffmpeg_example.mkv", writer="ffmpeg") |
| 215 | +# ani.save(filename="/tmp/ffmpeg_example.mp4", writer="ffmpeg") |
| 216 | +# ani.save(filename="/tmp/ffmpeg_example.mjpeg", writer="ffmpeg") |
| 217 | +# |
| 218 | +# Imagemagick writers:: |
| 219 | +# |
| 220 | +# ani.save(filename="/tmp/imagemagick_example.gif", writer="imagemagick") |
| 221 | +# |
| 222 | +# Since the frames are piped out to *ffmpeg* or *imagemagick*, |
| 223 | +# ``writer="ffmpeg"`` and ``writer="imagemagick"`` support all formats |
| 224 | +# supported by *ffmpeg* and *imagemagick*. |
0 commit comments