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.
Related
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'm not a beginner, but I'm also not advanced dev of python code.
I'm been trying to animate points movement in scatter plot and to put annotation on every point. All I have done is animation of one point with no annotation. I've searched similar solutions, but it's so confusing. Any help is welcome. This is what I've done.
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
import matplotlib.animation as animation
frame_count = 0
points = reading_file("some_data") # this method is not of intrest
def make_one_point(i):
global frame_count, points
ex = [1]
ey = [1]
ez = [1]
point = points[i]
frame = point[frame_count]
ex[0] = frame[0]
ey[0] = frame[1]
ez[0] = frame[2]
frame_count += 1
return ex, ey, ez
def update(i):
global frame_count, points
if frame_count < len(points[i]):
return make_one_point(i)
else:
frame_count = 0
return make_one_point(i)
fig = plt.figure()
ax1 = fig.add_subplot(111, projection='3d')
ax1.set_xlim3d(-500, 2000)
ax1.set_ylim3d(-500, 2000)
ax1.set_zlim3d(0, 2000)
x = [1]
y = [1]
z = [1]
scat = ax1.scatter(x,y,z)
def animate(i):
scat._offsets3d = update(0)
ani = animation.FuncAnimation(fig, animate,
frames=len(points[10]),
interval=100, repeat=True)
plt.show()
How to animate more points at the same time, and put annontation on every one of them? There are 50 points, and I'm not so consern about efficiency, just to make it work.
This code output is moving one point animation
It turns out animating Text in 3D was harder than I anticipated. Not surprisingly, I was able to find the solution to the problem in an answer from #ImportanceOfBeingErnest. I then simply adapted the code I had already written in a previous answer, and produced the following code:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D, proj3d
import matplotlib.animation as animation
N_points = 10
def update(num, my_ax):
# the following corresponds to whatever logic must append in your code
# to get the new coordinates of your points
# in this case, we're going to move each point by a quantity (dx,dy,dz)
dx, dy, dz = np.random.normal(size=(3,N_points), loc=0, scale=1)
debug_text.set_text("{:d}".format(num)) # for debugging
x,y,z = graph._offsets3d
new_x, new_y, new_z = (x+dx, y+dy, z+dz)
graph._offsets3d = (new_x, new_y, new_z)
for t, new_x_i, new_y_i, new_z_i in zip(annots, new_x, new_y, new_z):
# animating Text in 3D proved to be tricky. Tip of the hat to #ImportanceOfBeingErnest
# for this answer https://stackoverflow.com/a/51579878/1356000
x_, y_, _ = proj3d.proj_transform(new_x_i, new_y_i, new_z_i, my_ax.get_proj())
t.set_position((x_,y_))
return [graph,debug_text]+annots
# create N_points initial points
x,y,z = np.random.normal(size=(3,N_points), loc=0, scale=10)
fig = plt.figure(figsize=(5, 5))
ax = fig.add_subplot(111, projection="3d")
graph = ax.scatter(x, y, z, color='orange')
debug_text = fig.text(0, 1, "TEXT", va='top') # for debugging
annots = [ax.text2D(0,0,"POINT") for _ in range(N_points)]
# Creating the Animation object
ani = animation.FuncAnimation(fig, update, fargs=[ax], frames=100, interval=50, blit=True)
plt.show()
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.
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()
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.