stop / start / pause in matplotlib animation
Asked Answered
M

5

37

I'm using FuncAnimation in matplotlib's animation module for some basic animation. This function perpetually loops through the animation. Is there a way by which I can pause and restart the animation by, say, mouse clicks?

Massage answered 24/5, 2013 at 10:1 Comment(0)
W
34

Here is a FuncAnimation example which I modified to pause on mouse clicks. Since the animation is driven by a generator function, simData, when the global variable pause is True, yielding the same data makes the animation appear paused.

The value of paused is toggled by setting up an event callback:

def onClick(event):
    global pause
    pause ^= True
fig.canvas.mpl_connect('button_press_event', onClick)

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

pause = False
def simData():
    t_max = 10.0
    dt = 0.05
    x = 0.0
    t = 0.0
    while t < t_max:
        if not pause:
            x = np.sin(np.pi*t)
            t = t + dt
        yield x, t

def onClick(event):
    global pause
    pause ^= True

def simPoints(simData):
    x, t = simData[0], simData[1]
    time_text.set_text(time_template%(t))
    line.set_data(t, x)
    return line, time_text

fig = plt.figure()
ax = fig.add_subplot(111)
line, = ax.plot([], [], 'bo', ms=10)
ax.set_ylim(-1, 1)
ax.set_xlim(0, 10)

time_template = 'Time = %.1f s'
time_text = ax.text(0.05, 0.9, '', transform=ax.transAxes)
fig.canvas.mpl_connect('button_press_event', onClick)
ani = animation.FuncAnimation(fig, simPoints, simData, blit=False, interval=10,
    repeat=True)
fig.show()
Whet answered 24/5, 2013 at 10:56 Comment(2)
Cute, handy, entertaining, and in a way, nostalgic; youtu.be/TxmZ5sabk7U?t=17 or youtu.be/C1HuX6nQnQY?t=211Sideway
@Whet It does not work for me. The window is closed quickly. My platform: Windows 10, python 3.8, matplotlib 3.4.2.Villalba
H
27

This works...

anim = animation.FuncAnimation(fig, animfunc[,..other args])

#pause
anim.event_source.stop()

#unpause
anim.event_source.start()
Heimdall answered 13/9, 2016 at 2:29 Comment(0)
C
17

Combining both the answers from @fred and @unutbu here, we can add an onClick function after creating the animation:

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

fig = plt.figure()

def run_animation():
    anim_running = True

    def onClick(event):
        nonlocal anim_running
        if anim_running:
            anim.event_source.stop()
            anim_running = False
        else:
            anim.event_source.start()
            anim_running = True

    def animFunc( ...args... ):
        # Animation update function here

    fig.canvas.mpl_connect('button_press_event', onClick)

    anim = animation.FuncAnimation(fig, animFunc[,...other args])

run_animation()

Now we can simply stop or start the animation with clicks.

Capsular answered 6/12, 2016 at 11:39 Comment(4)
What version of matplotlib are you running? This doesn't seem to work for meAshley
@Ashley I'm using version 1.5.3. Sorry, should have mentioned that!Capsular
I can't seem to find the documentation on matplotlib for the event_source.stop() or start? Does it exist?Ashley
You need to assign the function to the event also, fig.canvas.mpl_connect('button_press_event', onClick) and I guess replace nonlocal with globalGusti
M
12

I landed on this page trying to implement the same functionality, pausing matplotlibs animation. The other answers are great, but in addition I wanted to be able to manually loop through the frames using the arrow keys. For anyone looking for the same functionality, here's my implementation:

import matplotlib.pyplot as plt
import matplotlib.animation as ani

fig, ax = plt.subplots()
txt = fig.text(0.5,0.5,'0')

def update_time():
    t = 0
    t_max = 10
    while t<t_max:
        t += anim.direction
        yield t

def update_plot(t):
    txt.set_text('%s'%t)
    return txt

def on_press(event):
    if event.key.isspace():
        if anim.running:
            anim.event_source.stop()
        else:
            anim.event_source.start()
        anim.running ^= True
    elif event.key == 'left':
        anim.direction = -1
    elif event.key == 'right':
        anim.direction = +1

    # Manually update the plot
    if event.key in ['left','right']:
        t = anim.frame_seq.next()
        update_plot(t)
        plt.draw()

fig.canvas.mpl_connect('key_press_event', on_press)
anim = ani.FuncAnimation(fig, update_plot, frames=update_time,
                         interval=1000, repeat=True)
anim.running = True
anim.direction = +1
plt.show()

Some notes:

  • To be able to modify the values of running and direction, I assigned them to anim. It avoids using nonlocal (not avaible in Python2.7) or global (not desirable since I'm running this code within another function). Not sure whether this is good practice, but I found it quite elegant.
  • For the manual update, I'm accessing anim's generator object that FuncAnimation uses to update the plot. This ensures that when I resume the animation, it starts from the active frame rather than from where it was originally paused.
Mastership answered 20/3, 2018 at 10:58 Comment(6)
Where are .running and .direction documented? I'm wondering if event_source.start() is "less" undocumented feature.Adaminah
.running and .direction are not documented, I made that up myself. See e.g. this blog post: you can just assign new attributes to any object and it will probably work just fine. Not sure whether this is good practice, but I found it quite useful here. I'm not sure whether event_source.start() is documented, but I think I found it in the source code or in another SO post.Mastership
As soon as i press left or right key I get following error: t = anim.frame_seq.next() AttributeError: 'generator' object has no attribute 'next'Pressurecook
@Gino Gulamhussene I bet you are using python3 :-)... change t = anim.frame_seq.next() to t = anim.frame_seq.__next__().Outpouring
Very elegant! I just modified a bit the generator. As it was values of t could become negative... ok will do it later... lol edit queue look to be full thanks to a missclick!Outpouring
How to make this work with blit=true (see my follow up questionMoulder
B
1

Since there are quite a few comments on different answers asking for documented feature, I dug deeper based on fred's answer. It seems to work, but since matplotlib 3.4.0 there are new functions to pause and resume plotting: pause() and resume(). They call event_source.stop() and start() internally, but they also pause animation altogether, which may reduce the hardware strain.

They can be called on any matplotlib.animation.Animation object, including FuncAnimation subclass.

Barbe answered 30/1, 2022 at 1:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.