I am trying to animate a simple demonstration of Benfold's Law. I am expecting an animated bar graph from this code:
import matplotlib.animation as animation
fig = plt.figure()
plt.xticks(np.arange(1,10))
def animate(i):
plt.title("Iteration: " + str(i))
plt.plot(np.arange(1,10,1),1000*benford[1:], linestyle="", marker="d",color='r')
plt.bar(all_leads[i].keys(), all_leads[i].values())
ani = animation.FuncAnimation(fig, animate, interval=100)
plt.show()
I get an empty plot.
Empty Plot
The animate(i) function works to give a correct individual plot
animate(10)
Image of correctly produced plot
Any ideas what I am doing wrong.
Because I don't have your data, I made a mock animation as best I could from what was provided. First, you have to plot your graph outside of the animate function, then you have to update the x and y data within the animate function, lastly - to loop your animation, you have to set frames to some value (we will go with 10 for your case). While this all isn't perfectly in line with your graphs (again, I don't have your data), this should get you started. You can also take a look at my other answer for an additional example.
%matplotlib notebook # If you are working in jupyter notebook
import matplotlib.animation as animation
fig,ax = plt.subplots()
plt.xticks(np.arange(1,10))
plot, = ax.plot(np.arange(1,10,1),np.arange(1,10,1), linestyle="", marker="d",color='r')
def animate(i):
plot.set_ydata(np.arange(1,10,1)[i:i+3])
plot.set_xdata(np.arange(1,10,1)[i:i+3])
plt.title("Iteration: " + str(i))
ani = animation.FuncAnimation(fig, animate, interval=100, frames=10)
plt.show()
You can also add an xlim argument in the animate function to follow your animation across the x-axis:
def animate(i):
plot.set_ydata(np.arange(1,10,1)[i:i+3])
plot.set_xdata(np.arange(1,10,1)[i:i+3])
plt.title("Iteration: " + str(i))
plt.xlim(i, i+4)
Gives:
Related
I currently have an array G = [x,y,t] where each spatial point (G[0][i], G[1][i]) has a time component t = G[2][i]. The array is sorted by time. I am trying to animate the scatter plot so points show up in chronological order and do not disappear. Here is my current code:
from matplotlib.animation import FuncAnimation
import matplotlib.animation as animation
fig = plt.figure(figsize=(10,7))
ax = plt.subplot(111, xlim=(0,1), ylim=(0,1))
def animationUpdate(k):
x = G[0][:k]
y = G[1][:k]
scat.set_offsets(np.c_[x,y])
return scat
anim = FuncAnimation(fig, animationUpdate, frames=10, interval=100, blit=True)
I get the error "'PathCollection' object is not iterable" which I am not sure how to fix. I am also unsure how to arrange it so the points show up with respect to their time component. Do I modify the frames or interval section of FuncAnimation? Thanks!
I want to add a legend in a python animation, like the line.set_label() below. It is similar to plt.plot(x,y,label='%d' %*variable*).
However, I find that codes do not work here. The animation only shows lines changing but no label or legend available. How can I fix this problem?
from matplotlib import pyplot as plt
from matplotlib import animation
fig = plt.figure()
ax = plt.axes(xlim=(0, 2), ylim=(0, 100))
N = 3
lines = [plt.plot([], [])[0] for _ in range(N)]
def init():
for line in lines:
line.set_data([], [])
return lines
def animate(i):
for j,line in enumerate(lines):
line.set_data([0, 2], [10*j,i])
line.set_label('line %d, stage %d'%(j,i))
return lines
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=100, interval=20, blit=True)
plt.show()
you must return the legend in your animation function for it to be rendered.
Try this, instead :
legend = plt.legend()
def animate(i):
for j,line in enumerate(lines):
line.set_data([0, 2], [10*j,i])
line.set_label('line %d, stage %d'%(j,i))
legend.remove()
legend = plt.legend()
return lines + [legend]
You should also include the same code in your init function, init is used when resizing the window, otherwise the legend will disappear when resizing
I'm no expert on matplotlib at all, but in the Double Pendulum animation they display texts which changes, and this leads to some variations which can help you.
To get legends with the actual color of the lines, you can either change the initial setting lines to:
lines = [plt.plot([], [], label = 'line {}'.format(i))[0] for i in range(N)]
or add a line.set_label() to the for loop in the init() function. Both these seem to work as expected. At least if you add plt.legend(loc="upper left") right before plt.show().
However the set_label doesn't work within the animate() function, but according to the linked animation you can use specific text fields added to the animation, and that seems to work nicely. Add the following code after initialisation of lines:
texts = [ax.text(0.80, 0.95-i*0.05, '', transform=ax.transAxes) for i in range(N)]
And change animate() to be:
def animate(i):
for j in range(N):
lines[j].set_data([0, 2], [10*j,i]) #, label="hei {}".format(i))
texts[j].set_text('line %d, stage %d'%(j,i))
return lines
This places the text close to the upper right corner, and is updated for each animation step. Since the lines still have their legend displayed, you possibly simplify into one text only displaying the stage. But I leave the fine tuning of messages to your discretion.
Addendum: Extend Line2D
Another alternative could possibly be to extend lines.Line2D and use these lines in your animation, something similar to this article. Not sure if this would work with animation, but if you can't get the above to work, this might be worth a try.
You can try this minimum working example below. The handle of legend hlegend consist of handles of text htext, so we can update htext content in the hf_frame.
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
N0 = 20
xdata = np.linspace(0, 2*np.pi, 100)
omega = np.linspace(1, 4, N0)
ydata = np.sin(omega[:,np.newaxis]*xdata)
hline0, = ax.plot(xdata, ydata[0], label=f'omega={omega[0]:.3f}')
hlegend = ax.legend(loc='upper right')
def hf_frame(ind0):
hline0.set_data(xdata, ydata[ind0])
label_i = f'{omega[ind0]:.3f}'
# hline0.set_label(label_i) #doesn't help
htext = hlegend.get_texts()[0]
htext.set_text(label_i)
return hline0,htext
ani = matplotlib.animation.FuncAnimation(fig, hf_frame, frames=N0, interval=200)
plt.show()
I am trying to do the following: Plot points and store a reference in a dictionary. While animating remove points. A minimal example looks as follows:
%matplotlib qt
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
import numpy as np
import matplotlib.animation as animation
fig = plt.figure()
m = Basemap(projection='aeqd',lat_0=72,lon_0=29, resolution='l',
llcrnrlon=15, llcrnrlat=69,
urcrnrlon=41, urcrnrlat=75.6,area_thresh = 100)
pointDict=dict()
pointDict[1]=m.plot (0, 0,marker='.',label='first')[0]
pointDict[2]=m.plot (0, 0,marker='.',label='second')[0]
def init():
print ("Init")
x,y = m(30, 73)
pointDict[1].set_data(x,y)
x,y = m(31, 73)
pointDict[2].set_data(x,y)
return pointDict.values()
def animate(i):
print ("Frame {0}".format(i))
if i==2:
l=pointDict.pop(1)
print ("Removing {0}".format(l.get_label()))
l.remove()
del l
return pointDict.values()
anim = animation.FuncAnimation(plt.gcf(), animate, init_func=init,
frames=10, interval=1000, blit=True)
plt.show()
Output:
Init
Init
Frame 0
Frame 1
Frame 2
Removing first
Frame 3
Interestingly, if I am plotting just the first point (that is, remove pointDict[2]=m.plot and pointDict[2].set_data in the init function), this works. But if both are plotted, neither removing the first, nor the second point works.
Related questions brought me just as far as I am now:
Matplotlib Basemap animation
How to remove lines in a Matplotlib plot
Matplotlib animating multiple lines and text
Python, Matplotlib, plot multi-lines (array) and animation
I am using Anaconda with Python-2.7 kernel.
I found out what the problem is and want therefore to answer my question by myself:
The problem with this is somewhat unexpected the blit=True.
Obviously, blitting can be only used if the point is set within the animate function. Thus, setting the data in the init routine causes problems.
So there are two options: set blit to False, but this is not very elegant. The other option is to set the points in the first frame.
Then the init and animate functions that work are as follows:
def init():
print ("Init")
pointDict[1].set_data([],[])
pointDict[2].set_data([],[])
return pointDict.values()
def animate(i):
print ("Frame {0}".format(i))
if i==0:
print ("Init")
x,y = m(30, 73)
pointDict[1].set_data(x,y)
x,y = m(31, 73)
pointDict[2].set_data(x,y)
if i==2:
l=pointDict.pop(1)
print ("Removing {0}".format(l.get_label()))
l.remove()
del l
return pointDict.values()
anim = animation.FuncAnimation(plt.gcf(), animate, init_func=init,
frames=10, interval=1000, blit=True)
plt.show()
Goal
Hi,
I am trying to animate a complex figure with several subplots and have started testing with the artist animation and the function animation methods.
For now, my goal is to have the subplot on the left show a moving colored line (not the problem) and the subplot on the right show an updated representation of a brain scan (the problem). Static, this looks something like this.
# Imports
import nilearn as nil
from nilearn import plotting as nlp
from matplotlib import pyplot as plt
window = np.arange(0,200-50)
fig = plt.figure(figsize=(7,4))
ax = fig.add_subplot(121)
ax.set_xlim([0, 200])
a = ax.axvspan(window[0], window[0]+50, color='blue', alpha=0.5)
ay = fig.add_subplot(122)
b = nlp.plot_stat_map(nil.image.index_img(s_img, 0), axes=ay, colorbar=False, display_mode='x', cut_coords=(0,))
Problem
As you can see, I am using nilearn for plotting the brain image. For some reason, the nilearn object from plot_stat_map does not have an attribute set_visible unlike the matplotlib object from axvspan.
So when I attempt a simple animation like so:
fig = plt.figure(figsize=(7,4))
ax = fig.add_subplot(121)
ax.set_xlim([0, 200])
ay = fig.add_subplot(122)
iml = list()
for i in np.arange(50):
a = ax.axvspan(window[i], window[i]+50, color='blue', alpha=0.5)
b = nlp.plot_stat_map(nil.image.index_img(s_img, i), axes=ay)
iml.append((a,b))
ani = animation.ArtistAniTruemation(fig, iml, interval=50, blit=False,
repeat_delay=1000)
it crashes with the following error:
/home/surchs/Enthought/Canopy_64bit/User/lib/python2.7/site-packages/matplotlib/animation.pyc in _init_draw(self)
974 for f in self.new_frame_seq():
975 for artist in f:
--> 976 artist.set_visible(False)
977 # Assemble a list of unique axes that need flushing
978 if artist.axes not in axes:
AttributeError: 'OrthoSlicer' object has no attribute 'set_visible'
Makes sense, nilearn does maybe not conform to matplotlibs expectations. So I try the function animation method like so:
def show_things(i, window, ax, ay):
ax.axvspan(window[i], window[i]+50, color='blue', alpha=0.5)
nlp.plot_stat_map(nil.image.index_img(s_img, i), axes=ay, colorbar=False, display_mode='x', cut_coords=(0,))
fig = plt.figure(figsize=(7,4))
ax = fig.add_subplot(121)
ax.set_xlim([0, 200])
ay = fig.add_subplot(122)
ani = animation.FuncAnimation(fig, show_things, interval=10, blit=False, fargs=(window, ax, ay))
Although I am not sure if I am using things correctly, this gives me an animated brain plot on the right. However, the plot on the left is now not updated but just drawn over. So instead of a sliding bar, I get an expanding color surface. Something like this:
Question
How do I
get the plot on the left to update (as opposed to overwrite) on each iteration when using the function animation method? I already tried the ax.cla() function in matplotlib but since this also clears all axis attributes (like xlim) this is not a solution for me. Are there altneratives?
get the plot on the right to work with the artist animation method even though the custom plotting class is obviously missing a crucial attribute.
Also, I am not sure if I am doing the whole implementation part right, so any advice on that front is also very appreciated.
I suspect you may need to clear the axvspan axis between plots with ax.cla() to get the correct left plot (N.B. probably should clear the right plot too). To get round the problem of missing attributes, I'd suggest extracting the data from the returned handle from nlp.plot_stat_map and plotting with matplotlib pcolormesh (or imshow). Another possibility is creating a child class and adding this method yourself. It may also be worth submitting a bug/feature request to nilearn if this should be present.
By the way, if you're only after a quick and easy plot, you can do a poor man's version of animation using interactive plots, as a minimal example,
import matplotlib.pyplot as plt
import numpy as np
import time
#Interactive plot
plt.ion()
#Setup figures
fig = plt.figure(figsize=(7,4))
ax = fig.add_subplot(121)
ay = fig.add_subplot(122)
plt.show()
x = np.linspace(0,2*np.pi)
for i in range(10000):
print(i)
#Clear axes
ax.cla(); ay.cla()
#Update data
yx = np.sin(x+i*0.1)
yy = np.sin(2.*(x+i*0.1))
#Replot
ax.plot(x,yx)
ay.plot(x,yy)
#Pause to allow redraw
plt.draw()
plt.pause(0.01)
I am trying to modify and example by making the animation run on increasing x values. I want update the x axis tick labels to update according to the x values.
I am trying to use the animation features (specifically FuncAnimation) in 1.2. I can set the xlimit but the tick labels are not updating. I tried explicitly setting the tick labels too and this does not work.
I saw this: Animating matplotlib axes/ticks and
I tried to adjust the bbox in animation.py but it did not work. I am fairly new to matplotlib and do not know enough about what is really going on to address this issue so I would appreciate any help.
Thank you
"""
Matplotlib Animation Example
author: Jake Vanderplas
email: vanderplas#astro.washington.edu
website: http://jakevdp.github.com
license: BSD
Please feel free to use and modify this, but keep the above information. Thanks!
"""
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(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.linspace(i, i+2, 1000)
y = np.sin(2 * np.pi * (x - 0.01 * i))
line.set_data(x, y)
ax.set_xlim(i, i+2)
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()
See Animating matplotlib axes/ticks, python matplotlib blit to axes or sides of the figure?, and Animated title in matplotlib
The simple answer is remove blit=True
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=200, interval=20)
If you have blit = True only artists that have changed are re-drawn (rather than re-drawing all of the artists) which makes the rendering more efficient. Artists are marked as changed if they are returned from the update-function (in this case animate). The other detail is that the artists must be with in the axes bounding box with the way the code works in animation.py. See one of the links at the top for how to deal with this.