This Gist provides two examples of how to create a GIF from matplotlib plots using matplotlib.animation.FuncAnimation.
The first movie is created using pre-computed data: a sin-wave which is travelling accross the screen. The second movie is created using data which is calculated bespokely/on-the-fly for each frame, as a function of the frame number; the resulting movie takes about 20% longer to write. The movie in this case is a simple simulation of a standing wave.
In both cases, the results are saved as a GIF; the GIF files are much larger than equivalent MP4 files, however Gisthub will not render an MP4 as part of a Gist, whereas it will render a GIF.
Note that to use animation.PillowWriter, the pillow module must be installed, which can be done using python -m pip install Pillow.
For uploading to Gisthub, the GIFs were compressed using ezgif's GIF compression with a compression level of 100, to reduce the file sizes by about 90%. It is also possible to optimise a GIF using Gifsicle; in a bash terminal (or Windows Bash terminal, which, if available, can be opened in the current directory using the command bash in PowerShell or CMD), do sudo apt-get update, then sudo apt-get install gifsicle, and then gifsicle -O3 < "./1. Travelling sin.gif" > ./gifsicle_out.gif, or gifsicle -O3 --batch "./1. Travelling sin.gif".
TODO: Create a third animation for plotting multiple pre-computed curves during each frame, using a 3-dimensional array ydata with dimensions (time_index, curve_index, x_value_index). Also, reduce the DPI of each gif to reduce the file-size (maybe also the frame rate)? => Tried this but didn't seem to work. Maybe try post-optimising the GIF using GIMP?
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from time import perf_counter
def plot_frame(frame_num, line, ydata): line.set_ydata(ydata[frame_num])
def save_gif(filename, x, ydata, fps, file_ext=".gif"):
# Create figure and line objects
fig = plt.figure(figsize=[8, 6])
[line] = plt.plot(x.ravel(), ydata[0], "b")
plt.grid(True)
plt.tight_layout()
plt.xlim(x.min(), x.max())
plt.ylim(-3, 3)
# Create animation object and save as gif
anim = animation.FuncAnimation(fig, plot_frame, fargs=[line, ydata],
save_count=ydata.shape[0])
anim.save("{}.{}".format(filename, file_ext),
writer=animation.PillowWriter(fps=fps))
if __name__ == "__main__":
# Define parameters
x_freq = 1 # spatial frequency of waves
t_freq = 0.5 # temporal frequency of waves
n_points = 200 # number of points to plot in each wave
x_lo, x_hi = 0, 1.6 # Limits of x-axis
fps = 30 # Sampling frequency / frames per second
length_s = 2 # Number of seconds of video to save
# Create data
x = np.linspace(x_lo, x_hi, n_points).reshape(1, -1)
t = np.linspace(0, length_s, int(length_s * fps)).reshape(-1, 1)
ydata = np.sin(2 * np.pi * (-t_freq * t + x_freq * x))
# Save as gif
print("Starting save...")
t0 = perf_counter()
save_gif("1. Travelling sin", x, ydata, fps)
print("Time taken = {:.4f} s".format(perf_counter() - t0))Output:
Starting save...
Time taken = 1.7544 s
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from time import perf_counter
def calc_waves(t, x, t_freq, x_freq, reflec_coef):
# Compute input wave
y_in = np.sin(2 * np.pi * (-t_freq * t + x_freq * x))
# Compute reflection by extending x and flipping results
y_reflec = reflec_coef * np.flip(
np.sin(2 * np.pi * (-t_freq * t + x_freq * (x - x.min() + x.max()))))
# Compute standing wave from sum
y_standing = y_in + y_reflec
return y_in, y_reflec, y_standing
def setup_fig(x, t_freq, x_freq, reflec_coef):
fig = plt.figure(figsize=[8, 6])
y_in, y_reflec, y_standing = calc_waves(0, x, t_freq, x_freq, reflec_coef)
lines = plt.plot(x, y_in, "k--", x, y_reflec, "k:", x, y_standing, "r")
plt.grid(True)
plt.tight_layout()
plt.xlim(x.min(), x.max())
plt.ylim(-3, 3)
plt.legend(["Input wave", "Reflected wave", "Standing wave"])
return fig, lines
def plot_frame(frame_num, f_s, lines, x, t_freq, x_freq, reflec_coef):
# Compute time from frame number
t = frame_num / f_s
# Compute waves
y_in, y_reflec, y_standing = calc_waves(t, x, t_freq, x_freq, reflec_coef)
# Plot
lines[0].set_ydata(y_in)
lines[1].set_ydata(y_reflec)
lines[2].set_ydata(y_standing)
if __name__ == "__main__":
x_freq = 1 # spatial frequency of waves
t_freq = 0.5 # temporal frequency of waves
n_points = 200 # number of points to plot in each wave
x_lo, x_hi = 0, 1.6 # Limits of x-axis
reflec_coef = 1.0 # How much the reflected wave is attenuated
f_s = 30 # Sampling frequency / frames per second
seconds_to_save = 2 # Number of seconds of video to save
interval_ms = 1000 / f_s # Interval between frames in ms
frames_to_save = seconds_to_save * f_s # Number of frames to save
x = np.linspace(x_lo, x_hi, n_points)
fig, lines = setup_fig(x, t_freq, x_freq, reflec_coef)
anim = animation.FuncAnimation(fig, plot_frame, fargs=[f_s, lines, x,
t_freq, x_freq, reflec_coef], interval=interval_ms,
save_count=frames_to_save)
print("Starting save...")
t0 = perf_counter()
anim.save("2. Standing waves.gif", writer="pillow")
print("Time taken = {:.4f} s".format(perf_counter() - t0))Output:
Starting save...
Time taken = 2.4753 s

