10

I created an animated plot using FuncAnimation from the Matplotlib Animation class, and I want to save it as a .gif file. When I run the script, the output looks normal, and looks like this (the animation works fine):

enter image description here

However, when I try to save the animated plot as a .gif file using ImageMagick or PillowWriter, the plot looks like the following graph:

enter image description here

The lines are clearly much thicker, and in general, just looks very bad. The problem is attributed to the points (the purple, and red circles). Thus it seems like the plot is writing over each frame (which I think is the case). I can avoid this by just getting rid of them all together. But I don't want to do that as it would be hard to see the lines.

Here is the code:

line, = ax.plot([], [], color = 'blue', lw=1)
line2, = ax.plot([], [], color = 'red', lw=1)
line3, = ax.plot([], [], color = 'purple', lw=1)
def animate(i):
    line.set_data(x1[:i], y1[:i])
    line2.set_data(x2[:i], y2[:i])
    line3.set_data(x3[:i], y3[:i])
    point1, = ax.plot(x1[i], y1[i], marker='.', color='blue')
    point2, = ax.plot(x2[i], y2[i], marker='.', color='red')
    point3, = ax.plot(x3[i], y3[i], marker='.', color='purple')
    return line, line2, line3, point1, point2, point3,
        
ani = animation.FuncAnimation(fig, animate, interval=20, blit=True, repeat=False, frames=1000, save_count=1000)    
ani.save("TLI.gif", writer='imagemagick',fps=60)

The arrays x1, y1, x2, y2, x3, y3 are all 1D arrays that contain the x, y coordinates. So why is this happening? Why is it that the .gif file doesn't show what the plot shows when I run it directly? And also, how can I fix this?

I am also aware of this Stack Overflow question: matplotlib animation save is not obeying blit=True but it seems to work just fine in plt.show() which means the problem is definitely attributed to blitting. However, reading the answer of that question did not solve my problem because that only refers to ax.text opposed to a regular point plotted via ax.plot.

Star Man
  • 171
  • 1
  • 2
  • 11
  • Is the DPI of the output image and the gif image in the question the same? Is it possible to provide the data necessary for drawing? – r-beginners Aug 30 '21 at 04:33
  • can you provide the `x1, y1, x2, y2, x3, y3` to make it reproducible? – R. Marolahy Sep 01 '21 at 12:33
  • @R.Marolahy x1, y1, etc are just lists of x and y coordinates respectively. I suppose you can make it a line, and the same problem will occur. – Star Man Sep 01 '21 at 16:15

1 Answers1

13

See if this works. I don't have Imagemagick so I used Pillow.

To prevent the animation showing stacked frames (i.e., dot traces), the trick is to clear the axes to refresh each frame. Then set xlim and ylim for each frame, and plot the incremental lines using ax.plot(x1[0:i], y1[0:i]...

To improve the image resolution, set the output dpi to a suitable value. I played around with it and settled on 300 to get sharp lines and axes lines/numbers. enter image description here

import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation, PillowWriter
import numpy as np

x1 = np.arange(0, -0.2, -0.002)
y1 = np.arange(0, -0.2, -0.002)
x2 = np.arange(3.9, 3.7, -0.002)
y2 = np.arange(0, 1, 0.01)
x3 = np.arange(0, 1.8, 0.018)
y3 = np.array(x3**2)

fig,ax = plt.subplots()

def animate(i):
    ax.clear()
    ax.set_xlim(-4,4)
    ax.set_ylim(-4,4)
    line, = ax.plot(x1[0:i], y1[0:i], color = 'blue', lw=1)
    line2, = ax.plot(x2[0:i], y2[0:i], color = 'red', lw=1)
    line3, = ax.plot(x3[0:i], y3[0:i], color = 'purple', lw=1)
    point1, = ax.plot(x1[i], y1[i], marker='.', color='blue')
    point2, = ax.plot(x2[i], y2[i], marker='.', color='red')
    point3, = ax.plot(x3[i], y3[i], marker='.', color='purple')
    return line, line2, line3, point1, point2, point3,
        
ani = FuncAnimation(fig, animate, interval=40, blit=True, repeat=True, frames=100)    
ani.save("TLI.gif", dpi=300, writer=PillowWriter(fps=25))
Indiana Bones
  • 334
  • 2
  • 11
  • Thank you. This works. The line and points are visible in the animation. However, it takes a long time to save if I want to save 1000 frames for example. However, that's a separate question. But just wondering if there are tips to make the animation save faster? – Star Man Sep 01 '21 at 23:34
  • You're welcome, glad it worked for you. I ran some tests reducing output dpi and/or fps, and they both impact run/save time. For my 4 second animation, I got the following times in Jupyter Lab: 25 fps, 300 dpi: 12.7 s 25 fps, 300 dpi: 8.8 s – Indiana Bones Sep 02 '21 at 13:10
  • (Sorry, forgot about the edit time limit) 15 fps 300 dpi: 7.5 s; 15 fps 150 dpi: 5.4 s. I do recommend dropping your fps way down (24 fps is typical for decent cartoon animation). Be sure to sync up your FuncAnimation fps with your animation.save fps - your example creates a 20 second animation at 50 fps (20 ms x 1,000 frames) but saves it as 60 fps (16.7 seconds). – Indiana Bones Sep 02 '21 at 13:30
  • I see thanks. Does syncing the fps of ani.save(), and FuncAnimation have any benefits performance and speed-wise? I'd like to save each at different fps so the outputted gif can be faster. – Star Man Sep 02 '21 at 20:39