How to add legend/label in python animation - python

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

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:

Blit Behaviour in FuncAnimate -Want to keep previous data

I'm trying to animate a figure using matplotlib->FuncAnimate function. However, I'm having trouble understanding how Blit works. With each frame, I want to draw only the new data point on top of the old one. It says that using Blit it should automatically update only the values that changed. Thus, if I turn it on (blit=True) the previous data points should remain in my figure. But this is not the case. The previous data get deleted and the figure gets redraw from scratch.
In the documentation, it says that I have to return "iterable_of_artists" and the algorithm will know which data has changed. I want to just pass the new data and just plot on top of the old one. By the way, what is an "iterable_of_artists", is that just a list of objects that can be drawn? if someone could point me out to the definition, I would appreciate it.
Anyway, I have worked several base examples that show the odd behavior. In the first example, I'm turning Blit=True and drawing only the new data using the animate function. This in theory should draw on top of the old ones, but is not the case, only the new data is drawn.
import time
import random
import numpy
import matplotlib
import matplotlib.pyplot as pyplot
from matplotlib.animation import FuncAnimation
def livePlot():
fig, ax = pyplot.subplots(1,1)
ax = pyplot.axes(xlim=(0, 2), ylim=(0, 100))
line, = ax.plot([], [], 'ro') #ax.plot will return a tupple
def init():
line.set_data(0, 50)
return line, #Return is not necessary when blit=False
def animate(frame):
x = frame
y = random.randint(0, 100)
line.set_data(x,y)
return line, #Return is not necessary when blit=False
animation = FuncAnimation(
fig, animate,
init_func = init,
frames= [0.5, 1, 1.5, 2.0],
interval=1000,
repeat=False,
blit=True, # Turning on Blit
cache_frame_data = True)
pyplot.show()
if __name__ == "__main__":
livePlot()
I was able to achieve my goal by tricking the FuncAnimate method. I can use the ax and plot in each frame the new data. If I do that, the old data remains and only the new data is drawn. However, I can do that with Blit=True or Blit=False, it has no effect. So, I'm so confused on how Blit works and what would be the correct way to plot only the new data without having to create a list with all the data to plot. Passing a large list will create a large variable in memory if I have a long set of data points. Here is my workaround but I'm not sure if this is the correct way to do it or if there is a better ways of using Blit=True and just redraw the new data.
import time
import random
import numpy
import matplotlib
import matplotlib.pyplot as pyplot
from matplotlib.animation import FuncAnimation
def livePlot():
fig, ax = pyplot.subplots(1,1)
ax = pyplot.axes(xlim=(0, 2), ylim=(0, 100))
def init():
ax.plot(0, 50, 'ro')
return []
def animate(frame):
x = frame
y = random.randint(0, 100)
ax.plot(x, y, 'ro') # plotting directly on the axis. This keeps the old data
return [] # fooling the blit algorithm with an empty stream
animation = FuncAnimation(
fig, animate,
init_func = init,
frames= [0.5, 1, 1.5, 2.0],
interval=1000,
repeat=False,
blit=True,
cache_frame_data = True)
pyplot.show()
if __name__ == "__main__":
livePlot()

Matplotlib's Animation.save produces un-openable pngs

I'm trying to save each frame of an animation as a png. The relevant code looks like this:
ani = animation.FuncAnimation(fig, update, fargs=(img, grid, N, beta, survival, theta),
frames=30,
interval=updateInterval,
save_count=50)
ani.save("animationpng_%03d.png")
plt.show()
I get 30 png files numbered correctly but I can't open them in any image viewer - they seem to be corrupted or either "pretend" files with nothing in them. The animation itself definitely works - it appears with plt.show() and I've successfully saved an mp4 version. Can someone point me to a solution?
Here's a workaround / alternative solution:
If you are using a FuncAnimation, my first thought would be to add a plt.savefig command in your update function - so as the animation is built, you save individual PNGs. Here is an example animation from another post I answered (just using it b/c it is available), but I have modified it to include savefig for each frame:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
time_steps = 50
N_nodes = 100
positions = []
solutions = []
for i in range(time_steps):
positions.append(np.random.rand(2, N_nodes))
solutions.append(np.random.random(N_nodes))
fig, ax = plt.subplots()
marker_size = 5 #upped this to make points more visible
def animate(i):
""" Perform animation step. """
#important - the figure is cleared and new axes are added
fig.clear()
ax = fig.add_subplot(111, aspect='equal', autoscale_on=False, xlim=(0, 1), ylim=(0, 1))
#the new axes must be re-formatted
ax.set_xlim(0,1)
ax.set_ylim(0,1)
# and the elements for this frame are added
ax.text(0.02, 0.95, 'Time step = %d' % i, transform=ax.transAxes)
s = ax.scatter(positions[i][0], positions[i][1], s = marker_size, c = solutions[i], cmap = "RdBu_r", marker = ".", edgecolor = None)
fig.colorbar(s)
#
# HERE IS THE NEW LINE
# v v v v v v v v v v
plt.savefig('frame {}.png'.format(str(i)))
plt.xlabel('x [m]')
plt.ylabel('y [m]')
plt.grid(b=None)
ani = animation.FuncAnimation(fig, animate, interval=100, frames=range(time_steps))
ani.save('animation.gif', writer='pillow')
Note that you still have to include the saving of the animation as a GIF (last line), or else there won't actually be iteration over every frame.
Trying to adapt your code, I couldn't get my frames to save as images after creating the animation - I'm not sure why this should work or what is going on with your unopenable files (sorry!).
You can save the animation as gif and then easily convert it to png format.

Matplotlib Scatterplot Animation with Time Stamp

