I want to animate some data, and I have been following the example from another stack question here to enable pausing. However, I am doing something a little different. In that example, they are using the sine function which can take non-integer values in the argument. What I am doing is plotting a set of data in the y-axis and matching it with a corresponding x-value (time in this case).
Desired Output
I simply want the output to plot the data and update the tick marks as it goes (thus the ax.set_xlim(0, i/sf) below), while being able to pause or play the animation.
Close but not correct solution
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as am
length = 8e6
data = np.random.rand(length)
sf = 100 #Sampling frequency in MHz
x = np.arange(length)/sf
pause = False
def init():
line.set_data([], [])
return line,
def animate(i):
if not pause:
y = data[0:i]
xax = x[0:i]
line.set_data(xax, y)
ax.set_xlim(0, i/sf)
time_text.set_text(time_template%(i/sf))
return line, time_text
def onPress(event):
if event.key==' ':
global pause
pause ^= True
fig = plt.figure(figsize=(15,15))
ax = fig.add_subplot(111)
ax.set_ylim(min(data),max(data))
line, = ax.plot([], [], lw=2)
time_template = 'Time = %.1f $\mu$s' # prints running simulation time
time_text = ax.text(0.05, 0.9, '', transform=ax.transAxes)
anim = am.FuncAnimation(fig, animate, interval=40, init_func=init)
fig.canvas.mpl_connect('key_press_event', onPress)
plt.show()
The problem with this solution is that when I pause and then unpause the animation the plot draws data to whatever it would have been at that point in time, had I not paused it.
You should be able to copy and paste this code to reproduce on your own machine.
Also Tried
I tried a similar structure to that of the example I linked. It doesn't work either.
The problem is nothing seemingly happens when I run the program. I think it's plotting something, because I can see the plot when I try to move around the plot.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as am
length = 8e6
data = np.random.rand(length)
sf = 100 #Sampling frequency in MHz
x = np.arange(length)/sf
pause = False
def init():
line.set_data([], [])
return line,
def theData():
i=0
while i<50:
if not pause:
y = data[0:i]
t = x[0:i]
i=i+1
yield t, y, i
def animate(theData):
t = theData[0]
y = theData[1]
i = theData[2]
line.set_data(t, y)
ax.set_xlim(0, i/sf)
time_text.set_text(time_template%(t))
return line, time_text
def onPress(event):
if event.key==' ':
global pause
pause ^= True
fig = plt.figure(figsize=(15,15))
ax = fig.add_subplot(111)
ax.set_ylim(min(data),max(data))
line, = ax.plot([], [], lw=2)
time_template = 'Time = %.1f $\mu$s' # prints running simulation time
time_text = ax.text(0.05, 0.9, '', transform=ax.transAxes)
anim = am.FuncAnimation(fig, animate, theData, interval=40, init_func=init)
fig.canvas.mpl_connect('key_press_event', onPress)
plt.show()
The argument passed to animate is a frame number. However, this number increments whether or not the animation is paused.
So instead, introduce your own global variable, frame, which records the true frame number and which only increments when the animation is not paused:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as am
length = 8*10**6
data = np.random.rand(length)
sf = 100 #Sampling frequency in MHz
x = np.arange(length)/sf
pause = False
frame = 0
def init():
line.set_data([], [])
return line,
def animate(i):
global frame
if not pause:
frame += 1
y = data[0:frame]
xax = x[0:frame]
line.set_data(xax, y)
ax.set_xlim(0, frame/sf)
time_text.set_text(time_template%(frame/sf))
return line, time_text
def onPress(event):
if event.key==' ':
global pause
pause ^= True
fig = plt.figure(figsize=(15,15))
ax = fig.add_subplot(111)
ax.set_ylim(min(data),max(data))
line, = ax.plot([], [], lw=2)
time_template = 'Time = %.1f $\mu$s' # prints running simulation time
time_text = ax.text(0.05, 0.9, '', transform=ax.transAxes)
anim = am.FuncAnimation(fig, animate, interval=40, init_func=init)
fig.canvas.mpl_connect('key_press_event', onPress)
plt.show()
Related
I would like to update my matplotlibplot with values calculated in each iteration of a for loop. The idea is that I can see in real time which values are calculated and watch the progress iteration by iteration as my script is running. I do not want to first iterate through the loop, store the values and then perform the plot.
Some sample code is here:
from itertools import count
import random
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
def animate(i, x_vals, y_vals):
plt.cla()
plt.plot(x_vals, y_vals)
if __name__ == "__main__":
x_vals = []
y_vals = []
fig = plt.figure()
index = count()
for i in range(10):
print(i)
x_vals.append(next(index))
y_vals.append(random.randint(0, 10))
ani = FuncAnimation(fig, animate, fargs=(x_vals, y_vals))
plt.show()
Most of the examples I have seen online, deal with the case where everything for the animation is global variables, which I would like to avoid. When I use a debugger to step through my code line by line, the figure does appear and it is animated. When I just run the script without the debugger, the figure displays but nothing is plot and I can see that my loop doesn't progress past the first iteration, first waiting for the figure window to be closed and then continuing.
You should never be using a loop when animating in matplotlib.
The animate function gets called automatically based on your interval.
Something like this should work
def animate(i, x=[], y=[]):
plt.cla()
x.append(i)
y.append(random.randint(0, 10))
plt.plot(x, y)
if __name__ == "__main__":
fig = plt.figure()
ani = FuncAnimation(fig, animate, interval=700)
plt.show()
trying to elaborate on #dumbpotato21 answer, here my attempt:
import random
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
def data():
cnt = 0
x = []
y = []
for i in range(1,10):
# x = []
# y = []
x.append(cnt*i)
y.append(random.randint(0, 10))
cnt += 1
yield x, y, cnt
input('any key to exit !!!')
quit()
def init_animate():
pass
def animate( data, *fargs) :
print('data : ', data, '\n data type : ', type(data), ' cnt : ', data[2])
plt.cla()
x = [i*k for i in data[0]]
y = [i*p for i in data[1]]
plt.plot(x,y)
if __name__ == "__main__":
fig = plt.figure()
k = 3
p = 5
ani = FuncAnimation(fig, animate, init_func=init_animate, frames=data, interval=700, fargs = [k,p])
plt.show()
There are a number of alternatives which might come in handy in different situations. Here is one that I have used:
import matplotlib.pyplot as plt
import numpy as np
from time import sleep
x = np.linspace(0, 30, 51)
y = np.linspace(0, 30, 51)
xx, yy = np.meshgrid(x, y)
# plt.style.use("ggplot")
plt.ion()
fig, ax = plt.subplots()
fig.canvas.draw()
for n in range(50):
# compute data for new plot
zz = np.random.randint(low=-10, high=10, size=np.shape(xx))
# erase previous plot
ax.clear()
# create plot
im = ax.imshow(zz, vmin=-10, vmax=10, cmap='RdBu', origin='lower')
# Re-render the figure and give the GUI event loop the chance to update itself
# Instead of the two lines one can use "plt.pause(0.001)" which, however gives a
# decepracted warning.
# See https://github.com/matplotlib/matplotlib/issues/7759/ for an explanation.
fig.canvas.flush_events()
sleep(0.1)
# make sure that the last plot is kept
plt.ioff()
plt.show()
Additionally, the set_data(...) method of a line plot or imshow object is useful if only the data changes and you don't want to re-drw the whole figure (as this is very time consuming).
I have just started learning python to plot realtime gragh. I have tried solutions provided on stackoverflow but none of them are working. Below is my code and it isn't woorking. Please help
import numpy as np
import matplotlib.pyplot as plt
import pyautogui as pg
from matplotlib.animation import FuncAnimation
%matplotlib notebook
binSize = 512
# fig(ax1,ax2) = plt.subplots(2,figsize=(12,6))
f = []
def animate(i):
try:
while True:
x, y = pg.position()
f.append(x)
except KeyboardInterrupt:
print('')
# f.append(15)
if len(f)<binSize :
plt.cla()
plt.plot(f, color='c',LineWidth=1.5,label="Noisy")
else:
plt.cla()
plt.plot(f[-binSize:],color='c',LineWidth=1.5,label="Noisy")
ani = FuncAnimation(plt.gcf(),animate,interval=1);
So I have updated the code and trying to draw two subplots but after sometime
Upper graph stopped clearing the canvas (Mouse X coordinates)
Lower graph stopped updating the plot (FFT)
When data grows beyond the binSize, notebook freezes and plots update really slowly
%matplotlib notebook
binSize = 256
# fig(ax1,ax2) = plt.subplots(2,figsize=(12,6))
f = []
t = 0
dt = 1
fig,axs = plt.subplots(2,1)
def animate(i):
x, y = pg.position()
f.append(x)
n = len(f)
if n<binSize :
plt.sca(axs[0])
plt.cla()
plt.plot(f, color='c',LineWidth=1.5,label="MOUSE")
else:
fhat = np.fft.fft(f,binSize)
PSD = fhat*np.conj(fhat)/binSize
freq = (1/(dt*binSize))*np.arange(binSize)
L = np.arange(1,np.floor(binSize/2),dtype='int')
# update the code third time
axs[0].clear()
axs[0].plot(f[-binSize:], color='c',LineWidth=1.5,label="MOUSE")
# axs[0].xlim(0,binSize) # this stopped the FFT graph to be plotted
# plt.cla()
axs[1].clear()
axs[1].plot(freq[L],PSD[L],color='r',LineWidth=2,label="FFT")
# plt.xlim(t[0],t[-1])
# plt.legend()
# plt.sca(axs[1])
# plt.plot(freq[L],PSD[L],color='c',LineWidth=2,label="Mouse FFT")
# plt.xlim(0,300)
# plt.legend()
# plt.cla()
# plt.plot(f[-binSize:],color='c',LineWidth=1.5,label="Mouse")
ani = FuncAnimation(plt.gcf(),animate,interval=dt)
To make it faster you may reduce data like in other answer
f.pop(0)
I use also different method to update plot which works much faster on my computer.
I create empty plots at start
# needs `,` to get first element from list
p1, = axs[0].plot([], [], color='c', LineWidth=1.5, label="MOUSE")
p2, = axs[1].plot([], [], color='r', LineWidth=2, label="FFT")
and later only update data in plots without clear() and plot() again
xdata = range(len(f))
ydata = f
p1.set_data(xdata, ydata)
and
# replace data in plot
xdata = range(binSize)
ydata = f[-binSize:]
p1.set_data(xdata, ydata)
#p1.set_xdata(xdata)
#p1.set_ydata(ydata)
# replace data in plot
xdata = freq[:(binSize//2)]
ydata = PSD[:(binSize//2)]
p2.set_data(xdata, ydata)
It needs only to run code which rescale plot
# rescale view
axs[0].relim()
axs[0].autoscale_view(True,True,True)
axs[1].relim()
axs[1].autoscale_view(True,True,True)
animate() has to also return new plots
# return plots
return p1, p2
And FuncAnimation() has to blit them
ani = FuncAnimation(..., blit=True)
EDIT:
Animation works much, much faster also because I run it normally python script.py, not in Jupuyter Notebook
EDIT:
when I run normally I found one problem which I could find solution: it doesn't update values/ticks on axes. Jupyter Notebook doesn't have this problem.
import numpy as np
import matplotlib.pyplot as plt
import pyautogui as pg
from matplotlib.animation import FuncAnimation
%matplotlib notebook
binSize = 256
f = []
t = 0
dt = 1
fig, axs = plt.subplots(2, 1)
# needs `,` to get first element from list
p1, = axs[0].plot([], [], color='c', LineWidth=1.5, label="MOUSE")
p2, = axs[1].plot([], [], color='r', LineWidth=2, label="FFT")
freq = np.arange(binSize)/(dt*binSize)
def animate(i):
x, y = pg.position()
n = len(f)
if n < binSize :
f.append(x)
# replace data in plot
xdata = range(len(f))
ydata = f
p1.set_data(xdata, ydata)
#p1.set_xdata(xdata)
#p1.set_ydata(ydata)
else:
f.pop(0)
f.append(x)
fhat = np.fft.fft(f, binSize)
PSD = fhat * np.conj(fhat) / binSize
# replace data in plot
#xdata = range(binSize)
ydata = f[-binSize:]
#p1.set_data(xdata, ydata)
#p1.set_xdata(xdata)
p1.set_ydata(ydata)
# replace data in plot
xdata = freq[:(binSize//2)]
ydata = PSD[:(binSize//2)]
p2.set_data(xdata, ydata)
# rescale view
axs[0].relim()
axs[0].autoscale_view(True,True,True)
axs[1].relim()
axs[1].autoscale_view(True,True,True)
# return plots
return p1, p2
ani = FuncAnimation(plt.gcf(), animate, interval=dt, blit=True)
plt.show()
You should try this. Instead of clearing the plt clear axs[0] and so on. Also, instead of plotting on plt.plot, plot on axs[0].plot
%matplotlib notebook
binSize = 256
# fig(ax1,ax2) = plt.subplots(2,figsize=(12,6))
f = []
t = 0
dt = 1
fig,axs = plt.subplots(2,1)
plt.sca(axs[0])
plt.sca(axs[1])
def animate(i):
x, y = pg.position()
n = len(f)
if n<binSize :
f.append(x*100)
axs[0].clear()
axs[0].plot(f, color='c',LineWidth=1.5,label="MOUSE")
else:
f.pop(0)
f.append(x)
fhat = np.fft.fft(f,binSize)
PSD = fhat*np.conj(fhat)/binSize
freq = (1/(dt*binSize))*np.arange(binSize)
L = np.arange(1,np.floor(binSize/2),dtype='int') # index array of [1,2,3..... binsize/2] type int
axs[0].clear()
axs[0].plot(f[-binSize:], color='c',LineWidth=1.5,label="MOUSE")
axs[1].clear()
axs[1].plot(freq[L],PSD[L],color='r',LineWidth=2,label="FFT")
ani = FuncAnimation(plt.gcf(),animate,interval=dt)
plt.show()
I need to animate something rather simple but I do need as well a slider to let the user interactively changes the parameters of the animation. I'd like it to happen online; i.e. if the user changes a parameter while the animation is playing, the animation should transit smoothly from its old dynamics to its new one.
So far I've written a function that takes arguments and makes an animation. But it's not interactive in the sense I mentioned before. There are no sliders or anything really interactive in my code. Nevertheless, the animation part is running smoothly at least.
Here is a simplified version of my code: A point revolves around the center with a specified distance r from the center and an angular velocity w. User can give them as arguments to see the animation. (If you saw something in the code that is never used don't bother yourself with it, it is probably because I forgot to trim it from the original code with is much longer.)
import numpy as np
%matplotlib notebook
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
def simplified_code(w,r):
fps = 36
M = int(.75*fps*2*np.pi/w)
T_final = 2*np.pi/w*16
def positions(t):
x = r*np.cos(w*t)
y = r*np.sin(w*t)
return x,y
fig = plt.figure()
ax = fig.add_subplot(111, aspect='equal', autoscale_on=False, )
ax.grid()
# position
x_e, y_e = [], []
# trajectory and center
traj_e, = plt.plot([],[],'-g',lw=1)
e, = plt.plot([], [], 'ok')
# time
time_text = ax.text(0.02, 0.95, '', transform=ax.transAxes)
def init():
ax.set_xlim(-(r+0.5), (r+0.5))
ax.set_ylim(-(r+0.5), (r+0.5))
ax.plot([0], ms=7, c='k',marker='o')
return d,e,traj_e
def update(frame):
x,y = positions(frame)
x_e.append(x)
y_e.append(y)
traj_e.set_data(x_e[-M:], y_e[-M:])
e.set_data(x, y)
time_text.set_text('time = %.1f' % frame)
return traj_d,traj_e,d,e, orb
return FuncAnimation(fig, update, frames=np.linspace(0, T_final, T_final*fps),
init_func=init, blit=True, interval=1./36*1000)
Note that it's possible to stop the animation, change the parameters via a slider and rerun it. I want to avoid this pause in the animation. I'd appreciate any help.
Thanks to #ImportanceOfBeingErnest I managed to combine update function of the animation and the one with the sliders:
import numpy as np
%matplotlib notebook
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib.widgets import Slider
fig = plt.figure(figsize=(6,7))
ax = fig.add_subplot(111, aspect='equal', autoscale_on=False, position=[.15,.15,.75,.75] )
ax.grid()
w = 2
r = 1
fps = 36
M= 1024#
T_final = 256
x_e, y_e = [], []
orb_x, orb_y = [], []
# trajectories
traj_e, = ax.plot(x_e,y_e,'-g',lw=1)
# center
e, = ax.plot([], [], 'ok')
# orbit
orb, = ax.plot([], [], '.r',ms=1)
# time
time_text = ax.text(0.02, 0.95, '', transform=ax.transAxes)
def positions(t):
x = r*np.cos(w*t) # epicycle
y = r*np.sin(w*t) # epicycle
return x,y
def orbit(r):
phi = np.linspace(0, 2*np.pi, 360)
orb_x = r*np.cos(phi)
orb_y = r*np.sin(phi)
return orb_x,orb_y
def init():
ax.plot([0], ms=7, c='k',marker='o')
return e,traj_e
def update(t):
global r, w
w = s_w.val
r = s_r.val
ax.set_xlim(-(r)*1.1, (r)*1.1)
ax.set_ylim(-(r)*1.1, (r)*1.1)
x,y = positions(t)
x_e.append(x)
y_e.append(y)
traj_e.set_data(x_e[-M:-1], y_e[-M:-1])
orb.set_data(orbit(r))
e.set_data(x, y)
time_text.set_text('time = %.1f' % t)
return traj_e,e, orb
ax_w = plt.axes([0.1, 0.05, 0.35, 0.03])#, facecolor=axcolor)
ax_r = plt.axes([0.55, 0.05, 0.35, 0.03])#, facecolor=axcolor)
s_w = Slider(ax_w, r'$\omega$', -20, 20, valinit=w, valstep=0.2)
s_r = Slider(ax_r, r'r', 0, 5, valinit=r, valstep=0.2)
s_w.on_changed(update)
s_r.on_changed(update)
def anim():
fig.canvas.draw_idle()
return FuncAnimation(fig, update, frames=np.linspace(0, T_final, T_final*fps),
init_func=init, blit=True, interval=30)
anim()
Using this piece of code I can change the values of r and w without pausing or restarting the animation from scratch. Another problem appears with this code though which is that the point jumps to some random position on the circle and then jumps back the expected trajectory. I'd address it in another question.
From an earlier question, it transpired that a piece of code was leading to different animations on my PC as it was to another commenter. I have since re-written the code to make it a little simpler, as was suggested:
from numpy import sin, cos
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# create a time array from 0..100 sampled at 0.05 second steps
dt = 0.025
t = np.arange(0.0, 20, dt)
length = len(t)
def listmaker(n):
return [0]*n
th1 = listmaker(length)
th2 = listmaker(length)
#dummy data
for i in range(0,length):
th1[i] = 0.01*i
th2[i] = 0.05*i
x1 = sin(th1)
y1 = -cos(th1)
x2 = sin(th2) + x1
y2 = -cos(th2) + y1
fig = plt.figure()
ax = fig.add_subplot(111, autoscale_on=False, xlim=(-2, 2), ylim=(-2, 2))
ax.grid()
line, = ax.plot([], [], 'o-', lw=2)
time_template = 'time = %.1fs'
time_text = ax.text(0.05, 0.9, '', transform=ax.transAxes)
def init():
line.set_data([], [])
time_text.set_text('')
return line, time_text
def animate(i):
thisx = [0, x1[i], x2[i]]
thisy = [0, y1[i], y2[i]]
line.set_data(thisx, thisy)
time_text.set_text(time_template % (i*dt))
return line, time_text
ani = animation.FuncAnimation(fig, animate, np.arange(1, length),
interval=25, blit=True, init_func=init)
# ani.save('double_pendulum.mp4', fps=15)
plt.show()
The issue, as shown in the other thread, is that since the interval (note that the interval argument is in milliseconds, hence the factor of 1000 difference) in the FuncAnimation is the same as the time step dt, the animation should run at "real time" i.e. the time tracker at the top left of the figure should run at the same speed as a normal clock. While this seemed to be the case for the other commenter, it was not the case on my own PC. I am hoping someone else is also able to reproduce the issue, so I can be pointed in the right direction.
I have no idea what is relevant, but I am running this code on Python 3.7, Idle 3.6.6 on a Windows machine.
How can I specify the duration when saving a matplotlib animation to a file? Usually it would be given by the frame argument of animation.FuncAnimation(), but not when using a generator to create the frames of the animation. E.g. using this example
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
def simData():
t_max = 10.0
dt = 0.05
x = 0.0
t = 0.0
while t < t_max:
x = np.sin(np.pi*t)
t = t + dt
yield x, t
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' # prints running simulation time
time_text = ax.text(0.05, 0.9, '', transform=ax.transAxes)
ani = animation.FuncAnimation(fig, simPoints, simData)
#plt.show()
ani.save('animation.mp4', writer="avconv", codec="libx264")
creates a 20 sec video displaying approx. five sec of "simulation time", half of the frames the generator would generate when displaying it with plt.show().
You are missing the save_count keyword on FuncAnimation. If you pass it a generator, then you may pass the number of frames to save:
ani = animation.FuncAnimation(fig, simPoints, simData, save_count=200)
The iteration seems to go on until either the generator is exhausted or the save_count is reached. The default value is 100 even though it does not seem to be documented very clearly outside of the source code.