0

I want to save an animation made via plt.FuncAnimation(). It has a long title and text boxes (showing various parameters) to the side. I've tried ffpmeg (.mp4), pillow (.gif) and imagemagick (.gif) writers. All attempts lead to the output file seemingly zooming-in on the main figure, both cutting off text and lowering quality. How do I avoid this?

The same issue arose with saving figures via plt.savefig(), but this is fixed via either plt.tight_layout() or plt.savefig(bbox_inches='tight'). Neither of these work with plt.FuncAnimation().save().

Here's an example:

"""
Matplotlib Animation Example

author: Jake Vanderplas
email: vanderplas@astro.washington.edu
website: http://jakevdp.github.com
license: BSD
Please feel free to use and modify this, but keep the above information. Thanks!
"""

import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation

# First set up the figure, the axis, and the plot element we want to animate
fig = plt.figure()
ax = plt.axes(xlim=(0, 2), ylim=(-2, 2))
line, = ax.plot([], [], lw=2)

ax.set_title('This is a really long title that is cropped in the .mp4, but not the .ipynb output.')

ax.text(3, 0.5, 'Text outside the graph is also cropped in the .mp4, but not the .ipynb output.')

# initialization function: plot the background of each frame
def init():
    line.set_data([], [])
    return line,

# animation function.  This is called sequentially
def animate(i):
    x = np.linspace(0, 2, 1000)
    y = np.sin(2 * np.pi * (x - 0.01 * i))
    line.set_data(x, y)
    return line,

# call the animator.  blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate, init_func=init,
                               frames=200, interval=20, blit=True)

# save the animation as an mp4.  This requires ffmpeg or mencoder to be
# installed.  The extra_args ensure that the x264 codec is used, so that
# the video can be embedded in html5.  You may need to adjust this for
# your system: for more information, see
# http://matplotlib.sourceforge.net/api/animation_api.html
anim.save('basic_animation.mp4', fps=30, extra_args=['-vcodec', 'libx264'])

plt.show()
Will
  • 1
  • 3
  • I would recommend creating the figure in such a way that no content overlaps from the start. That way you avoid the complete problem. If you really need to rely on the figure being expanded via `bbox_inches="tight"`, you can supply that as an argument via `savefig_kwargs` when calling `anim.save`. – ImportanceOfBeingErnest Feb 19 '20 at 17:48
  • @ImportanceOfBeingErnest Thanks for the quick reply. With `savefig_kwargs={'bbox_inches' : 'tight'}` I get: "Warning: discarding the 'bbox_inches' argument in 'savefig_kwargs' as it may cause frame size to vary, which is inappropriate for animation." In hindsight, this makes sense. Please may you explain what you mean by creating the figure in such a way that no content overlaps from the start? Are you suggesting I use subplots? – Will Feb 19 '20 at 18:01
  • Oh yes, sorry, I forgot about that warning. What I mean is that, if the title is 7 inches wide, make sure the figure is 7 inches wide as well, such that it does not overlap. – ImportanceOfBeingErnest Feb 19 '20 at 18:11
  • @ImportanceOfBeingErnest Yes, I agree. However, the animation still cuts off the text annotation. Also, if the size of the title or axis labels are large, they partially get cut off too. I've tried the `bbox_extra_artists` savefig kwarg*, but to no avail. * https://stackoverflow.com/questions/10101700/moving-matplotlib-legend-outside-of-the-axis-makes-it-cutoff-by-the-figure-box – Will Feb 19 '20 at 18:21
  • Yes. Make sure not to position any elements outside the figure borders. – ImportanceOfBeingErnest Feb 19 '20 at 18:31
  • Then I'm confused how to make animations with (several) dynamic, synchronized annotations. – Will Feb 19 '20 at 18:52

1 Answers1

0

I managed to solve this by using empty subplots. See here for details on how to completely hide a subplot. Then you can add shapes/text in that subplot as desired.

Will
  • 1
  • 3