Adrian Price-Whelan

Making a Matplotlib animation with a transparent background:

2016-06-03

I recently needed to overlay a Matplotlib animation on an image in a keynote presentation. This requires creating an animation with a transparent background so that only the plot elements are shown. It turns out that this is possible using the Matplotlib animation objects (note: I’ve only tried this on Mac). The key elements to doing this are to (1) make the Matplotlib figure background invisible, (2) save the video using a png codec (yes, the image format), and (3) to pass keyword arguments through the animation object to the individual Matplotlib savefig calls. I’ll show a simple example below of a circle orbiting in a circle with a trail of points that fade out. First, some imports:

Input[1]:

# Third-party
from matplotlib import animation
import matplotlib.pyplot as plt
import numpy as np

%matplotlib inline
from IPython.display import HTML

We start by defining how many frames (timesteps) to use in the animation, and how many circles to draw in the trail:

Input[2]:

n_frames = 128
n_trails = 8

The circle will orbit in a circle:

Input[3]:

t = np.linspace(0, 10, n_frames)
x = np.sin(t)
y = np.cos(t)

Finally, the meat of the code containing the calls to Matplotlib:

Input[4]:

fig, ax = plt.subplots(1, 1, figsize=(8, 8))

ax.set_xlim(-1.1, 1.1)
ax.set_ylim(-1.1, 1.1)

# turn off axis spines
ax.xaxis.set_visible(False)
ax.yaxis.set_visible(False)
ax.set_frame_on(False)

# set figure background opacity (alpha) to 0
fig.patch.set_alpha(0.0)

fig.tight_layout()

(pt,) = ax.plot([], [], linestyle="none", marker="o", ms=15, color="r")

trails = []
for i, alpha in enumerate(np.linspace(1.0, 0, n_trails)):
    (l,) = ax.plot(
        [], [], linestyle="none", marker="o", ms=6, alpha=alpha, c="w", zorder=-1000
    )
    trails.append(l)


def init():
    pt.set_data([], [])
    for trail in trails:
        trail.set_data([], [])
    return (pt,) + tuple(trails)


def update(i):
    ix = i - n_trails

    pt.set_data(x[i], y[i])
    for j, trail in zip(range(len(trails))[::-1], trails):
        if ix + j < 0:
            continue
        trail.set_data(x[ix + j], y[ix + j])
    return (pt,) + tuple(trails)


ani = animation.FuncAnimation(
    fig, update, n_frames, init_func=init, interval=20, blit=True
)
HTML(ani.to_html5_video())

Output[4]:

png

Notice the lines that hide the plot elements and make the figure background transparent:

ax.xaxis.set_visible(False)
ax.yaxis.set_visible(False)
ax.set_frame_on(False)

and

fig.patch.set_alpha(0.)

To save the video out with a transparent background, the other critical arguments are to the save() call, especially the keyword arguments passed through via savefig_kwargs:

Input[5]:

ani.save(
    "circle_anim.mov",
    codec="png",
    dpi=100,
    bitrate=-1,
    savefig_kwargs={"transparent": True, "facecolor": "none"},
)