How to animate and update the horizontal axis? - python

I am trying to use matplotlib to display some "live" data. Preferably, I would like to have the horizontal axis display a running interval over the most recent 10 seconds or so.
The program below demonstrates what I'm after. However, this program doesn't do what I want in two ways.
First, I'd like the horizontal axis to display absolute time (currently, it displays time, in seconds, relative to "tNow"). The horizontal axis should ideally be continuously updated.
Second, for some reason I don't understand, the first evaluations of the signals drawn are persistent; I am only interested in the "moving" signal; the static signal is an artifact. How can I get rid of it?
I am unfortunately not terribly good with matplotlib. Therefore, any help will be greatly appreciated.
#! /usr/bin/env python
import time
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
def update_line(num, line1, line2):
tNow = time.time()
t = np.linspace(tNow - 10.0, tNow, 200)
f1 = 0.5 # Hz
f2 = 0.3 # Hz
line1.set_data(t - tNow, np.sin(2 * np.pi * f1 * t))
line2.set_data(t - tNow, np.sin(2 * np.pi * f2 * t))
return (line1, line2)
(fig, axes_list) = plt.subplots(2, 1)
axes1 = axes_list[0]
axes2 = axes_list[1]
line1, = axes1.plot([], [], 'r-')
line2, = axes2.plot([], [], 'b-')
for ax in [axes1, axes2]:
ax.set_xlim(-10, 0)
ax.set_ylim(-1, 1)
#ax.set_xlabel('x')
#ax.set_ylabel('y')
#ax.set_title('test')
animation = animation.FuncAnimation(fig, update_line, 250, fargs = (line1, line2), interval = 0, blit = True)
# Enter the event loop
fig.show()

I would approach it a little differently. However, I'm not sure if this is the best way. Comments and suggestions are welcome
import time
import datetime
import numpy as np
import matplotlib.pyplot as plt
F1 = 0.5 # Hz
F2 = 0.3 # Hz
def update_line(tstart, axes):
for ax in axes:
# Remove old lines
ax.lines.pop()
# Plot new lines
axes[0].plot(t, np.sin(2 * np.pi * F1 * np.array(t)), 'r-')
axes[1].plot(t, np.sin(2 * np.pi * F2 * np.array(t)), 'b-')
# relabel the x tick marks to be absolute time
def show_abs_time(abs_start, axes):
for ax in axes:
tick_labels = []
ticks = ax.get_xticks()
# using the relative time for each tick mark, add it to the absolute time
for rel_time in ticks:
abs_time = abs_start + datetime.timedelta(0,rel_time)
tick_labels.append(abs_time.strftime("%X"))
ax.set_xticklabels(tick_labels)
if __name__ == "__main__":
plt.ion()
plt.close("all")
(fig, axes) = plt.subplots(2, 1)
# Initial Plot configuration
for ax in axes:
ax.set_xlim(0, 10)
ax.set_ylim(-1, 1)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_title('test')
tstart = time.time()
delta_t = 0
t = []
abs_start = datetime.datetime.strptime(time.strftime("%X"), "%H:%M:%S")
axes[0].plot([], [], 'r-')
axes[1].plot([], [], 'b-')
plt.show()
while delta_t < 20 :
tNow = time.time()
delta_t = tNow - tstart
t.append(delta_t)
update_line(t, axes)
# Once your time extends past a certain point, update the x axis
if delta_t > 9:
for ax in axes:
ax.set_xlim(delta_t-9,delta_t + 1)
# redo the x ticks to be absolute time
show_abs_time(abs_start, axes)
# update the plots
plt.draw()

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:

Continuously change direction of shifting matplotlib animation?

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

Matplotlib animation in real time

