Python matplotlib animating the path of an object - python

I've been fiddling with this bit of Python code to simualate a spring-pendulum system. I altered the equation slightly and it plots fine. However, I also want to add a persistent trace after it like in this gif.
Here is my full code (I can't trim it down any more since you need the ODE solved to generate the plotted data), the relevant bit is near the end:
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from numpy import sin, cos, pi, array
spring_constant = 22.93
length = 0.16
mass = 0.1
# initial conditions
init = array([-0.35, 0, 0.08, 1]) # initial values
#array([theta, theta_dot, x, x_dot])
#Return derivatives of the array z (= [theta, theta_dot, x, x_dot])
def deriv(z, t, spring_k, spring_l, bob_mass):
k = spring_k
l = spring_l
m = bob_mass
g = 9.8
theta = z[0]
thetadot = z[1]
x = z[2]
xdot= z[3]
return array([
thetadot,
(-1.0/(l+x)) * (2*xdot*thetadot + g*sin(theta)),
xdot,
g*cos(theta) + (l+x)*thetadot**2 - (k/m)*x
])
#Create time steps
time = np.linspace(0.0,10.0,1000)
#Numerically solve ODE
y = odeint(deriv,init,time, args = (spring_constant, length, mass))
l = length
r = l+y[:,2]
theta = y[:,0]
dt = np.mean(np.diff(time))
x = r*sin(theta)
y = -r*cos(theta)
##MATPLOTLIB BEGINS HERE##
fig = plt.figure()
ax = fig.add_subplot(111, autoscale_on=False,
xlim=(-1.2*r.max(), 1.2*r.max()),
ylim=(-1.2*r.max(), 0.2*r.max()), aspect = 1.0)
ax.grid()
##ANIMATION STUFF BEGINS HERE##
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, x[i]]
thisy = [0, y[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, len(y)),
interval=25, blit=True, init_func=init)
plt.show()
I tried making a list of points that gets appended to every time the animation loop calls, and then drawing all of those points so far each frame:
time_template = 'time = %.1fs'
time_text = ax.text(0.05, 0.9, '', transform=ax.transAxes)
foox = []
fooy = []
def init():
line.set_data([], [])
foo.set_data([], [])
time_text.set_text('')
return line, time_text, foo
def animate(i):
thisx = [0, x[i]]
thisy = [0, y[i]]
foox += [x[i]]
fooy += [y[i]]
line.set_data(thisx, thisy)
foo.set_data(foox, fooy)
time_text.set_text(time_template%(i*dt))
return line, time_text, foo
But I get
UnboundLocalError: local variable 'foox' referenced before assignment
Which I guess means it doesn't like it when you use a global variable? I'm not sure how to keep a history of which points have been drawn without using a variable outside of the animate() scope. Anyone know how?
Thank you.
EDIT:
I solved it. I was using += instead of .append() by mistake. Now I feel like an idiot.
For posterity it should be:
def animate(i):
thisx = [0, x[i]]
thisy = [0, y[i]]
foox.append(x[i])
fooy.append(y[i])
line.set_data(thisx, thisy)
foo.set_data(foox, fooy)
time_text.set_text(time_template%(i*dt))
return line, time_text, foo

