I'm trying to animate a set of particles that follow trajectories in x, y and z. Each object has a specific radius which is relevant in axis units, which is why I want each object to be represented by a Circle patch, where I can specify the size of the patch (as opposed to a scatter plot, where I have to convert the size to axis units).
The other constraint is that I want the patches to be colored according to a colormap, with the color of the patch determined by the value of z.
The only way I have found of plotting a set of patches with a colormap is by putting them inside a collection. So, this successfully produces one frame
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as anm
import matplotlib.collections as clt
fig, ax = plt.subplots(1,1,figsize=(7,7))
ax.set_xlim(-1,1)
ax.set_ylim(-1,1)
n_of_particles = 3
frames = 10
radius = 0.05
x = 0.5*np.random.randn(frames,n_of_particles)
y = 0.5*np.random.randn(frames,n_of_particles)
z = 0.5*np.random.randn(frames,n_of_particles)
patches = []
for p in range(n_of_particles):
circle = plt.Circle((x[0,p], y[0,p]), radius)
patches.append(circle)
collection = clt.PatchCollection(patches, cmap=plt.cm.jet, alpha=0.4)
collection.set_array(z[0,:])
collection.set_clim([-1, 1])
plt.colorbar(collection)
ax.add_collection(collection)
But how do I animate it?
I have tried to add at the end (instead of ax.add_collection(collection))
def animate(frame):
patches = []
for p in range(n_of_particles):
circle = plt.Circle((x[frame,p], y[frame,p]), radius)
patches.append(circle)
collection = clt.PatchCollection(patches, cmap=plt.cm.jet, alpha=0.4)
collection.set_array(z[0,:])
ax.add_collection(collection)
return collection,
and then:
anim = anm.FuncAnimation(fig, animate,
frames=10, interval=100, blit=True)
HTML(anim.to_html5_video())
But it doesn't erase the previous frame.
Ideally, I would prefer to change the position of the patches, instead of redefining them. But I don't know how to modify a collection.
Any ideas?
The idea would of course be to add the collection only once to the axes. Then change the artists inside the collection via collection.set_paths.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as anm
import matplotlib.collections as clt
fig, ax = plt.subplots(1,1,figsize=(7,7))
ax.set_xlim(-1,1)
ax.set_ylim(-1,1)
n_of_particles = 3
frames = 10
radius = 0.05
x = 0.5*np.random.randn(frames,n_of_particles)
y = 0.5*np.random.randn(frames,n_of_particles)
z = 0.5*np.random.randn(frames,n_of_particles)
patches = []
for p in range(n_of_particles):
circle = plt.Circle((x[0,p], y[0,p]), radius)
patches.append(circle)
collection = clt.PatchCollection(patches, cmap=plt.cm.jet, alpha=0.4)
collection.set_array(z[0,:])
collection.set_clim([-1, 1])
fig.colorbar(collection)
ax.add_collection(collection)
def animate(frame):
patches = []
for p in range(n_of_particles):
circle = plt.Circle((x[frame,p], y[frame,p]), radius)
patches.append(circle)
collection.set_paths(patches)
collection.set_array(z[frame,:])
anim = anm.FuncAnimation(fig, animate,
frames=10, interval=1000, blit=False)
plt.show()
The other option could be to use the solution provided in this answer: matplotlib change a Patch in PatchCollection
and create an UpdatablePatchCollection. This would allow to just update the properties of the original patches inside the loop.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as anm
import matplotlib.collections as clt
fig, ax = plt.subplots(1,1,figsize=(7,7))
ax.set_xlim(-1,1)
ax.set_ylim(-1,1)
n_of_particles = 3
frames = 10
radius = 0.05
x = 0.5*np.random.randn(frames,n_of_particles)
y = 0.5*np.random.randn(frames,n_of_particles)
z = 0.5*np.random.randn(frames,n_of_particles)
patches = []
for p in range(n_of_particles):
circle = plt.Circle((x[0,p], y[0,p]), radius)
patches.append(circle)
class UpdatablePatchCollection(clt.PatchCollection):
def __init__(self, patches, *args, **kwargs):
self.patches = patches
clt.PatchCollection.__init__(self, patches, *args, **kwargs)
def get_paths(self):
self.set_paths(self.patches)
return self._paths
collection = UpdatablePatchCollection(patches, cmap=plt.cm.jet, alpha=0.4)
collection.set_array(z[0,:])
collection.set_clim([-1, 1])
fig.colorbar(collection)
ax.add_collection(collection)
def animate(frame):
for p in range(n_of_particles):
patches[p].center = x[frame,p], y[frame,p]
collection.set_array(z[frame,:])
anim = anm.FuncAnimation(fig, animate,
frames=10, interval=1000, blit=False)
plt.show()
Related
I tried to write a simple script which updates a scatter plot for every timestep t. I wanted to do it as simple as possible. But all it does is to open a window where I can see nothing. The window just freezes. It is maybe just an small error, but I can not find it.
The the data.dat has the format
x y
Timestep 1 1 2
3 1
Timestep 2 6 3
2 1
(the file contains just the numbers)
import numpy as np
import matplotlib.pyplot as plt
import time
# Load particle positioins
with open('//home//user//data.dat', 'r') as fp:
particles = []
for line in fp:
line = line.split()
if line:
line = [float(i) for i in line]
particles.append(line)
T = 100
numbParticles = 2
x, y = np.array([]), np.array([])
plt.ion()
plt.figure()
plt.scatter(x,y)
for t in range(T):
plt.clf()
for k in range(numbP):
x = np.append(x, particles[numbParticles*t+k][0])
y = np.append(y, particles[numbParticles*t+k][1])
plt.scatter(x,y)
plt.draw()
time.sleep(1)
x, y = np.array([]), np.array([])
The simplest, cleanest way to make an animation is to use the matplotlib.animation module.
Since a scatter plot returns a matplotlib.collections.PathCollection, the way to update it is to call its set_offsets method. You can pass it an array of shape (N, 2) or a list of N 2-tuples -- each 2-tuple being an (x,y) coordinate.
For example,
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
T = 100
numbParticles = 2
particles = np.random.random((T,numbParticles)).tolist()
x, y = np.array([]), np.array([])
def init():
pathcol.set_offsets([[], []])
return [pathcol]
def update(i, pathcol, particles):
pathcol.set_offsets(particles[i])
return [pathcol]
fig = plt.figure()
xs, ys = zip(*particles)
xmin, xmax = min(xs), max(xs)
ymin, ymax = min(ys), max(ys)
ax = plt.axes(xlim=(xmin, xmax), ylim=(ymin, ymax))
pathcol = plt.scatter([], [], s=100)
anim = animation.FuncAnimation(
fig, update, init_func=init, fargs=(pathcol, particles), interval=1000, frames=T,
blit=True, repeat=True)
plt.show()
I finally found a solution. You can do it simply by using this script. I tried to keep it simple:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
# Helps me to get the data from the file I want to plot
N = 0
# Load particle positioins
with open('//home//user//data.dat', 'r') as fp:
particles = []
for line in fp:
line = line.split()
particles.append(line)
# Create new Figure and an Axes which fills it.
fig = plt.figure(figsize=(7, 7))
ax = fig.add_axes([0, 0, 1, 1], frameon=True)
border = 100
ax.set_xlim(-border, border), ax.set_xticks([])
ax.set_ylim(-border, border), ax.set_yticks([])
# particle data
p = 18 # number of particles
myPa = np.zeros(p, dtype=[('position', float, 2)])
# Construct the scatter which we will update during animation
scat = ax.scatter(myPa['position'][:, 0], myPa['position'][:, 1])
def update(frame_number):
# New positions
myPa['position'][:] = particles[N*p:N*p+p]
# Update the scatter collection, with the new colors, sizes and positions.
scat.set_offsets(myPa['position'])
increment()
def increment():
global N
N = N+1
# Construct the animation, using the update function as the animation director.
animation = FuncAnimation(fig, update, interval=20)
plt.show()
Consider the following code which implements ArtistAnimation to animate two different subplots within the same figure object.
import numpy as np
import itertools
import matplotlib.pyplot as plt
import matplotlib.mlab as ml
import matplotlib.animation as animation
def f(x,y,a):
return ((x/a)**2+y**2)
avals = np.linspace(0.1,1,10)
xaxis = np.linspace(-2,2,9)
yaxis = np.linspace(-2,2,9)
xy = itertools.product(xaxis,yaxis)
xy = list(map(list,xy))
xy = np.array(xy)
x = xy[:,0]
y = xy[:,1]
fig, [ax1,ax2] = plt.subplots(2)
ims = []
for a in avals:
xi = np.linspace(min(x), max(x), len(x))
yi = np.linspace(min(y), max(y), len(y))
zi = ml.griddata(x, y, f(x, y, a), xi, yi, interp='linear') # turn it into grid data, this is what imshow takes
title = plt.text(35,-4,str(a), horizontalalignment = 'center')
im1 = ax1.imshow(zi, animated = True, vmin = 0, vmax = 400)
im2 = ax2.imshow(zi, animated=True, vmin=0, vmax=400)
ims.append([im1,im2, title])
ani = animation.ArtistAnimation(fig, ims, interval = 1000, blit = False)
plt.show()
In this case the number of items in im1 and im2 are the same, and the frame rate for each subplot is identical.
Now, imagine I have 2 lists with different numbers of items, and that I wish ArtistAnimate to go through the frames in the same total time. Initially I thought of manipulating the interval keyword in the ArtistAnimation call but this implies that you can set different intervals for different artists, which I don't think is possible.
Anyway, I think the basic idea is pretty clear len(im1) is not equal to len(im2), but the animation needs to go through them all in the same amount of time.
Is there any way to do this please? Thanks
EDIT
While I try out the answer provided below, I should add that I would rather use ArtistAnimation due to the structure of my data. If there are no alternatives I will revert to the solution below.
Yes that is possible, kinda, using Funcanimation and encapsulating your data inside func.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
arr1 = np.random.rand(300,3,4)
arr2 = np.random.rand(200,5,6)
fig, (ax1, ax2) = plt.subplots(1,2)
img1 = ax1.imshow(arr1[0])
img2 = ax2.imshow(arr2[0])
# set relative display rates
r1 = 2
r2 = 3
def animate(ii):
if ii % r1:
img1.set_data(arr1[ii/r1])
if ii % r2:
img2.set_data(arr2[ii/r2])
return img1, img2
ani = animation.FuncAnimation(fig, func=animate, frames=np.arange(0, 600))
plt.show()
I tried to write a simple script which updates a scatter plot for every timestep t. I wanted to do it as simple as possible. But all it does is to open a window where I can see nothing. The window just freezes. It is maybe just an small error, but I can not find it.
The the data.dat has the format
x y
Timestep 1 1 2
3 1
Timestep 2 6 3
2 1
(the file contains just the numbers)
import numpy as np
import matplotlib.pyplot as plt
import time
# Load particle positioins
with open('//home//user//data.dat', 'r') as fp:
particles = []
for line in fp:
line = line.split()
if line:
line = [float(i) for i in line]
particles.append(line)
T = 100
numbParticles = 2
x, y = np.array([]), np.array([])
plt.ion()
plt.figure()
plt.scatter(x,y)
for t in range(T):
plt.clf()
for k in range(numbP):
x = np.append(x, particles[numbParticles*t+k][0])
y = np.append(y, particles[numbParticles*t+k][1])
plt.scatter(x,y)
plt.draw()
time.sleep(1)
x, y = np.array([]), np.array([])
The simplest, cleanest way to make an animation is to use the matplotlib.animation module.
Since a scatter plot returns a matplotlib.collections.PathCollection, the way to update it is to call its set_offsets method. You can pass it an array of shape (N, 2) or a list of N 2-tuples -- each 2-tuple being an (x,y) coordinate.
For example,
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
T = 100
numbParticles = 2
particles = np.random.random((T,numbParticles)).tolist()
x, y = np.array([]), np.array([])
def init():
pathcol.set_offsets([[], []])
return [pathcol]
def update(i, pathcol, particles):
pathcol.set_offsets(particles[i])
return [pathcol]
fig = plt.figure()
xs, ys = zip(*particles)
xmin, xmax = min(xs), max(xs)
ymin, ymax = min(ys), max(ys)
ax = plt.axes(xlim=(xmin, xmax), ylim=(ymin, ymax))
pathcol = plt.scatter([], [], s=100)
anim = animation.FuncAnimation(
fig, update, init_func=init, fargs=(pathcol, particles), interval=1000, frames=T,
blit=True, repeat=True)
plt.show()
I finally found a solution. You can do it simply by using this script. I tried to keep it simple:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
# Helps me to get the data from the file I want to plot
N = 0
# Load particle positioins
with open('//home//user//data.dat', 'r') as fp:
particles = []
for line in fp:
line = line.split()
particles.append(line)
# Create new Figure and an Axes which fills it.
fig = plt.figure(figsize=(7, 7))
ax = fig.add_axes([0, 0, 1, 1], frameon=True)
border = 100
ax.set_xlim(-border, border), ax.set_xticks([])
ax.set_ylim(-border, border), ax.set_yticks([])
# particle data
p = 18 # number of particles
myPa = np.zeros(p, dtype=[('position', float, 2)])
# Construct the scatter which we will update during animation
scat = ax.scatter(myPa['position'][:, 0], myPa['position'][:, 1])
def update(frame_number):
# New positions
myPa['position'][:] = particles[N*p:N*p+p]
# Update the scatter collection, with the new colors, sizes and positions.
scat.set_offsets(myPa['position'])
increment()
def increment():
global N
N = N+1
# Construct the animation, using the update function as the animation director.
animation = FuncAnimation(fig, update, interval=20)
plt.show()
I have the following code :
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.axes3d as p3
from matplotlib import animation
fig = plt.figure()
p3.autoscale = True
ax = p3.Axes3D(fig)
ax.grid(True)
ax.set_xlim(-100, 100)
ax.set_ylim(-100, 100)
ax.set_zlim(-100, 100)
u = np.r_[0:2*np.pi:100j]
v = np.r_[0:np.pi:100j]
scale = 15
x = scale * np.outer(np.cos(u),np.sin(v))
y = scale * np.outer(np.sin(u),np.sin(v))
z = scale * np.outer(np.ones(np.size(u)),np.cos(v))
Line3DCollection_1 = ax.plot_wireframe(x,y,z, rstride=50, cstride=50)
Line3DCollection_2 = ax.plot_wireframe(x + 50,y,z, rstride=50, cstride=50)
# initialization function: plot the background of each frame
def init():
return Line3DCollection_1,
def animate(i):
print("frame :" + str(i))
x = 50 * np.sin(np.radians(i))
y = 50 * np.cos(np.radians(i))
path = plt.plot([x],[y],[0], 'bo')
return path
# call the animator. blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate, init_func=init, frames=360, interval=0.1, blit=True)
plt.show()
This will produce 2 spheres and a path that I want one of the spheres to take, but I'm not sure how to include this in the animation, I can animate the path, but not the 'Line3DCollection_2' sphere.
Does anyone have any ideas?
Thanks.
I have a bar graph which retrieves its y values from a dict. Instead of showing several graphs with all the different values and me having to close every single one, I need it to update values on the same graph. Is there a solution for this?
Here is an example of how you can animate a bar plot.
You call plt.bar only once, save the return value rects, and then call rect.set_height to modify the bar plot.
Calling fig.canvas.draw() updates the figure.
import matplotlib
matplotlib.use('TKAgg')
import matplotlib.pyplot as plt
import numpy as np
def animated_barplot():
# http://www.scipy.org/Cookbook/Matplotlib/Animations
mu, sigma = 100, 15
N = 4
x = mu + sigma*np.random.randn(N)
rects = plt.bar(range(N), x, align = 'center')
for i in range(50):
x = mu + sigma*np.random.randn(N)
for rect, h in zip(rects, x):
rect.set_height(h)
fig.canvas.draw()
fig = plt.figure()
win = fig.canvas.manager.window
win.after(100, animated_barplot)
plt.show()
I've simplified the above excellent solution to its essentials, with more details at my blogpost:
import numpy as np
import matplotlib.pyplot as plt
numBins = 100
numEvents = 100000
file = 'datafile_100bins_100000events.histogram'
histogramSeries = np.loadtext(file)
fig, ax = plt.subplots()
rects = ax.bar(range(numBins), np.ones(numBins)*40) # 40 is upper bound of y-axis
for i in range(numEvents):
for rect,h in zip(rects,histogramSeries[i,:]):
rect.set_height(h)
fig.canvas.draw()
plt.pause(0.001)