Search code examples
pythonpython-3.xmatplotlibanimationmatplotlib-animation

Matplotlib animation.FuncAnimation() animation miss first frame?


I am new to both Python and Matplotlib. I'have this code to plot a trajectory in a 3d Matplotlib subplot:

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

from itertools import islice

import sympy as sym
from sympy.parsing.sympy_parser import parse_expr

from sympy import Eq, solve




class FunZ:
    
    def __init__(self, f):  
  
        self.x, self.y  = sym.symbols('x y')
        
        self.f = parse_expr(f)
        
        # print('f : ', self.f)
        
    def evalu(self, xx, yy):
    
        return float(self.f.subs({self.x: xx, self.y: yy}).evalf())
    

    def derX(self, xx, yy):        
        
        self.dx = sym.diff(self.f, self.x)
        
        # print('dx : ', self.dx)
        
        return float(self.dx.subs({self.x: xx, self.y: yy}).evalf())
    
    def derY(self, xx, yy):
        
        self.dy = sym.diff(self.f, self.y)
        
        # print('dy :', self.dy)
        
        return float(self.dy.subs({self.x: xx, self.y: yy}).evalf())
    
    def derXY(self, xx, yy):
        
        return [float(self.derX(xx, yy)), float(self.derY(xx, yy))]
    
    def minim(self):
        
        self.dx = sym.diff(self.f, self.x)
        self.dy = sym.diff(self.f, self.y)
        print('dx : ', self.dx)
        print('dy : ', self.dy)
        
        eq1 = Eq(self.dx ,0)
        
        eq2 = Eq(self.dy ,0)
        
        solu = solve((eq1,eq2), (self.x, self.y), dict= False)
        
        print(solu, type(solu))
        
        return solu

XX = np.linspace(-4, 4, 100)

YY = np.linspace(-4, 4, 100)

funz = FunZ('x**2 + y**2 + 2*y + 2')

ij = [(x, y, funz.evalu(x, y)) for x in XX for y in YY]


arr = np.array(ij, dtype=float)

# print(arr, arr.size, arr.shape, arr.dtype)


der_x = [(a, b, funz.derX(a, b)) for a in XX for b in YY] 

derX = np.array(der_x)

# print(derX, derX.size, derX.shape, derX.dtype)


der_y = [(a, b, funz.derY(a, b)) for a in XX for b in YY] 

derY = np.array(der_y)

# print(derY, derY.size, derY.shape, derY.dtype)


x = arr[:, 0]
y = arr[:, 1]

data = arr[:, 2]




fig = plt.figure()
ax = fig.add_subplot(projection='3d')
ax.set_xlim([-4,4])
ax.set_ylim([-4,4])
ax.set_zlim([-3,50])
ax.plot_trisurf(x, y, data, color="red", alpha=0.5)


### devo duplicare primo elemento della lista altrimenti skippa a visualizzare i primi due sul video

# trajectory =  [(2.5, 3.5, 27.5),(2.5, 3.5, 27.5), (2.0, 3.0, 21.0), (1.5, 2.5, 15.5), (1.0, 2.0, 11.0), (0.5, 1.5, 7.5), (0.0, 1.0, 5.0), (0.0, 0.5, 3.25), (0.0, 0.0, 2.0), (0.0, -0.5, 1.25), (0.0, -1.0, 1.0)]

trajectory =  [(2.5, 3.5, 27.5), (2.0, 3.0, 21.0), (1.5, 2.5, 15.5), (1.0, 2.0, 11.0), (0.5, 1.5, 7.5), (0.0, 1.0, 5.0), (0.0, 0.5, 3.25), (0.0, 0.0, 2.0), (0.0, -0.5, 1.25), (0.0, -1.0, 1.0)]

print('len trajectory : ', len(trajectory))


i = islice(trajectory, 0 , len(trajectory), 1)


pippo = None
def animate(j):  #funziona senza stop iteration with animation.save
    
    global pippo
    
    try: 
        coord = next(i)
        
    except:
        print('coord empty')
        return
    
    if pippo:
        pippo.remove()
    
    print(coord)
                
    x = coord[0]
    y = coord[1]
    z = coord[2]
      
    pippo = ax.scatter(x,y,z)          
    
    return pippo

ani = animation.FuncAnimation(fig, animate, interval=1000, frames=len(trajectory), repeat=False)  # e' necessario



plt.show()
plt.close('all')