You are modifying global variables in your animate function, without declaring them as global
foo and line are also redundant
Other than that, your animation works well; you can run the following code to see it:
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from numpy import sin, cos, pi, array
spring_constant = 22.93
length = 0.16
mass = 0.1
# initial conditions
init = array([-0.35, 0, 0.08, 1]) # initial values
#array([theta, theta_dot, x, x_dot])
#Return derivatives of the array z (= [theta, theta_dot, x, x_dot])
def deriv(z, t, spring_k, spring_l, bob_mass):
k = spring_k
l = spring_l
m = bob_mass
g = 9.8
theta = z[0]
thetadot = z[1]
x = z[2]
xdot= z[3]
return array([
thetadot,
(-1.0/(l+x)) * (2*xdot*thetadot + g*sin(theta)),
xdot,
g*cos(theta) + (l+x)*thetadot**2 - (k/m)*x
])
#Create time steps
time = np.linspace(0.0,10.0,1000)
#Numerically solve ODE
y = odeint(deriv,init,time, args = (spring_constant, length, mass))
l = length
r = l+y[:,2]
theta = y[:,0]
dt = np.mean(np.diff(time))
x = r*sin(theta)
y = -r*cos(theta)
##MATPLOTLIB BEGINS HERE##
fig = plt.figure()
ax = fig.add_subplot(111, autoscale_on=False,
xlim=(-1.2*r.max(), 1.2*r.max()),
ylim=(-1.2*r.max(), 0.2*r.max()), aspect = 1.0)
ax.grid()
##ANIMATION STUFF BEGINS HERE##
line, = ax.plot([], [], 'o-', lw=2)
time_template = 'time = %.1fs'
time_text = ax.text(0.05, 0.9, '', transform=ax.transAxes)
foox = []
fooy = []
#foo.set_data(foox, fooy)
def init():
global line, time_text, foo
line.set_data([], [])
# foo.set_data([], [])
time_text.set_text('')
return line, time_text#, foo
def animate(i):
global foox, fooy, foo
thisx = [0, x[i]]
thisy = [0, y[i]]
foox += [x[i]]
fooy += [y[i]]
line.set_data(thisx, thisy)
# foo.set_data(foox, fooy)
time_text.set_text(time_template%(i*dt))
return line, time_text#, foo
ani = animation.FuncAnimation(fig, animate, np.arange(1, len(y)), interval=25, blit=False, init_func=init)
plt.show()
I've set blit=False because last I checked, blit was not working on OSX

I solved it. I was using += instead of .append() by mistake. Now I feel like an idiot.
For posterity it should be:
def animate(i):
thisx = [0, x[i]]
thisy = [0, y[i]]
foox.append(x[i])
fooy.append(y[i])
line.set_data(thisx, thisy)
foo.set_data(foox, fooy)
time_text.set_text(time_template%(i*dt))
return line, time_text, foo

Related

'FuncAnimation' object has no attribute '_resize_id'

I am trying to plot a single pendulum using Eulers method and with given theta values and formula in python but I am getting an Attribute error on FuncAnimation saying 'FuncAnimation' object has no attribute '_resize_id'. Does anyone know what I'm doing wrong here?
# Liður 2
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
def ydot(t, y):
g = 9.81
l = 1
z1 = y[1]
z2 = -g/l*np.sin(y[0])
return np.array([z1, z2])
def eulerstep(t, x, h):
return ([x[j]+h*ydot(t,x)[j] for j in range(len(x))])
def eulersmethod(Theta0, T, n):
z = Theta0
h = T/n
t = [i*h for i in range(n)]
theta = [[],[]]
for i in range(n):
z = eulerstep(t[i], z, h)
theta[0].append(z[0])
theta[1].append(z[1])
return(t, theta[0], theta[1])
def animate_pendulum(x, y, h):
fig = plt.figure(figsize=(8,8))
ax = fig.add_subplot(autoscale_on = False, xlim=(-2.2, 2.2), ylim = (-2.2, 2.2))
ax.grid()
line = ax.plot([],[], 'o', c='blue', lw=1)
time_text = ax.text(0.05, 0.9, '', transform = ax.transAxes)
def animate(i):
xline = [0, x[1]]
yline = [0, y[1]]
line.set_data(xline, yline)
time_text.set_text(f"time = {i*h:1f}s")
return line, time_text
ani = FuncAnimation(
fig, animate, len(x), interval = h*1000, blit = True, repeat = False
)
plt.show()
def min():
L=2
T=20
n=500
h=T/n
y_0 = [np.pi/12, 0]
t, angle, velocity = eulersmethod(y_0, T, n)
x, y = L*np.sin(angle[:]), -L*np.cos(angle[:])
animate_pendulum(x, y, h)
min()
Axes.plot() returns a list of lines, see the docs. You Can unpack the list by adding a comma at the variable assignment. This code runs on my machine:
# Liður 2
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
def ydot(t, y):
g = 9.81
l = 1
z1 = y[1]
z2 = -g/l*np.sin(y[0])
return np.array([z1, z2])
def eulerstep(t, x, h):
return ([x[j]+h*ydot(t,x)[j] for j in range(len(x))])
def eulersmethod(Theta0, T, n):
z = Theta0
h = T/n
t = [i*h for i in range(n)]
theta = [[],[]]
for i in range(n):
z = eulerstep(t[i], z, h)
theta[0].append(z[0])
theta[1].append(z[1])
return(t, theta[0], theta[1])
def animate_pendulum(x, y, h):
fig = plt.figure(figsize=(8,8))
ax = fig.add_subplot(autoscale_on = False, xlim=(-2.2, 2.2), ylim = (-2.2, 2.2))
ax.grid()
line, = ax.plot([],[], 'o', c='blue', lw=1)
time_text = ax.text(0.05, 0.9, '', transform = ax.transAxes)
def animate(i):
xline = [0, x[1]]
yline = [0, y[1]]
line.set_data(xline, yline)
time_text.set_text(f"time = {i*h:1f}s")
return line, time_text
ani = FuncAnimation(
fig, animate, len(x), interval = h*1000, blit = True, repeat = False
)
plt.show()
def min():
L=2
T=20
n=500
h=T/n
y_0 = [np.pi/12, 0]
t, angle, velocity = eulersmethod(y_0, T, n)
x, y = L*np.sin(angle[:]), -L*np.cos(angle[:])
animate_pendulum(x, y, h)
min()

