I am trying to plot an animation of a physics system. I've worked out the equations and it plots, but there's a second plot I don't want that keeps showing up and I can't get it to stop.
import numpy as np
from scipy import integrate
import matplotlib.pyplot as plt
from matplotlib import animation, rc
from IPython.display import HTML
# Input constants
m = 10 # mass (kg)
L = 4 # length (m)
g = 9.81 # gravity (m/s^2)
dt = 0.1 # time step size (seconds)
t_max = 40 # max sim time (seconds)
num_steps = 1 + int(t_max/dt)
theta_0 = np.pi/2 # initial angle (radians)
theta_dot_0 = 0 # initial angular velocity (rad/s)
state0 = [theta_0,theta_dot_0]
# Get timesteps
time_index = np.arange(0, t_max + dt, dt)
def derivatives(state, time_index, L=L, m=m, g=g):
theta_dot = state[1]
theta_ddot = -g*np.sin(state[0])/L
return theta_dot,theta_ddot
output = integrate.odeint(derivatives, state0, time_index)
theta = output[:,0]
theta_dot = output[:,1]
fig = plt.figure()
ax = fig.add_subplot(111, autoscale_on=True, xlim=(-2*L, 2*L), ylim=(-2*L, 2*L))
ax.set_aspect('equal')
ax.grid()
line, = ax.plot([], [], 'o-', lw=2)
time_template = 'time = %.1fs'
time_text = ax.text(0.05, 0.9, '', transform=ax.transAxes)
x = L*np.sin(theta)
y = -L*np.cos(theta)
def init():
# create empty object to put the points in
line.set_data([], [])
time_text.set_text('')
return line, time_text
def animate(t):
x_t = [0, x[t]]
y_t = [0, y[t]]
# add the point to the line
line.set_data(x_t, y_t)
# add the time text to plot
# time_template will be the text next to thetime
time_text.set_text(time_template % (t*dt))
return line, time_text
# we don't want the range of the steps to start at 0
# start it at one
ani = animation.FuncAnimation(fig, animate, range(1, num_steps),
interval=dt*1000, blit=True, init_func=init)
rc('animation', html='jshtml')
#ani.save('pendulum.mp4', fps=15)
ani
Here's the output:
The plot I want to get rid of is the one I circled with red. This is the entirety of my code, so it should be completely reproducible.
I tried several variations of trimming the plotting code but I wasn't able to debug why it's happening.
How can I get rid of this second plot?
A simple plt.close() before your call to ani will do the job.
Last few lines:
ani = animation.FuncAnimation(fig, animate, range(1, num_steps),
interval=dt*1000, blit=True, init_func=init)
rc('animation', html='jshtml')
#ani.save('pendulum.mp4', fps=15)
plt.close()
ani
Demo:
More info at this link.
Related
I'm trying to add to an animated plot of a random variable the dates in the x axes.
I've tried different things but the code is working just with a static x-axes array..
I made a small function to update the dates array T and random var array y the I called shift().
To see how the basic code (no dates) is behaving you need to uncomment every line that is followed by "# uncomment 1". Viceversa uncomment every line that has "# uncomment 0".
I don't know why I can't plot the dates in the x-axes.
This below is the code:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
import pandas as pd
import time
import datetime
plt.rcParams.update({'axes.facecolor':'lightgray'})
plt.rcParams.update({'figure.facecolor':'gray'})
# First set up the figure, the axis, and the plot element we want to animate
fig = plt.figure()
# ax = plt.axes()
ax = plt.axes(xlim=(0, 2), ylim=(-8, 8))
line, = ax.plot([], [], lw=2)
def shift(y_arr,y_i,cont_cascata):
print("cont_cascata:",cont_cascata)
if type(y_arr)==list:
y_arr.pop(0)
y_arr = y_arr+[y_i]
if type(y_arr) is np.ndarray:
print("np.array..")
y_arr = np.delete(y_arr, 0) # togliamo primo
y_arr = np.append(y_arr, y_i) # aggiungiamo ultimo
return y_arr
# initialization function: plot the background of each frame
def init():
line.set_data([], [])
return line,
# animation function. This is called sequentially
# pd._libs.tslibs.timestamps.Timestamp
def ts_array(n):
t0 = pd.Timestamp(2018,1,1,12,30)
T = []
for i in range(n):
t_i = t0+pd.Timedelta(minutes=i)
T = T+[t_i]
return T
# tarr = ts_array(n=100)
def animate(i):
global y,x,T
n = 100
if i==0:
y = np.round(np.random.normal(loc=0, scale=2, size=n), decimals=2)
x = np.linspace(0, 2, n) # uncomment 0
T = ts_array(n) # uncomment 1
y_i = np.round(np.random.normal(loc=0,scale=2),decimals=2)
t_i = T[-1]+pd.Timedelta(minutes=1) # uncomment1
y = shift(y_arr=y,y_i=y_i, cont_cascata=i)
T = shift(y_arr=T,y_i=t_i,cont_cascata=i) # uncomment 1
T = pd.DatetimeIndex(T) # uncomment 1
T = T.to_pydatetime() # uncomment 1
# line.set_data(x, y) # uncomment 0
line.set_data(T,y) # uncomment 1
time.sleep(0.5)
return line,
print("animate")
# call the animator. blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=200, interval=20, blit=True)
plt.show()
What should I do to make this code work properly? Thanks
You need to adjust the xlim of the axes to account for date time. Inside animate, just after line.set_data(T,y), try adding this:
ax.set_xlim(T.min(), T.max())
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
import matplotlib.dates as mdates
import pandas as pd
import time
import datetime
plt.rcParams.update({'axes.facecolor': 'lightgray'})
plt.rcParams.update({'figure.facecolor': 'gray'})
# First set up the figure, the axis, and the plot element we want to animate
fig = plt.figure() # include
ax = plt.axes() # include
# ax = plt.axes(xlim=(0, 2), ylim=(-8, 8))
line, = ax.plot([], [], lw=2)
## tilt dates
plt.setp(ax.xaxis.get_majorticklabels(), rotation=35)
def shift(y_arr, y_i, cont_cascata):
# print("cont_cascata:",cont_cascata)
if type(y_arr) == list:
y_arr.pop(0)
y_arr = y_arr + [y_i]
if type(y_arr) is np.ndarray:
# print("np.array..")
y_arr = np.delete(y_arr, 0) # togliamo primo
y_arr = np.append(y_arr, y_i) # aggiungiamo ultimo
return y_arr
# initialization function: plot the background of each frame
def init():
line.set_data([], [])
line.axes.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M")) # "%Y-%m-%d %H:%M:%S"
return line,
# animation function. This is called sequentially
# pd._libs.tslibs.timestamps.Timestamp
def ts_array(n):
t0 = pd.Timestamp(2018, 1, 1, 12, 00)
T = []
for i in range(n):
t_i = t0 + pd.Timedelta(minutes=i)
T = T + [t_i]
return T
def animate(i):
global y, x, T
print("i:", i)
n = 10
if i == 0:
y = np.round(np.random.normal(loc=0, scale=2, size=n), decimals=2)
x = np.linspace(6, 2, n) # uncomment 0
T = ts_array(n) # uncomment 1
y_i = np.round(np.random.normal(loc=6, scale=2), decimals=2)
t_i = T[-1] + pd.Timedelta(minutes=1) # uncomment1
y = shift(y_arr=y, y_i=y_i, cont_cascata=i)
T = shift(y_arr=T, y_i=t_i, cont_cascata=i) # uncomment 1
T = pd.DatetimeIndex(T) # uncomment 1
T = T.to_pydatetime() # uncomment 1
# line.set_data(x, y) # uncomment 0
line.set_data(T, y) # uncomment 1
ax.relim(visible_only=True)
ax.autoscale()
# ax.autoscale_view(True,True,True)
# time.sleep(0.5)
return line,
print("animate")
# call the animator. blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=200, interval=500) # blit = True
plt.show()
I have a animation of an orbit written in python. I want a legend that the label is a time "dt". This time updates (increases) along the orbit. For example, in the first frame of the gif the legend should be "dt", in the second, dt + dt and so on. I tried something that didn't work. Maybe I'm not using the correct commands. The error that appearas: TypeError: __init__() got multiple values for argument 'frames' . Does someone know how to do this legend? The code is above.
fig = plt.figure()
plt.xlabel("x (km)")
plt.ylabel("y (km)")
plt.gca().set_aspect('equal')
ax = plt.axes()
ax.set_facecolor("black")
circle = Circle((0, 0), rs_sun, color='dimgrey')
plt.gca().add_patch(circle)
plt.axis([-(rs_sun / 2.0) / u1 , (rs_sun / 2.0) / u1 , -(rs_sun / 2.0) / u1 , (rs_sun / 2.0) / u1 ])
# Legend
dt = 0.01
leg = [ax.plot(loc = 2, prop={'size':6})]
def update(dt):
for i in range(len(x)):
lab = 'Time:'+str(dt+dt*i)
leg[i].set_text(lab)
return leg
# GIF
graph, = plt.plot([], [], color="gold", markersize=3)
plt.close()
def animate(i):
graph.set_data(x[:i], y[:i])
return graph,
skipframes = int(len(x)/500)
if skipframes == 0:
skipframes = 1
ani = FuncAnimation(fig, update, animate, frames=range(0,len(x),skipframes), interval=20)
To update the labels in your legend you can use:
graph, = plt.plot([], [], color="gold",lw=5,markersize=3,label='Time: 0')
L=plt.legend(loc=1)
L.get_texts()[0].set_text(lab)
L.get_texts()[0].set_text(lab) should be called inside the update function to refresh the label at each iteration of the animation.
You can find a minimal example below to illustrate the process:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig,ax = plt.subplots()
dt = 0.01
N_frames=30
x=np.random.choice(100,size=N_frames,) #Create random trajectory
y=np.random.choice(100,size=N_frames,) #Create random trajectory
graph, = plt.plot([], [], color="gold",lw=5,markersize=3,label='Time: 0')
L=plt.legend(loc=1) #Define legend objects
def init():
ax.set_xlim(0, 100)
ax.set_ylim(0, 100)
return graph,
def animate(i):
lab = 'Time:'+str(round(dt+dt*i,2))
graph.set_data(x[:i], y[:i])
L.get_texts()[0].set_text(lab) #Update label each at frame
return graph,
ani = animation.FuncAnimation(fig,animate,frames=np.arange(N_frames),init_func=init,interval=200)
plt.show()
And the output gives:
I've been playing with the animation module from matplotlib and I realized I couldn't efficiently make a sine wave loop between two limits (in this case between -180° and 180°).
Like this...
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
fig = plt.figure()
ax = plt.axes(xlim=(0, 0.04), ylim=(-1.5, 1.5))
# initialize moving plots
line1, = ax.plot([], [], linewidth=2, label='sine')
line2, = ax.plot([], [], label='cosine')
ax.legend()
ax.grid()
def animate(i):
step = np.pi/30
# loop by hand...
if i < 30:
phase = i*step
elif 30 <= i < 90:
phase = -i*step
elif 90 <= i < 150:
phase = i*step
elif 150 <= i < 210:
phase = -i*step
else:
phase = i*step
x = np.linspace(0, 0.04, 1000)
y1 = np.sin( 2*np.pi*50*x - phase )
y2 = 0.5*np.cos( 2*np.pi*50*x + phase )
line1.set_data(x, y1)
line2.set_data(x, y2)
print('i:',i) # debug i
return line1, line2
anim = animation.FuncAnimation(fig, animate, interval=250, blit=True)
plt.show()
The reason is because I'm using the i variable, that is used for the frames count and only increases with time. Is there a way to loop indefinitely without writing if conditions until the end of time?
From this answer I found that is posible to refresh the data from the plot, and I've manage to make it loop almost like I wanted.
Adapted example... (workaround not complete)
import matplotlib.pyplot as plt
import numpy as np
def Yvalue(t, phase):
"""Function to plot"""
w = 2*np.pi*50
return np.sin(w*t + phase)
plt.ion() # You probably won't need this if you're embedding things in a tkinter plot...
step = np.pi/30 # steps for phase shifting
t = np.linspace(0, 0.04) # x values
y1 = Yvalue(t, 0) # y values
# starts figure
fig = plt.figure()
ax = plt.axes(xlim=(0, 0.04), ylim=(-1.5, 1.5))
# Returns a tuple of line objects, thus the comma
line1, = ax.plot(t, y1, linewidth=2, label='sine')
# static plot (cosine)
ax.plot(t, np.cos(2*np.pi*50*t), label='cosine static')
ax.legend()
ax.grid()
# initial values
phase = 0
direction = 1 # 1: shifting plot to left; 0: shifting plot to right
UpperLimit = np.pi
LowerLimit = -np.pi
# magic begins...
for something in range(210):
# while 1:
if direction and phase < UpperLimit:
phase += step
direction = 1
else:
phase -= step
direction = 0
# condition that helps to return to left shifting
if phase < LowerLimit:
direction = 1
line1.set_ydata( Yvalue(t, phase) )
fig.canvas.draw()
The problem with is that it doesn't allow me to close the window like it would be with the animation module. Therefore the program must be killed manually when changing the for loop by the while loop.
You would usually not use the animating function itself to calculate its animating parameter. Instead you would provide that parameter as argument to it using the frames argument.
In this case you would want the animating function to take the phase as argument. To create the phase, which is a kind of sawtooth function you can use numpy like
a = np.linspace(0,1, 30, endpoint=False)
phase = np.concatenate((a, 1-a, -a, a-1))*np.pi
Complete example:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
fig = plt.figure()
ax = plt.axes(xlim=(0, 0.04), ylim=(-1.5, 1.5))
line1, = ax.plot([], [], linewidth=2, label='sine')
line2, = ax.plot([], [], label='cosine')
ax.legend()
ax.grid()
x = np.linspace(0, 0.04, 1000)
a = np.linspace(0,1, 30, endpoint=False)
phase = np.concatenate((a, 1-a, -a, a-1))*np.pi
def animate(phase):
y1 = np.sin( 2*np.pi*50*x - phase )
y2 = 0.5*np.cos( 2*np.pi*50*x + phase )
line1.set_data(x, y1)
line2.set_data(x, y2)
return line1, line2
anim = animation.FuncAnimation(fig, animate, frames=phase, interval=50, blit=True)
plt.show()
I don't know if I understand your problem because I don't see problem to use second method (used in for loop) inside animate
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
fig = plt.figure()
ax = plt.axes(xlim=(0, 0.04), ylim=(-1.5, 1.5))
# initialize moving plots
line1, = ax.plot([], [], linewidth=2, label='sine')
line2, = ax.plot([], [], label='cosine')
ax.legend()
ax.grid()
# -------------------------------------------------
def func1(t, phase):
"""Function to plot"""
w = 2*np.pi*50
return np.sin( w*t + phase)
def func2(t, phase):
"""Function to plot"""
w = 2*np.pi*50
return np.sin( w*t - phase)
# -------------------------------------------------
t = np.linspace(0, 0.04)
step = np.pi/30
UpperLimit = np.pi
LowerLimit = -np.pi
direction = 1
phase = 0
def animate(i):
global direction
global phase
if direction:
phase += step
if phase >= UpperLimit:
direction = 0
else:
phase -= step
if phase < LowerLimit:
direction = 1
line1.set_data(t, func1(t, phase))
line2.set_data(t, func2(t, phase))
return line1, line2
anim = animation.FuncAnimation(fig, animate, interval=250, blit=True)
plt.show()
Or even without variable direction
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
fig = plt.figure()
ax = plt.axes(xlim=(0, 0.04), ylim=(-1.5, 1.5))
# initialize moving plots
line1, = ax.plot([], [], linewidth=2, label='sine')
line2, = ax.plot([], [], label='cosine')
ax.legend()
ax.grid()
# -------------------------------------------------
def func1(t, phase):
"""Function to plot"""
w = 2*np.pi*50
return np.sin( w*t + phase)
def func2(t, phase):
"""Function to plot"""
w = 2*np.pi*50
return np.sin( w*t - phase)
# -------------------------------------------------
t = np.linspace(0, 0.04)
step = np.pi/30
UpperLimit = np.pi
LowerLimit = -np.pi
phase = 0
def animate(i):
global phase
global step
phase += step
if phase >= UpperLimit or phase <= LowerLimit:
step = -step
line1.set_data(t, func1(t, phase))
line2.set_data(t, func2(t, phase))
return line1, line2
anim = animation.FuncAnimation(fig, animate, interval=250, blit=True)
plt.show()
I have a static plot which calculates in one step and a dynamically updating plot (animated). My codes displays it correctly but in different windows, how can I combine it in one plot window.
I am getting solutions for animating two plots simultaneously but not anything with one static and another dynamic
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import math
E50 = 5000
c = 5
phi = math.radians(30)
sig3 = 100
a = c/math.tan(phi)
# deviatoric load
qa = (sig3+a)*(2*math.sin(phi))/(1-math.sin(phi))
print(qa)
ultimateLoad = 200
def hyperbola():
stress = []
strain = []
q = 0
while q < ultimateLoad:
stress.append(q)
eps1 = (qa/(2*E50)) * (q/(qa-q))
strain.append(eps1)
q +=10
return strain, stress
def plotHyperbola():
strain, stress = hyperbola()
plt.plot(strain, stress ,'bo', linewidth=5, label='Existing Kernel' )
def data_gen():
load = 0
while load < ultimateLoad:
load += 10
# finally this yield function should give x any that needs to be plotted
yield load/5000, load
def init():
ax.set_ylim(-1.1, 300)
ax.set_xlim(0, 0.1)
del xdata[:]
del ydata[:]
line.set_data(xdata, ydata)
return line,
fig, ax = plt.subplots()
line, = ax.plot([], [], 'ro', lw=2)
ax.grid()
xdata, ydata = [], []
def run(data):
# update the data
t, y = data
xdata.append(t)
ydata.append(y)
xmin, xmax = ax.get_xlim()
plotHyperbola()
if t >= xmax:
ax.set_xlim(xmin, 2*xmax)
ax.figure.canvas.draw()
line.set_data(xdata, ydata)
return line,
# interval control the time in between each iteration
# repeat whether the whole process needs to be repeated
ani = animation.FuncAnimation(fig, run, data_gen, interval=1,
repeat=False, init_func=init)
plt.show()
I want to create an animation showing a diver jumps into water.
By the given parameters of original height of diver from the water, h and his mass, m, I defined a procedure in Python to calculate the moment he touches the water, Tc .
Knowing that he jumps vertically, X axis is fixed, and
Y axis obeys equation (1/2)gt^2 + h (g is gravitational constant)
How do I plot a graph while time t is in range(Tc), X and Y axis shows the projection the diver? (x is fixed and y depends on time t)
In the graphic window we are supposed to see a dot that 'jumps' from certain height vertically downwards, without seeing the line/trace of projection.
Here is part of my work. I don't know where to introduce Tc in the procedure:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
# First set up the figure, the axis, and the plot element we want to animate
fig = plt.figure()
ax = plt.axes(xlim=(0, 2), ylim=(-2, 2))
line, = ax.plot([], [], lw=2)
# initialization function: plot the background of each frame
def init():
line.set_data([], [])
return line,
# animation function. This is called sequentially
def animate(i):
x = np.empty(n) ; x.fill(1) # the vertical position is fixed on x-axis
y = 0.5*g*i^2 + h # the equation of diver's displacement on y axis
line.set_data(x, y)
return line,
# call the animator. blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=200, interval=20, blit=True)
plt.show()
Edit:
Here is the whole program. I applied and modified the suggestion given by #Mike Muller, but it didn't work. I don’t understand where it goes wrong. Hope you can clarify my doubts.
# -*- coding: utf-8 -*-
from math import *
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
def Plongeon():
h = input("height = ")
h = float(h)
m = input(" mass = ")
m = float(m)
global g
g = 9.8
g = float(g)
global Tc #calculate air time, Tc
Tc = sqrt(2*h/g)
Tc = round(Tc,2)
print Tc
# First set up the figure, the axis, and the plot element we want to animate
fig = plt.figure()
ax = plt.axes(xlim=(0, 2), ylim=(-2, h+1)) #ymax : initial height+1
line, = ax.plot([], [], ' o', lw=2)
Tc = int(Tc+1) #make Tc an integer to be used later in def get_y()
xs = [1] # the vertical position is fixed on x-axis
ys = [h, h]
# initialization function: plot the background of each frame
def init():
line.set_data([], [])
return line,
# animation function. This is called sequentially
def animate(y):
ys[-1] = y
line.set_data(xs, ys)
return line,
def get_y():
for step in range(Tc):
t = step / 100.0
y = -0.5*g*t**2 + h # the equation of diver's displacement on y axis
yield y
# call the animator. blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate, frames=get_y, interval=100)
plt.show()
Plongeon()
Answer based on original question
You need to use a generator to produce your y data. This works:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
# First set up the figure, the axis, and the plot element we want to animate
fig = plt.figure()
ax = plt.axes(xlim=(0, 2), ylim=(-2, 2))
line, = ax.plot([], [], ' o', lw=2)
g = 9.81
h = 2
tc = 200
xs = [1] # the vertical position is fixed on x-axis
ys = [h, h]
# animation function. This is called sequentially
def animate(y):
ys[-1] = y
line.set_data(xs, ys)
return line,
def get_y():
for step in range(tc):
t = step / 100.0
y = -0.5*g*t**2 + h # the equation of diver's displacement on y axis
yield y
# call the animator. blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate, frames=get_y, interval=100)
plt.show()
Integrated answer
This should work:
# -*- coding: utf-8 -*-
from math import *
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
def Plongeon():
h = float(input("height = "))
g = 9.81
#calculate air time, Tc
Tc = sqrt(2 * h / g)
# First set up the figure, the axis, and the plot element we want to animate
fig = plt.figure()
ax = plt.axes(xlim=(0, 2), ylim=(-2, h+1)) #ymax : initial height+1
line, = ax.plot([], [], ' o', lw=2)
step = 0.01 # animation step
xs = [1] # the vertical position is fixed on x-axis
ys = [h]
# animation function. This is called sequentially
def animate(y):
ys[-1] = y
line.set_data(xs, ys)
return line,
def get_y():
t = 0
while t <= Tc:
y = -0.5 * g * t**2 + h # the equation of diver's displacement on y axis
yield y
t += step
# call the animator. blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate, frames=get_y, interval=100)
plt.show()
Plongeon()
I removed unneeded lines. No need for global. Also mass has never been used anywhere in the program.
This is the most important part:
def get_y():
t = 0
while t <= Tc:
y = -0.5 * g * t**2 + h
yield y
t += step
You need to advance your time by an increment.