Continuously change direction of shifting matplotlib animation? - python

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()

Related

How to add legend on matplotlib animations?

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:

Matplotlib Animation : Graph can't appear

Recently I have managed to do the animation. But now I can't make the tangent line went as I want (not even displayed yet). The formula of the tangent line is y=(2/r)(sqrt(1-((r^2)/4))-1)x +r. The formula is obtained from 2 circles equation (C1 and C2). C1(blue) : x^2+y^2=r^2, and C2(green) : (x-1)^2+y^2=1. My goal is to obtain this kind of animation as and my current animation goes like this .
How should the code looks like when the animation looks like the reference one (the first one)? Any comments and answers would be very helpful for me as a beginner, I appreciate it, Thank you.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots(1)
line, = ax.plot([], [], lw=2)
line2, = ax.plot([], [], lw=2)
ax.set_xlim(-5,5)
ax.set_ylim(-5,5)
# theta goes from 0 to 2pi
theta = np.linspace(0, 2*np.pi, 100)
# the radius of the circle
r = np.sqrt(1)
r2 = np.sqrt(4)
# compute x1 and x2
x1 = 1+r*np.cos(theta)
y1 = r*np.sin(theta)
x2 = r2*np.cos(theta)
y2 = r2*np.sin(theta)
# Move left y-axis and bottim x-axis to centre, passing through (0,0)
ax.spines['left'].set_position('center')
ax.spines['bottom'].set_position('center')
# Eliminate upper and right axes
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
# Show ticks in the left and lower axes only
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
def init():
line.set_data([], [])
return line,
def init2():
line.set_data([], [])
return line2,
def animate(i):
x2 = np.sqrt(i)*np.cos(theta)
y2 = np.sqrt(i)*np.sin(theta)
line.set_data(x2, y2)
return line,
def animate2(i):
x3 = np.linspace(0,r2**2,100)
y3 = ((2/r2)*(np.sqrt(1-(r2**2)/4)-1)*x3)+r2
line.set_data(x3, y3)
return line,
# create the figure
ax.plot(x1,y1)
ax.set_aspect(1)
plt.grid()
anim = animation.FuncAnimation(fig, animate, init_func=init,
interval=1000, blit=False,\
frames=np.hstack([range(0),
range(4)[::-1]]))
anim2 = animation.FuncAnimation(fig, animate2, init_func=init2,
interval=1000, blit=False)
plt.show()
f = r"D:/UNPAR/Semester 2/Pemrograman Komputer/Project/SK.gif"
writergif = animation.PillowWriter(fps=30)
anim.save(f, writer=writergif)
The animation functions need to be combined into one. We will combine them into an initialization function and an animation function.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots(1)
line, = ax.plot([], [], lw=2, color='green')
line2, = ax.plot([], [], lw=2, color='red')
ax.set_xlim(-5,5)
ax.set_ylim(-5,5)
# theta goes from 0 to 2pi
theta = np.linspace(0, 2*np.pi, 100)
# the radius of the circle
r = np.sqrt(1)
r2 = np.sqrt(4)
# compute x1 and x2
x1 = 1+r*np.cos(theta)
y1 = r*np.sin(theta)
x2 = r2*np.cos(theta)
y2 = r2*np.sin(theta)
# create the figure
ax.plot(x1,y1)
ax.set_aspect(1)
plt.grid()
def init():
line.set_data([], [])
line2.set_data([], [])
return line,line2
def animate(i):
x2 = np.sqrt(i)*np.cos(theta)
y2 = np.sqrt(i)*np.sin(theta)
print(max(y2))
line.set_data(x2, y2)
x3 = [0, 4-(0.1*i)]
y3 = [max(y2), 0]
line2.set_data(x3, y3)
return line,line2
anim = animation.FuncAnimation(fig, animate, init_func=init, interval=1000, blit=False,frames=np.arange(10,0,-1))
plt.show()

How to stop second plot from showing up in matplotlib?

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.

Combine a static plot and an animated plot in matplotlib python

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()

How to plot real-time graph, with both axis dependent on time?

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.

Categories