Wrong matplotlib animation

I have the following code that should draw a cycloid with animation and save it to a gif
but after running the program, a white square appears that covers everything, I can't find the reason cycloid_animation
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.animation import FuncAnimation, PillowWriter
plt.rcParams['animation.html'] = 'html5'
R = 1
def circle(a, b, r):
# (a,b): the center of the circle
# r: the radius of the circle
# T: The number of the segments
T = 100
x, y = [0]*T, [0]*T
for i,theta in enumerate(np.linspace(0,2*np.pi,T)):
x[i] = a + r*np.cos(theta)
y[i] = b + r*np.sin(theta)
return x, y
# Calculate the cycloid line
thetas = np.linspace(0,4*np.pi,100)
cycloid_x = R*(thetas-np.sin(thetas))
cycloid_y = R*(1-np.cos(thetas))
cycloid_c = R*thetas
fig = plt.figure()
lns = []
trans = plt.axes().transAxes
for i in range(len(thetas)):
x,y = circle(cycloid_c[i], R, R)
ln1, = plt.plot(x, y, 'g-', lw=2)
ln2, = plt.plot(cycloid_x[:i+1] ,cycloid_y[:i+1], 'r-', lw=2)
ln3, = plt.plot(cycloid_x[i], cycloid_y[i], 'bo', markersize=4)
ln4, = plt.plot([cycloid_c[i], cycloid_x[i]], [R,cycloid_y[i]], 'y-', lw=2)
tx1 = plt.text(0.05, 0.8, r'$\theta$ = %.2f $\pi$' % (thetas[i]/np.pi), transform=trans)
lns.append([ln1,ln2,ln3,ln4,tx1])
plt.xlim(0,15)
plt.ylim(0,3)
plt.xlabel('x')
plt.ylabel('y')
plt.grid()
plt.axes().set_aspect('equal')
ani = animation.ArtistAnimation(fig, lns, interval=50)
#ani.save('cycloid_ArtistAnimation.mp4',writer='ffmpeg')
ani.save('cycloid_ArtistAnimation.gif',writer='pillow')
ani
Each time you call plt.axis() you are creating a new axis on top of the figure. Since what you want is to get the current axis and then apply the transformations, after creating the figure you should call plt.gca() to get the current axis and use that instead.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.animation import FuncAnimation, PillowWriter
plt.rcParams['animation.html'] = 'html5'
R = 1
def circle(a, b, r):
# (a,b): the center of the circle
# r: the radius of the circle
# T: The number of the segments
T = 100
x, y = [0]*T, [0]*T
for i,theta in enumerate(np.linspace(0,2*np.pi,T)):
x[i] = a + r*np.cos(theta)
y[i] = b + r*np.sin(theta)
return x, y
# Calculate the cycloid line
thetas = np.linspace(0,4*np.pi,100)
cycloid_x = R*(thetas-np.sin(thetas))
cycloid_y = R*(1-np.cos(thetas))
cycloid_c = R*thetas
fig = plt.figure()
lns = []
trans = plt.gca().transAxes #<=== HERE
for i in range(len(thetas)):
x,y = circle(cycloid_c[i], R, R)
ln1, = plt.plot(x, y, 'g-', lw=2)
ln2, = plt.plot(cycloid_x[:i+1] ,cycloid_y[:i+1], 'r-', lw=2)
ln3, = plt.plot(cycloid_x[i], cycloid_y[i], 'bo', markersize=4)
ln4, = plt.plot([cycloid_c[i], cycloid_x[i]], [R,cycloid_y[i]], 'y-', lw=2)
tx1 = plt.text(0.05, 0.8, r'$\theta$ = %.2f $\pi$' % (thetas[i]/np.pi), transform=trans)
lns.append([ln1,ln2,ln3,ln4,tx1])
plt.xlim(0,15)
plt.ylim(0,3)
plt.xlabel('x')
plt.ylabel('y')
plt.grid()
plt.gca().set_aspect('equal') #<=== And HERE
ani = animation.ArtistAnimation(fig, lns, interval=50)
#ani.save('cycloid_ArtistAnimation.mp4',writer='ffmpeg')
ani.save('cycloid_ArtistAnimation.gif',writer='pillow')

