Search code examples
pythonmatplotlibanimationrandommatplotlib-animation

Extra Frame in matplotlib.animation.FuncAnimate


I am exploring live graphs with matplotlib animations. Currently, I'm just adding a random integer to an array, and having my plot update based on the new number. The issue is that I seem to be getting an extra value drawn to the plot.

I have the following code:

import matplotlib.pyplot as plt
import matplotlib.animation as animation
import random

fig = plt.figure()
ax1 = fig.add_subplot(1, 1, 1)

xs = []
ys = []

def animate(i, xs, ys):
    
    x = i + 1
    y = random.randint(1, 50)
    
    print '{}: {}, {}'.format(i, x, y)
    
    xs.append(x)
    ys.append(y)
            
    ax1.clear()
    ax1.plot(xs, ys)
        
ani = animation.FuncAnimation(fig,
                              animate,
                              frames = 10,
                              fargs = (xs, ys),
                              repeat = False)
plt.show()

I only want 10 values to be drawn, so I set frames = 10 in the FuncAnimate call. However, the print statement is outputting an 11th value:

Plot Result

Print Statement Output

So it's clear that 11 frames are being generated, rather than 10. Looking at the documenation for FuncAnimate, I can't see a reason why this would be happening.

Can anyone tell me what I'm missing?

Thanks!


Solution

  • Maybe it is not the most elegant way to solve it, but you could use a flag as a workaround in order to avoid the run of the animation in the very first loop:

    import matplotlib.pyplot as plt
    import matplotlib.animation as animation
    import random
    
    fig = plt.figure()
    ax1 = fig.add_subplot(1, 1, 1)
    
    xs = []
    ys = []
    
    flag = 0
    def animate(i, xs, ys):
    
        global flag
    
        if i == 0 and flag == 0:
            flag = 1
            return
    
        if flag == 1:
            x = i + 1
            y = random.randint(1, 50)
    
            print '{}: {}, {}'.format(i, x, y)
    
            xs.append(x)
            ys.append(y)
    
            ax1.clear()
            ax1.plot(xs, ys)
    
    
    ani = animation.FuncAnimation(fig,
                                  animate,
                                  frames = 10,
                                  fargs = (xs, ys),
                                  repeat = False)
    plt.show()
    

    Output:

    0: 1, 37
    1: 2, 2
    2: 3, 46
    3: 4, 39
    4: 5, 30
    5: 6, 47
    6: 7, 16
    7: 8, 3
    8: 9, 3
    9: 10, 49
    

    Plot:

    enter image description here


    EDIT:
    I have done some research. This behavior is due to the fact you (and I) do not specified the init_func parameter of the FuncAnimation. As you can read from the documentation:

    init_func : callable, optional A function used to draw a clear frame. If not given, the results of drawing from the first item in the frames sequence will be used. This function will be called once before the first frame. The required signature is:

    def init_func() -> iterable_of_artists

    If blit == True, init_func must return an iterable of artists to be re-drawn. This information is used by the blitting algorithm to determine which parts of the figure have to be updated. The return value is unused if blit == False and may be omitted in that case.

    If you do not specify the init_func, the first frame is repeated (to be used as a initialization frame).
    That said, I think the most proper way to avoid the repetition is to call a init function where you do not plot anything:

    import matplotlib.pyplot as plt
    import matplotlib.animation as animation
    import random
    
    fig = plt.figure()
    ax1 = fig.add_subplot(1, 1, 1)
    
    xs = []
    ys = []
    
    def init():
        ax1.clear()
    
    def animate(i, xs, ys):
        x = i + 1
        y = random.randint(1, 50)
    
        print '{}: {}, {}'.format(i, x, y)
    
        xs.append(x)
        ys.append(y)
    
        ax1.clear()
        ax1.plot(xs, ys)
    
    
    ani = animation.FuncAnimation(fig = fig,
                                  func = animate,
                                  init_func = init,
                                  frames = 10,
                                  fargs = (xs, ys),
                                  repeat = False)
    
    plt.show()
    

    Output:

    0: 1, 12
    1: 2, 25
    2: 3, 20
    3: 4, 49
    4: 5, 49
    5: 6, 28
    6: 7, 26
    7: 8, 49
    8: 9, 10
    9: 10, 2
    

    Animation:

    enter image description here