fig = plt.figure()
ax = fig.add_subplot(projection='3d')
ax.set_xlim([-4,4])
ax.set_ylim([-4,4])
ax.set_zlim([-3,50])
ax.plot_trisurf(x, y, data, color="red", alpha=0.5)


print('#####################')

i = islice(trajectory, 0 , len(trajectory), 1)

ani2 = animation.FuncAnimation(fig, animate, interval=1000, frames=len(trajectory), repeat=False)

# writervideo = animation.FFMpegWriter(fps=1, codec="h264") 
# ani2.save('video_steeper.mp4', writer=writervideo)

ani2.save("movie.gif", writer=animation.PillowWriter(fps=1))

I am struggling trying to understand why to have a proper animation and saved animation I need to duplicate the first element of my trajectory list ((2.5, 3.5, 27.5)).

If I don't do so the animation and my gif show only 9 points instead of 10, see resulting output:

movie from 10 element list trajectory :

enter image description here

movie from 11 element list trajectory (first one duplicated):

enter image description here

I've tried a lot to figure out what is going on but can't understand, it is strange that my print statement inside animate function prints exactly 11 and 10 points for the two different list but both of them miss a frame in the animation and file. Any clue ?


Solution

  • It seems to me that your issue comes from the iterable i you are creating and the use of the function next. I am not exactly sure why it's happening but if instead of creating your iterable i you just index your trajectory directly then your animation works as expected.

    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib.animation as animation
    from itertools import islice
    import sympy as sym
    from sympy.parsing.sympy_parser import parse_expr
    from sympy import Eq, solve
    
    class FunZ:    
        def __init__(self, f):  
            self.x, self.y  = sym.symbols('x y')
            self.f = parse_expr(f)
            
        def evalu(self, xx, yy):   
            return float(self.f.subs({self.x: xx, self.y: yy}).evalf())
      
        def derX(self, xx, yy):        
            self.dx = sym.diff(self.f, self.x)
            return float(self.dx.subs({self.x: xx, self.y: yy}).evalf())
        
        def derY(self, xx, yy):
            self.dy = sym.diff(self.f, self.y)
            return float(self.dy.subs({self.x: xx, self.y: yy}).evalf())
        
        def derXY(self, xx, yy):
            return [float(self.derX(xx, yy)), float(self.derY(xx, yy))]
        
        def minim(self):     
            self.dx = sym.diff(self.f, self.x)
            self.dy = sym.diff(self.f, self.y)
            print('dx : ', self.dx)
            print('dy : ', self.dy)      
            eq1 = Eq(self.dx ,0)   
            eq2 = Eq(self.dy ,0)
            solu = solve((eq1,eq2), (self.x, self.y), dict= False)
            print(solu, type(solu))
            return solu
    
    XX = np.linspace(-4, 4, 100)
    YY = np.linspace(-4, 4, 100)
    
    funz = FunZ('x**2 + y**2 + 2*y + 2')
    ij = [(x, y, funz.evalu(x, y)) for x in XX for y in YY]
    arr = np.array(ij, dtype=float)
    
    der_x = [(a, b, funz.derX(a, b)) for a in XX for b in YY] 
    derX = np.array(der_x)
    der_y = [(a, b, funz.derY(a, b)) for a in XX for b in YY] 
    derY = np.array(der_y)
    
    x = arr[:, 0]
    y = arr[:, 1]
    data = arr[:, 2]
    
    fig = plt.figure()
    ax = fig.add_subplot(projection='3d')
    ax.set_xlim([-4,4])
    ax.set_ylim([-4,4])
    ax.set_zlim([-3,50])
    ax.plot_trisurf(x, y, data, color="red", alpha=0.5)
    
    trajectory =  [(2.5, 3.5, 27.5), (2.0, 3.0, 21.0), (1.5, 2.5, 15.5), (1.0, 2.0, 11.0), (0.5, 1.5, 7.5), (0.0, 1.0, 5.0), (0.0, 0.5, 3.25), (0.0, 0.0, 2.0), (0.0, -0.5, 1.25), (0.0, -1.0, 1.0)]
    print('len trajectory : ', len(trajectory))
    
    pippo = None
    def animate(j):  # it works without stop iteration with animation.save
        
        global pippo
    
        x = trajectory[j][0]
        y = trajectory[j][1]
        z = trajectory[j][2]
    
        if pippo is not None:
            pippo.remove()
        pippo = ax.scatter(x,y,z)          
        return pippo
    
    ani = animation.FuncAnimation(fig, animate, interval=1000, frames=len(trajectory), repeat=False)  # it is required
    plt.show()
    

    The output gives:

    enter image description here