Python function animation for two graphes (displayed after each other)

I have two data sets y1 = vol1 and y2 = vol2 for the same x range (0 to 5000 in steps of 10). I would like to use function animation in order to first animate y1 and after that animate y2 while the graph of y1 remains.
This is what I got from combing several examples (incl. this):
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
x = range(0, 5000, 10)
y1 = vol1
y2 = vol2
fig, ax = plt.subplots()
ax.set_xlim(0, 5000)
ax.set_ylim(0, 1000)
l1, = plt.plot([],[],'b-')
l2, = plt.plot([],[],'r-')
def init1():
return l1,
def init2():
return l2,
def animate1(i):
l1.set_data(x[:i],y1[:i])
return l1,
def animate2(i):
l2.set_data(x[:i-500],y2[:i-500])
return l2,
def gen1():
i = 0
while(i<500):
yield i
i += 1
def gen2():
j = 500
while(j<1000):
yield j
j += 1
ani1 = FuncAnimation(fig, animate1, gen1, interval=1, save_count=len(x),
init_func=init1, blit=True,
repeat=False)
ani2 = FuncAnimation(fig, animate2, gen2, interval=1, save_count=len(x),
init_func=init2, blit=True,
repeat=False)
# ani.save('ani.mp4')
plt.show()
My idea was to make two 'counters' gen1 andgen2 but since I have the same x values for both data sets, I tried to compensate that in the animate2 function. But this doesn't work..
Obviously, I'm quite new to python and I appreciate any help.
I would do just one animation, keeping track of the frame with respect to the line length:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation, FFMpegWriter
x = np.linspace(0, 2 * np.pi)
y1 = np.sin(x)
y2 = np.cos(x)
k = 0
fig, ax = plt.subplots()
ax.set_xlim(0, x.max())
ax.set_ylim(-1.5, 1.5)
l1, = plt.plot([],[],'b-')
l2, = plt.plot([],[],'r-')
def animate1(i):
global k
if k > 2 * len(x):
# reset if "repeat=True"
k = 0
if k <= len(x):
l1.set_data(x[:k],y1[:k])
else:
l2.set_data(x[:k - len(x)],y2[:k - len(x)])
k += 1
ani1 = FuncAnimation(fig, animate1, frames=2*len(x), interval=1, repeat=True)
writer = FFMpegWriter(fps=10)
ani1.save("test.mp4", writer=writer)
plt.show()

How to create an animation with a filled 'span?