Hello and thanks in advance for any tips and advice. I'm trying to create an animated scatterplot that changes dot color based on model results that I'm reading into python. The model results would be much easier to interpret if I could display an updating time stamp along with the animation.
I tried to incorporate the answer for to this post:
Matplotlib animating multiple lines and text, but I think the fact that I'm using a scatter dataset instead of lines complicates the way the data needs to be returned, and I'm not sure how to correct the problem.
This code alternately flashes the time step and the scatter animation, which makes for a distracting and useless visual.
# load result
testModel.addResult(testDye,type='dye')
xs = testModel.grid['x']
ys = testModel.grid['y']
zs = testModel.grid['z']
# graphing
fig = plt.figure()
ax = fig.add_subplot(111)
gcf = plt.gcf()
scat = ax.scatter(xs,ys)
timetext = gcf.text(0.2, 0.2, '', size = 14)
def animate(i):
print i
actDye = testModel.getResult(type='dye',layer=1,tIndex=i)
scat.set_array((actDye/1000)*100) #update colors of points
timetext.set_text(i)
return scat,
def init():
actDye = testModel.getResult(type='dye',layer=1,tIndex=0)
scat.set_array((actDye/1000)*100) #update colors of points
return scat,
ani = animation.FuncAnimation(fig,animate,np.arange(0,200),init_func=init, interval=500, blit=True)
plt.show()
I think that returning timetext along with scat, would fix the problem (like it did for the other poster), but I can't get the syntax right. Switching to this block of code gives me the error that the 'PathCollection' object is not iterable.
def animate(i):
print i
actDye = testModel.getResult(type='dye',layer=1,tIndex=i)
scat.set_array((actDye/1000)*100) #update colors of points
timetext.set_text(i)
return tuple(scat,) + (timetext,)
What should I be doing differently? Thanks!

matplotlib plotting in loop, removing colorbar but whitespace remains

My code is something (roughly) like this:
UPDATE: I've redone this with some actual mock-up code that reflects my general problem. Also, realized that the colorbar creation is in the actual loop as otherwise there's nothing to map it to. Sorry for the code before, typed it up in frantic desperation at the very end of the workday :).
import numpy
import matplotlib as mplot
import matplotlib.pyplot as plt
import os
#make some mock data
x = np.linspace(1,2, 100)
X, Y = np.meshgrid(x, x)
Z = plt.mlab.bivariate_normal(X,Y,1,1,0,0)
fig = plt.figure()
ax = plt.axes()
'''
Do some figure-related stuff that take up a lot of time,
I want to avoid having to do them in the loop over and over again.
They hinge on the presence of fig so I can't make
new figure to save each time or something, I'd have to do
them all over again.
'''
for i in range(1,1000):
plotted = plt.plot(X,Y,Z)
cbar = plt.colorbar(ax=ax, orientation = 'horizontal')
plt.savefig(os.path.expanduser(os.path.join('~/', str(i))))
plt.draw()
mplot.figure.Figure.delaxes(fig, fig.axes[1]) #deletes but whitespace remains
'''
Here I need something to remove the colorbar otherwise
I end up with +1 colorbar on my plot at every iteration.
I've tried various things to remove it BUT it keeps adding whitespace instead
so doesn't actually fix anything.
'''
Has anyone come across this problem before and managed to fix it? Hopefully this is enough
for an idea of the problem, I can post more code if needed but thought it'd be less of a clutter if I just give an overview example.
Thanks.
colorbar() allows you explicitly set which axis to render into - you can use this to ensure that they always appear in the same place, and not steal any space from another axis. Furthermore, you could reset the .mappable attribute of an existing colorbar, rather than redefine it each time.
Example with explicit axes:
x = np.linspace(1,2, 100)
X, Y = np.meshgrid(x, x)
Z = plt.mlab.bivariate_normal(X,Y,1,1,0,0)
fig = plt.figure()
ax1 = fig.add_axes([0.1,0.1,0.8,0.7])
ax2 = fig.add_axes([0.1,0.85,0.8,0.05])
...
for i in range(1,5):
plotted = ax1.pcolor(X,Y,Z)
cbar = plt.colorbar(mappable=plotted, cax=ax2, orientation = 'horizontal')
#note "cax" instead of "ax"
plt.savefig(os.path.expanduser(os.path.join('~/', str(i))))
plt.draw()
I had a very similar problem, which I finally managed to solve by defining a colorbar axes in a similar fashion to:
Multiple imshow-subplots, each with colorbar
The advantage compared to mdurant's answer is that it saves defining the axes location manually.
import matplotlib.pyplot as plt
import IPython.display as display
from mpl_toolkits.axes_grid1 import make_axes_locatable
from pylab import *
%matplotlib inline
def plot_res(ax,cax):
plotted=ax.imshow(rand(10, 10))
cbar=plt.colorbar(mappable=plotted,cax=cax)
fig, axarr = plt.subplots(2, 2)
cax1 = make_axes_locatable(axarr[0,0]).append_axes("right", size="10%", pad=0.05)
cax2 = make_axes_locatable(axarr[0,1]).append_axes("right", size="10%", pad=0.05)
cax3 = make_axes_locatable(axarr[1,0]).append_axes("right", size="10%", pad=0.05)
cax4 = make_axes_locatable(axarr[1,1]).append_axes("right", size="10%", pad=0.05)
# plt.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=0.3, hspace=0.3)
N=10
for j in range(N):
plot_res(axarr[0,0],cax1)
plot_res(axarr[0,1],cax2)
plot_res(axarr[1,0],cax3)
plot_res(axarr[1,1],cax4)
display.clear_output(wait=True)
display.display(plt.gcf())
display.clear_output(wait=True)

Categories

Resources