Matplotlib animate does not update tick labels - python

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.

Related

Why does my matplotlib animation give an empty axis?

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:

Matplotlib FuncAnimation frame not rendering when a new artist added

I am making an animation in Matplotlib where new artists (specifically patches) are added every few frames, but when I run it, every frame in which a new artist is added is completely blank. I know there is some issue with the blitting since it works when I turn that off, but I need it on. I return every shape that is created or modified in each frame, just like the documentation requires. I am using the MacOSX backend.
My code looks similar to this:
from random import random
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
fig = plt.figure()
axe = fig.add_axes([0, 0, 1, 1], frameon=False)
circles = []
def update(i):
if not i % 10:
new_circle = plt.Circle((random(), random()), 0.05, color='black')
axe.add_patch(new_circle)
circles.append(new_circle)
for circle in circles:
circle.center = (random(), random())
return circles
animation = FuncAnimation(fig, update, frames=60, interval=1000/30, repeat=False, blit=True)
plt.show()
This appears to be a bug with matplotlib in the MacOSX backend, so the solution is just to work around it by using a different backend or not blitting if possible.

How to add legend/label in python animation

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

Removing points stored in a dictionary in a matplotlib basemap animation

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

How to save an animation without showing the previous frames in the video?

I would like to save an animation using Python but I get the frames superposed! I want to get the frames displayed individually.
Please here what I used:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
from numpy import pi, cos, sin
fig = plt.figure()
plt.axis([-1.5, 1.5,-1.5, 1.5])
ax = plt.gca()
ax.set_aspect(1)
N=100
xp = [None] * N
yp = [None] * N
def init():
# initialize an empty list of cirlces
return []
def animate(i):
xp[i]=sin(i*pi/10)
yp[i]=cos(i*pi/10)
patches = []
patches.append(ax.add_patch( plt.Circle((xp[i],yp[i]),0.02,color='b') ))
return patches
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=N-1, interval=20, blit=True)
anim.save("example.avi")
plt.show()
There are some things I'm not sure about and it really seems to be that the axis.plot() behavior and FuncAnimate() behavior are different. However, the code below works for both.
Use only one patch (in your case)
The key point from your code is that you are adding a new circle in addition to the old circles every iteration:
patches = []
patches.append(ax.add_patch( plt.Circle((xp[i],yp[i]),0.02,color='b') ))
Even though you clear the patches list, they are still stored in the axis.
Instead, just create one circle and change its position.
Clear first frame with init()
Also, init() needs to clear the patch from the base frame.
Standalone Example
from matplotlib import pyplot as plt
from matplotlib import animation
from numpy import pi, cos, sin
fig = plt.figure()
plt.axis([-1.5, 1.5, -1.5, 1.5])
ax = plt.gca()
ax.set_aspect(1)
N = 100
xp = []
yp = []
# add one patch at the beginning and then change the position
patch = plt.Circle((0, 0), 0.02, color='b')
ax.add_patch(patch)
def init():
patch.set_visible(False)
# return what you want to be cleared when axes are reset
# this actually clears even if patch not returned it so I'm not sure
# what it really does
return tuple()
def animate(i):
patch.set_visible(True) # there is probably a more efficient way to do this
# just change the position of the patch
x, y = sin(i*pi/10), cos(i*pi/10)
patch.center = x, y
# I left this. I guess you need a history of positions.
xp.append(x)
yp.append(y)
# again return what you want to be cleared after each frame
# this actually clears even if patch not returned it so I'm not sure
# what it really does
return tuple()
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=N-1, interval=20, blit=True)
# for anyone else, if you get strange errors, make sure you have ffmpeg
# on your system and its bin folder in your path or use whatever
# writer you have as: writer=animation.MencoderWriter etc...
# and then passing it to save(.., writer=writer)
anim.save('example.mp4')
plt.show()
Return values???
Regarding the return values of init() and animate(), It doesn't seem to matter what is returned. The single patch still gets moved around and drawn correctly without clearing previous ones.

Categories

Resources