I've created an animated plot of a wavefunction, psi:
def psi(x, t):
real = 0.4*np.cos(0.4*x - 0.08*t) + 0.6*np.cos(0.6*x - 0.18*t)
imag = 0.4*np.sin(0.4*x - 0.08*t) + 0.6*np.sin(0.6*x - 0.18*t)
square = real**2 + imag**2
return real, imag, square
I've then animated it successfully, however on adding the axvspan fill I've encountered an issue:
x = np.linspace(-10, 1000, 10000)
fig, (ax1, ax2) = plt.subplots(2,1)
line1, = ax1.plot([], [])
line2, = ax1.plot([], [])
line3, = ax2.plot([], [])
line = [line1, line2, line3]
def animate(i):
y1, y2, y3 = psi(x, t=i/2)
line1.set_data(x, y1)
line2.set_data(x, y2)
line3.set_data(x, y3)
spline = UnivariateSpline(x, y3-max(y3)/2, s=0)
r1, r2 = spline.roots()
ax2.axvspan(r1, r2, facecolor='b', alpha=0.5)
plt.legend(['Max Probability = %1.3f' % (max(y3))])
return line,
anim = animation.FuncAnimation(fig, animate, frames=600, interval = 100, blit=False, repeat=False)
It starts like
and it ends like .
Every iteration of the animation function, the fill increases across the page having started off as filling half the graph (I'd include a gif but at the moment that's a struggle I'm having with anaconda). I'm working under the assumption this is because the axes don't clear properly, however with the blit=false I assumed this wouldn't be a problem?
As asked for - the full psi function is detailed below:
n = 15
amp_scale = np.linspace(0, 0.8, n)
amp_init = norm.pdf(amp_scale, 0.4, 0.2)
#normalise wavefunction to prob=1
amp = []
for i in range(n):
amp_val = amp_init[i]/sum(amp_init)
amp.append(amp_val)
k = np.linspace(1.4, 2.6, n)
def psi (x, t=1, n=1, a = 1, k = 1, m = 1):
psi_real = 0
psi_imag = 0
for i in range(n):
a_val = a[i]
k_val = k[i]
w = (k_val**2)/(2*m)
psi_real+=a_val*np.cos(k_val*x - w*t)
psi_imag+=a_val*np.sin(k_val*x - w*t)
psi_squared = psi_real**2 + psi_imag**2
return psi_real, psi_imag, psi_squared
In your current code, a new axvspan() is continually added, never removed. You could explicitly remove the old span inside animate(). Or, similarly to what happens to the lines, update the position. A span is internally represented as a polygon, of which the coordinates can be set via .set_xy().
The function psi in the post doesn't seem to be the same as the function that generated the example plots. This also made that I couldn't get to work to calculate the spline and the roots. I replaced them by some simpler positions to show how the span can be updated during the animation.
The code also adds explicit x and y limits, as they weren't set in the question's code.
import matplotlib.pyplot as plt
from matplotlib import animation
import numpy as np
from scipy.interpolate import UnivariateSpline
def psi(x, t):
# the function from the question is adapted to more resemble the plot
real = (0.4 * np.cos(0.4 * x - 0.08 * t) + 0.6 * np.cos(0.6 * x - 0.18 * t)) * np.exp(- (x - t) ** 2 / 5000)
imag = (0.4 * np.sin(0.4 * x - 0.08 * t) + 0.6 * np.sin(0.6 * x - 0.18 * t)) * np.exp(- (x - t) ** 2 / 5000)
square = real ** 2 + imag ** 2
return real, imag, square
x = np.linspace(-10, 1000, 10000)
fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True)
line1, = ax1.plot([], [])
line2, = ax1.plot([], [])
line3, = ax2.plot([], [])
line = [line1, line2, line3]
span1 = ax2.axvspan(0, 0, facecolor='b', alpha=0.2)
ax1.set_xlim(x[0], x[-1]/2)
ax1.set_ylim(-1, 1)
ax2.set_ylim(0, 1.1)
def animate(i):
y1, y2, y3 = psi(x, t=i / 2)
line1.set_data(x, y1)
line2.set_data(x, y2)
line3.set_data(x, y3)
# this didn't work for me, spline.roots() gave me a long array of values
spline = UnivariateSpline(x, y3 - max(y3) / 2, s=0)
r1, r2 = spline.roots()[[0, -1]] # [[0, -1]] takes the first and the last
# r1, r2 = i - 50, i + 50
span1.set_xy([[r1, 0], [r1, 1], [r2, 1], [r2, 0], [r1, 0]])
plt.legend(['Max Probability = %1.3f' % (max(y3))])
return line, span1, ax2.legend_,
anim = animation.FuncAnimation(fig, animate, frames=600, interval=100, blit=False, repeat=False)
plt.plot()
The resulting end frame looks like (note that a slightly different function is used):

Animation using animation.FuncAnimation from matplotlib playing slower than expected

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.

Categories

Resources