Reputation: 36329
I want to create an animation in matplotlib
using FuncAnimation
. The animation contains various "stages" which I would like to separate (emphasize) by adding an extra delay to the interval between the two corresponding frames. Consider the following example code that draws five circles and the drawing of each two consecutive circles should be separated by 1 second:
import time
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
import numpy as np
f, ax = plt.subplots()
ax.set_xlim([-5, 5])
ax.set_ylim([-5, 5])
radius = 1
dp = 2*np.pi / 50
circles = [[(radius, 0)]]
plots = ax.plot([radius], [0])
def update(frame):
global radius
if frame % 50 == 0:
radius += 1
circles.append([(radius, 0)])
plots.extend(ax.plot([radius], [0]))
# I want to add a delay here, i.e. before the drawing of a new circle starts.
# This works for `plt.show()` however it doesn't when saving the animation.
time.sleep(1)
angle = (frame % 50) * dp
circles[-1].append((radius * np.cos(angle), radius * np.sin(angle)))
plots[-1].set_data(*zip(*circles[-1]))
return plots[-1]
animation = FuncAnimation(f, update, frames=range(1, 251), interval=50, repeat=False)
### Uncomment one of the following options.
# animation.save('test.mp4', fps=20)
# with open('test.html', 'w') as fh:
# fh.write(animation.to_html5_video())
# plt.show()
This works when playing the animation via plt.show()
however it doesn't work when saving as .mp4
or HTML5 video. This makes sense since, according to the documentation, the FPS determines the frame delay for mp4 video and the interval
parameter is used for HTML5 video. Then frames are just played one after another (ignoring any compute time as well).
So how can I add a delay that will be retained upon saving the animation?
Upvotes: 1
Views: 3239
Reputation: 339705
You may use the frame
argument to steer your animation. Essentially a pause after frame n
is the same as showing the frame number n
repeatedly until the pause ends. E.g. if you run an animation at a rate of 1 frame per second, and want 3 seconds pause after the second frame, you can supply
0, 1, 1, 1, 1, 2, 3, ....
as frames, such that the frame with number 1 is shown four times.
Applying that concept can be done as follows in your code.
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
import numpy as np
f, ax = plt.subplots()
ax.set_xlim([-5, 5])
ax.set_ylim([-5, 5])
radius = 0
bu = 50
dp = 2*np.pi / bu
circles = [[(radius, 0)]]
plots = ax.plot([radius], [0])
def update(frame):
global radius
if frame % bu == 0:
radius += 1
circles.append([(radius, 0)])
plots.extend(ax.plot([radius], [0]))
angle = (frame % bu) * dp
circles[-1].append((radius * np.cos(angle), radius * np.sin(angle)))
plots[-1].set_data(*zip(*circles[-1]))
return plots[-1]
interval = 50 # milliseconds
pause = int(1 * 1000 / interval)
cycles = 4
frames = []
for c in range(cycles):
frames.extend([np.arange(c*bu, (c+1)*bu), np.ones(pause)*((c+1)*bu)])
frames = np.concatenate(frames)
animation = FuncAnimation(f, update, frames=frames, interval=50, repeat=False)
### Uncomment one of the following options.
# animation.save('test.mp4', fps=20)
# with open('test.html', 'w') as fh:
# fh.write(animation.to_html5_video())
plt.show()
Upvotes: 1
Reputation: 30250
You should be able to use a generating function for your frames
argument. For example:
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
import numpy as np
INTERVAL = 50 # ms
HOLD_MS = 1000
HOLD_COUNT = HOLD_MS // INTERVAL
def frame_generator():
for frame in range(1, 251):
# Yield the frame first
yield frame
# If we should "sleep" here, yield None HOLD_COUNT times
if frame % 50 == 0:
for _ in range(HOLD_COUNT):
yield None
f, ax = plt.subplots()
ax.set_xlim([-5, 5])
ax.set_ylim([-5, 5])
radius = 1
dp = 2*np.pi / 50
circles = [[(radius, 0)]]
plots = ax.plot([radius], [0])
def update(frame):
global radius
if frame is None: return #--------------------------------- Added
if frame % 50 == 0:
radius += 1
circles.append([(radius, 0)])
plots.extend(ax.plot([radius], [0]))
#-------------------------------------------------------- sleep removed
angle = (frame % 50) * dp
circles[-1].append((radius * np.cos(angle), radius * np.sin(angle)))
plots[-1].set_data(*zip(*circles[-1]))
return plots[-1]
# Slightly changed
animation = FuncAnimation(f, update, frames=frame_generator(), interval=INTERVAL, repeat=False)
plt.show()
Should work.
print(list(frame_generator()))
May help clarify what's going on.
Upvotes: 3