Matplotlib, Python’s primary scientific plotting library, provides tools to make many elaborate plots, graphs, and diagrams. Many of these can be animated, but the process isn’t always intuitive. The hardest part is learning how to animate a simple line plot (here’s my easy way). Beyond that, the steps to creating most animations tend to be similar.
The examples below demonstrate the particular methods needed to animate common types of plot. Here I focus on the key components needed for updating each frame of the animation. The full code for the examples is here. It includes liberal and arbitrary use of sines and cosines so as to produce looping gifs.
Scatter with variable colour, position, and size
Here we are animating 8 × 8 = 64 points.
Y3, the locations of the circles, are each arrays of size (Nf, 8, 8) where Nf is the number of frames.
S (the sizes of the circles) is the same size as
C (the colours of the circles) has size (Nf, 8, 8, 4).
def animate(i): Xi, Yi = X3[i, :, :].flatten(), Y3[i, :, :].flatten() scat.set_offsets(np.c_[Xi, Yi]) scat.set_sizes(S[i, :, :].flatten()) scat.set_facecolors(C[i, :, :].reshape(-1, 4))
set_offsetsalters the location of the points. It accepts a 64 × 2 array of (x, y) coordinates. np.c_ is a handy tool to combine two 1D arrays appropriately.
set_sizesdoes what it sounds like. It accepts a 1D array of length 64.
set_facecolorsalso does what it sounds like. It accepts a 64 × 4 array, where each row is (R, G, B, A). The A controls transparency and is optional, so 64 × 3 would work.
Quiver with variable direction, position, and colour
Here we are animating 30 arrows.
Qy are the locations of the base of the arrows for all frames. Their size is (Nf, 30).
V, the arrows’ directions, are also this size.
C, the colours of the arrows, is (Nf, 4), but for each frame we take a subset of size (30, 4).
def animate(i): s = np.s_[i, :] qax.set_offsets(np.c_[Qx[s].flatten(), Qy[s].flatten()]) qax.set_UVC(U[s], V[s]) Cidx = np.mod(np.r_[i:i+U.shape], Nframes) qax.set_facecolor(C[Cidx, :])
set_offsetsworks as it does for the scatter. Since we have 30 arrows, it accepts an array of size (30, 2).
set_UVCaccepts the arrow vectors as two 1D arrays, rather than combined.
set_facecoloraccepts an array of size (30, 4), one RGBA colour for each arrow. As above, the A is optional.
The first line within the function simplifies the slicing of arrays in the following lines. The fourth line involving the modulo division (
np.mod) is only there to help cycle through the colourmap.
Line plots in a polar projection
Because Matplotlib does all the hard work behind the scenes with respect to graph projections, animating a polar plot is no different to animating a line plot. Here we update two curves.
theta2 are 2D arrays of size (Nf, 100), where 100 is the number of elements comprising a curve for a given frame.
def animate(i): curve1.set_ydata(theta1[i, :]) curve2.set_ydata(theta2[i, :])
Filled area plots with variable colour
Filled area plots are a little more challenging as they are patches, rather than points or lines. Matplotlib’s treatment of patches makes sense, but it isn’t intuitive at the outset which properties control what.
fill is an object created by the
Y2, the upper and lower bounds of the fill, are each arrays of size (Nf, Nx), where Nx is the number of elements making up each curve in a single frame.
def animate(i): path = fill.get_paths() verts = path.vertices verts[1:Nx+1, 1] = Y1[i, :] verts[Nx+2:-1, 1] = Y2[i, :][::-1] fill.set_color(C[i, :])
get_paths()extracts the one and only path. The output of
get_paths()is a list, even if only one element long.
- We rename
- The elements
1:Nx+1contain the y coordinates of the upper curve.
- The elements
Nx+2:-1contain the y coordinates of the lower curve, but right-to-left, hence the need to reverse the array using
set_colorworks as it does for examples above.
Careful inspection of the third and fourth lines of the function indicates that the
-1 elements of
verts are not being updated. It’s not clear what these elements do.
Three-dimensional contour plots
Contour plots are treated different from the other examples. In a way, they are simpler, because for each frame, we simply delete the current contour object and then plot a new one. No need to figure out what
set_... method to use. Although any animation could be created this way, the trade-off is often a noticeable slow down in the speed that the animation renders and unnecessary repetition in the Python code.
Y are 2D arrays of size (20, 30) and
Z, the height of the contour, is a 3D array of size (Nf, 20, 30).
def animate(i): ax.collections =  ax.plot_surface(X, Y, Z[i, :, :], **contour_opts) data-mce-type="bookmark" id="mce_SELREST_start" data-mce-style="overflow:hidden;line-height:0" style="overflow:hidden;line-height:0" &amp;amp;gt;&amp;amp;amp;#65279;&amp;amp;lt;/span&amp;amp;gt;
- Delete the existing contour by emptying the list of
collections, which is where Matplotlib stores information about the contour object.
- Replot the contour using
**contour_optsbeing a dictionary of keyword arguments.
With judicious use of
ax.collections, it is possible to retain some components of a plot while removing others. Here that’s unnecessary since we’re plotting only a single thing.