In the example below I want to make an animation where a point moves around a circle in T seconds (for example T=10). However it is a lot slower and doesn't work. So, what is wrong with my code and how to fix it? As far as I understand the api (http://matplotlib.org/api/animation_api.html) setting interval=1 should update the figure every millisecond.
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
R = 3
T = 10
fig = plt.figure()
fig.set_dpi(300)
fig.set_size_inches(7, 6.5)
ax = plt.axes(xlim=(-10, 10), ylim=(-R*1.5, R*1.5))
ax.set_aspect('equal')
patch = plt.Circle((0, 0), 0.1, fc='r')
looping = plt.Circle((0,0),R,color='b',fill=False)
ax.add_artist(looping)
time_text = ax.text(-10,R*1.2,'',fontsize=15)
def init():
time_text.set_text('')
patch.center = (0, 0)
ax.add_patch(patch)
return patch,time_text,
def animate(i):
t=i/1000.0
time_text.set_text(t)
x, y = patch.center
x = R*np.sin(t/T*2*np.pi)
y = R*np.cos(t/T*2*np.pi)
patch.center = (x, y)
return patch,time_text
slow_motion_factor=1
anim = animation.FuncAnimation(fig, animate,
init_func=init,
frames=10000,
interval=1*slow_motion_factor,
blit=True)
plt.show()
I should add that the problem depends on the machine where I run the program. For example on a old Intel dualcore (P8700) (that's the box where the program should run), it is considerable slower than on a newer haswell i7 desktop cpu. But in the latter case it is also much slower as intended.
The problem is, that your computer is not fast enough, to deliver a new image every 1 ms. Which is kind of expected.
You should go for a more realistic speed. 25 frames per second should be enough
and also be possible to render in time.
I also made a few adjustment to you code, mostly style and more semantic variable names.
The biggest change was adapting this answer to your code to get rid of the first frame being still there after the init:
Matplotlib animation: first frame remains in canvas when using blit
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
R = 3
T = 10
time = 3 * T
slow_motion_factor = 1
fps = 50
interval = 1 / fps
fig = plt.figure(figsize=(7.2, 7.2))
ax = fig.add_subplot(1, 1, 1, aspect='equal')
ax.set_xlim(-1.5 * R, 1.5 * R)
ax.set_ylim(-1.5 * R, 1.5 * R)
runner = plt.Circle((0, 0), 0.1, fc='r')
circle = plt.Circle((0, 0), R, color='b', fill=False)
ax.add_artist(circle)
time_text = ax.text(1.1 * R, 1.1 * R,'', fontsize=15)
def init():
time_text.set_text('')
return time_text,
def animate(i):
if i == 0:
ax.add_patch(runner)
t = i * interval
time_text.set_text('{:1.2f}'.format(t))
x = R * np.sin(2 * np.pi * t / T)
y = R * np.cos(2 * np.pi * t / T)
runner.center = (x, y)
return runner, time_text
anim = animation.FuncAnimation(
fig,
animate,
init_func=init,
frames=time * fps,
interval=1000 * interval * slow_motion_factor,
blit=True,
)
plt.show()

How to show the changes of multiple images in one figure?

My code has been modified according to many great suggestions from people in this forum. However, I still have some questions about the code.
My code is:
from pylab import *
from numpy import *
N = 100 #lattice points per axis
dt = 1 #time step
dx = 1 #lattice spacing
t = arange(0, 1000000*dt, dt) #time
a = 1 #cofficient
epsilon = 100 #cofficient
M = 1.0 #cofficient
every = 100 #dump an image every
phi_0 = 0.5 #initial mean value of the order parameter
noise = 0.1 #initial amplitude of thermal fluctuations in the order parameter
th = phi_0*ones((N, N)) + noise*(rand(N, N) - 0.5) #initial condition
x, y = meshgrid(fftfreq(int(th.shape[0]), dx), fftfreq(int(th.shape[1]), dx))
k2 = (x*x + y*y) #k is a victor in the Fourier space, k2=x^2+y^2
g = lambda th, a: 4*a*th*(1-th)*(1-2*th) #function g
def update(th, dt, a, k2):
return ifft2((fft2(th)-dt*M*k2*fft2(g(th,a)))/(1+2*epsilon*M*dt*k2**2))
for i in range(size(t)):
print t[i]
if mod(i, every)==0:
imshow(abs(th), vmin=0.0, vmax=1.0)
colorbar()
show()
#savefig('t'+str(i/every).zfill(3)+'.png', dpi=100)
clf()
th=update(th, dt, a, k2)
When I run it, I have to close the figures one by one to see the changes. But I want to demonstrate the changes of the images in one figure. Any good ideas?
Use the "animation" feature of matplotlib, like in
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
def update_line(num, data, line):
line.set_data(data[...,:num])
return line,
fig1 = plt.figure()
data = np.random.rand(2, 25)
l, = plt.plot([], [], 'r-')
plt.xlim(0, 1)
plt.ylim(0, 1)
plt.xlabel('x')
plt.title('test')
line_ani = animation.FuncAnimation(fig1, update_line, 25, fargs=(data, l),
interval=50, blit=True)
plt.show()
Tutorial at :
http://matplotlib.org/1.3.1/examples/animation/index.html

Animation with matplotlib where points are dynamically added to a graph

I've written a simple code which generates random points (x0, y0) between certain values using a while loop. After the coordinates of each point are set, that point is drawn in an empty graph which is showed at the end of the while loop.
However, I would like to set up an animation with matplotlib which would allow me to see the initial graph and the points progressively added to it as the code is calculating them. I've looked for some examples but the ones I found are mainly concerned with waves and so on and I guess I need a slightly different approach.
This is the basic code:
from numpy import *
from pylab import *
import random
figure(figsize=(8,6), dpi=150)
x = np.linspace(-1, 4.5, 250)
h=5
a=0.5
b=4
ylim(-0.5,5.5)
xlim(-1,5.0)
i= 0
while i< 500:
R1 = random.random()
R2 = random.random()
x0 = (b - a)*R1 + a
y0 = h*R2
scatter(x0, y0, 10, color="red")
i = i + 1
show()
Thanks for your help!
EDIT: ANIMATION CODE
import numpy as np
import matplotlib.pyplot as plt
from pylab import *
import matplotlib.animation as animation
import random
fig = plt.figure(figsize=(8,6), dpi=150)
x = np.linspace(-2, 4.5, 250)
h=4
a=1
b=3
hlines(y=h, xmin=1, xmax=3, linewidth=1.5)
vlines(x=a, ymin=0, ymax=4, linewidth=1.5)
vlines(x=b, ymin=0, ymax=4, linewidth=1.5)
ylim(-2.5,10.5)
xlim(-2.5,4.5)
grid()
def data_gen():
i = 0
while i< 1:
R1 = random.random()
R2 = random.random()
x0 = (b - a)*R1 + a
y0 = h*R2
i = i + 1
yield x0, y0
line, = plot([], [], linestyle='none', marker='o', color='r')
ax = gca()
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data',0))
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0))
xdata, ydata = [], []
def run(data):
x0,y0 = data
xdata.append(x0)
ydata.append(y0)
line.set_data(xdata, ydata)
return line,
ani = animation.FuncAnimation(fig, run, data_gen, blit=True, interval=0.5,
repeat=False)
plt.show()
I do not know if this is exactly what you are looking for; in any case, you can generate random points inside the run function and there plot them. You do not need neither blit = True nor clear the axis from one frame to another.
Here is my code:
from pylab import *
from matplotlib.animation import FuncAnimation
import random
fig = plt.figure(figsize=(8,6), dpi=150)
x = np.linspace(-2, 4.5, 250)
h=4
a=1
b=3
hlines(y=h, xmin=a, xmax=b, linewidth=1.5)
vlines(x=a, ymin=0, ymax=h, linewidth=1.5)
vlines(x=b, ymin=0, ymax=h, linewidth=1.5)
ylim(-2.5,10.5)
xlim(-2.5,4.5)
grid()
ax = gca()
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data',0))
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0))
def run(i):
R1 = random.random()
R2 = random.random()
x0 = (b - a)*R1 + a
y0 = h*R2
ax.scatter(x0, y0, 10, color='red')
ani = FuncAnimation(fig = fig, func = run, frames = 500, interval = 10, repeat = False)
plt.show()
which produces this animation:
(I cut this animation to 100 points in order to get a lighter file, less than 2 MB; the code above produces an animation wiht 500 points)

Categories