I am working on some animated scatter plots in python with matplotlib. I currently have this code:
def calulateStep():
# Math stuff ....
# Changes values in in 'circpos' Nx2 array
fig, ax = plt.subplots(figsize=(5, 5))
ax.set(xlim=(-WELLRADIUS,WELLRADIUS), ylim=(-WELLRADIUS,WELLRADIUS))
[x,y] = np.hsplit(circpos,2)
scat = ax.scatter(x.flatten(),y.flatten())
def animate(i):
calculateStep()
scat.set_offsets(circpos)
return scat,
anim = FuncAnimation(fig, animate, frames=60)
anim.save('test2.gif',writer='imagemagick')
plt.draw()
plt.show()
The function calculateStep calculates new x,y values for the scatter. circpos contains the data array at each step. this works well and produces an animated gif ofthe scatter plot as expected. However the function is a rather slow numerical calculation and many many steps are required to produce stable output, so I would rather calculate all before and then animate only select frames. So I tried this.
results = [circpos]
for h in range(61):
calculateStep()
results.append(circpos)
fig, ax = plt.subplots(figsize=(5, 5))
ax.set(xlim=(-WELLRADIUS,WELLRADIUS), ylim=(-WELLRADIUS,WELLRADIUS))
[x,y] = np.hsplit(results[0],2)
scat = ax.scatter(x.flatten(),y.flatten())
def animate(i):
scat.set_offsets(results.pop(0))
return scat,
anim = FuncAnimation(fig, animate, frames=60)
anim.save('test2.gif',writer='imagemagick')
plt.draw()
plt.show()
However with this method the generated gif contains only the final frame of the animation. If I print the data from within the animate function I find that the correct numerical values are being popped from the results list but for some reason only the final value is there in the gif. I have also tried using results[i] rather than results.pop(0) I am at a loss to understand this behavior.
Well it seems I solved my own problem. When I add each iteration of the global array circpos to the results list, it is of course a shallow copy. Meaning it's just a reference to the original circpos array. So I end up with a list full of references to the same object. The print out was just me misinterpreting what I was looking at.
Instead I now add circpos.copy() to my list to get new copies of the array at each step.
This has tripped me up in Python before I realize. Still learning!
Related
I currently have a simulation process that outputs a data point on each iteration. I would like to animate this with matplotlib, but am unsure if possible with matplotlib.animation.
Many online tutorials/examples I have come across always start with a list of predefined points, e.g. x = [1,2,3,4,5], y=[5.5,3.6,7.1,2.2,3.3], and essentially animate this list. Technically this also works for me, but I will have to first run the simulation and append the results into lists x and y, and then run the animation process on these lists (which would require iterating through the lists again, which is pointless as ideally it should be animating alongside the simulation phase.) This will be cumbersome if I run the simulation with millions of iterations.
I was wondering if mpl.animation can animate data as it comes, e.g. start with x=[], y=[], then on first iteration we get x=[0.1], y=[3.3] and we animate this, and then on second iteration we get x=[0.1,0.52], y=[3.3,4.4] and we animate again, and so on, rather than requiring the entire list to be populated first before animating.
Why not just try it?
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig = plt.figure()
pltdata, = plt.plot([], [])
plt.ylim(-1.1,1.1)
plt.xlim(0,200)
def animate(i):
x=np.arange(i, i*2+2)
y=np.sin(x*0.1)
pltdata.set_data(x, y)
return [pltdata]
theAnim = animation.FuncAnimation(fig, animate, frames=100, interval=100, blit=True, repeat=False)
plt.show()
As you can see, it is not a predefined list (it could have been for this example, but it is not. First plot is with constant [] list. And then x and y are recomputed from scratch at each animate call).
And works as intended.
(As always with animation, one must take care of xlim and ylim, because if they are chosen automatically, since there is no data at the beginning, they won't fit the future, yet unknown, data).
I'm trying to create histogram animation that stacks up samples one by one.
I thought the following code would work, but it doesn't.
import matplotlib.pyplot as plt
from matplotlib.animation import ArtistAnimation
ims = []
fig = plt.figure()
x =[1,2,3,3,4,5,5,5,5,6,7,8,9,9,9,9,9,10]
for i in range(len(x)):
img = plt.hist(x[:i])
ims.append(img)
ani = ArtistAnimation(fig, ims, interval=100)
plt.show()
Changing plt.hist to plt.plot shows animation. Don't know what the difference is.
Thanks for reading.
plt.hist does not return an artist but a tuple with binning results and the artist (see here).
This is also discussed in this thread in the Matplotlib mailing list:
ArtistAnimation expects to be given a list of lists (or tuples), where
the inner collection contains all of the artists that should be
rendered for a given frame. In the case of bar, it returns a
BarCollection object (which I just learned), is a subclass of tuple.
This explains why it works (by itself), when directly appended to the
list given to ArtistAnimation; the BarCollection acts as the
collection of artists that ArtistAnimation is expecting. In the case
of the second example, ArtistAnimation is being given a
list([BarCollection, Image]); because BarCollection isn't actually an
Artist, it causes the problem.
The problem mentioned there uses different plot types:
im1 = ax1.imshow(f(xm, ym), aspect='equal', animated=True)
im2 = ax2.bar(xx, a, width=0.9*(b[1]-b[0]), color='C1')
and the solution in that case is
ims.append([im1] + list(im2))
The way to make this work in your case is to look at the return values of plt.hist and find out where the artists are being returned and put them in the correct list of lists format.
This works:
import matplotlib.animation as animation
fig = plt.figure()
# ims is a list of lists, each row is a list of artists to draw in the
# current frame; here we are just animating one artist, the image, in
# each frame
ims = []
for i in range(60):
# hist returns a tuple with two binning results and then the artist (patches)
n, bins, patches = plt.hist((np.random.rand(10),))
# since patches is already a list we can just append it
ims.append(patches)
ani = animation.ArtistAnimation(fig, ims, interval=50, blit=True,
repeat_delay=1000,)
plt.show()
For a personal project, I'm trying to animate a fairly large data set (1000 rows) to show multiple bird dives in Jupyter notebook. Eventually I'd also like to add subplots of acceleration data along with it.
I used simple examples as a rough template, such as the growing coil example in: https://towardsdatascience.com/animations-with-matplotlib-d96375c5442c
The code itself seems to run slow but fine, however it doesn't output an animation, just a static graph:
Here's my current code:
x = np.array(dives.index)
y = np.array(dives['depth'])
x_data, y_data = [], []
fig = plt.figure()
ax = plt.axes(xlim=(0, 1000), ylim=(min(y),max(y)))
line, = ax.plot([], [])
def init():
line.set_data([], [])
return line,
def animate(i):
x_data.append(x[i])
y_data.append(y[i])
line.set_data(x, y)
return line,
plt.title('Bird Dives')
ani = animation.FuncAnimation(
fig, animate, init_func=init, frames= 1000, interval=50, blit=True)
ani.save('./plot-test.gif')
plt.show()
Is there a reason why it's just plotting a graph rather than an animated one?
Yes, your error is in your animate function. You have line.set_data(x, y), which is plotting the entire contents of x and y at every frame (and hence produces an animated graph that doesn't change).
What you intended to have in your animate function was line.set_data(x_data, y_data).
As for performance: you can improve this by not creating an empty list and appending to it at every iteration. Instead its simpler to slice your original arrays x and y. Consider the following animate function instead:
def animate(i):
line.set_data(x[:i], y[:i])
return line,
Having said this, given that you've got a thousand frames it's still going to take a little while to run.
I have two arrays x and y, each one has more than 365000 elements. I would like to draw an animated line using these array elements. I'm using matplotlib.animation for it. Problem is when I execute the code below I can't see the graph smoothly(animated) drawed. Contrary I see it's final drawed version.
Here is my code:
#libs
# Movement instance creation-----------------------------
movement1=Movement(train1, track1)
# # Move the train on the track
movement1.move()
y = movement1.speed
x = movement1.pos
Writer = animation.writers['ffmpeg']
writer = Writer(fps=20, metadata=dict(artist='Me'), bitrate=1800)
fig = plt.figure()
ax = plt.axes(xlim=(0, 25), ylim=(0, 300))
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):
line.set_data(x, y)
return line,
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=200, interval=200, blit=True)
anim.save('basic_animation.mp4', writer=writer)
Here is the similar result that I expect:
Of course my graph would be another curve.
Your code is mostly fine; you just need to do three things.
Set the xdata and ydata of the line to different values every iteration of anim_func (otherwise, there would be no animation, would there?)
Set constant axis limits so your plot doesn't change shape
Remove the save call for display purposes (for me personally, I find it affects the animation)
So:
ax.axis((x.min(), x.max(), y.min(), y.max())
def animate(i):
line.set_data(x[:i], y[:i])
return line,
You need to define a set of data that is changing for animation to occur. In the example site you gave, the author does it by slicing the data using overdose.iloc[:int(i+1] (see below for the actual code used). This is the part that creates the animation as matplotlib plots whatever data is in the animate function. In your code you have input line.set_data(x, y) which I suppose is your entire dataset. That's why it isn't moving.
def animate(i):
data = overdose.iloc[:int(i+1)] #select data range
p = sns.lineplot(x=data.index, y=data[title], data=data, color="r")
p.tick_params(labelsize=17)
plt.setp(p.lines,linewidth=7)
Second thing to note is that your plot is getting chopped off at the top. That is likely because your initialisation is already setting the axis wrongly. What I would do is to add in a plt.axis([0, 25, 0, 'upper limit']) to help set the axis correctly.
Im trying to save a GIF with the evolucion of some waves in 2d using pcolormesh (using surface or wireframe would also be ok).
This has been my aproach so far:
set the quadmesh to plot in polar coordinates:
from matplotlib import pyplot
from matplotlib.animation import FuncAnimation as FuncAnimation
pi=np.pi
rmax=6.
r=2*np.linspace(0,np.sqrt(rmax*.5),100)**2
phi=np.linspace(0,2*pi,80)
R, P = np.meshgrid(r, phi)
X, Y = R*np.cos(P), R*np.sin(P)
set the figure and functions for the animation:
count is the amount of frames i have.
Z is a count*2D-array with the values i want to plot.
(it has the sum of some fourier like series)
fig, ax = pyplot.subplots()
def anim_I(count,r,phi):
anim=np.zeros((count,len(phi), len(r)))
for i in range(count):
anim[i,:,:]=coef_transf(final_coefs[i,:,:,:,0],r,phi)**2
return anim
Z=anim_I(count,r,phi)
def animate(i):
pyplot.title('Time: %s'%time[i])
#This is where new data is inserted into the plot.
plot=ax.pcolormesh(X, Y,Z[i,:,:],cmap=pyplot.get_cmap('viridis'),vmin=0., vmax=15.)
return plot,
ax.pcolormesh(X, Y,Z[0,:,:],cmap=pyplot.get_cmap('viridis'),vmin=0., vmax=15.)
pyplot.colorbar()
anim = FuncAnimation(fig, animate, frames = range(0,count,7), blit = False)
i don't really need to see it live, so i just save a gif.
anim.save('%d_%d_%d-%d.%d.%d-2dgif.gif' %(localtime()[0:6]), writer='imagemagick')
pyplot.close()
While this works, it can take to an hour to make the gif of a even a hundred frames.
I wan't to know what would be the correct way to do this so it could be usable.
I have seen the other post in this regard, but i couldn't get the code working, or it would be just as inneficient.
You could try to write
def animate(i):
pyplot.title('Time: %s'%time[i])
#This is where new data is inserted into the plot.
plot=plot.set_array(Z[i,:,:].ravel())
return plot,
instead of
def animate(i):
pyplot.title('Time: %s'%time[i])
#This is where new data is inserted into the plot.
plot=ax.pcolormesh(X, Y,Z[i,:,:],cmap=pyplot.get_cmap('viridis'),vmin=0., vmax=15.)
return plot,
This does not create a new object every time you call the animate funtion. Instead it changes the image of object that was already created.
However, the set_array method seems to need a flattened array, hence the .ravel().
This only produces the right image if you set the shading option of the pcolormap function to shading='gouraud'.
I don't know why, unfortunatelly, it seems to have to do with the sorting of the array.
I hoped, that helped a little.
I suggest inserting a
pyplot.clf()
at the beginning of your animate(i) function. This will start each frame with a blank figure. Otherwise, I suspect the plot will not be cleared, and the long time is due to actually plotting all previous frame below the new one.