I am not able to make (animated) labels using FuncAnimation from matplotlib. Please find below a minimal code that I made. ax.annotate has no effect at all - the animation itself works though. What can I change to get animated labels/titles, which are different for each frame?
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots()
fig.clear()
steps = 10
data = np.random.rand(20,20,10)
imagelist = [data[:,:,i] for i in range(steps) ]
im = plt.imshow(imagelist[0], cmap='Greys', origin='lower', animated=True)
plt.colorbar(shrink=1, aspect=30, label='Counts')
# does not work
ax.annotate("Frame: %d " % steps,(0.09,0.92),xycoords ='figure fraction')
def updatefig(j):
im.set_array(imagelist[j])
return [im]
ani = animation.FuncAnimation(fig, updatefig, frames=range(steps), interval=200, blit=True)
plt.show()
Two problems overall:
The annotation text never gets updated in updatefig()
The canvas gets cleared+blitted, which wipes out annotations
Five steps to resolve:
Remove fig.clear() to preserve annotations
Save the initial annotation's handle
Update the annotation's text in updatefig()
Include the annotation in the return of updatefig()
Set blit=False to preserve annotations
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots()
#1 do NOT call fig.clear()
steps = 10
data = np.random.rand(20, 20, steps)
im = plt.imshow(data[:, :, 0], cmap='Greys', origin='lower', animated=True)
plt.colorbar(shrink=1, aspect=30, label='Counts')
#2 annotate frame 0 and save handle
annot = ax.annotate('Frame: 0', (0.09, 0.92), xycoords='figure fraction')
def updatefig(j):
im.set_array(data[:, :, j])
#3 update annotation text
annot.set_text(f'Frame: {j}')
#4 include annotation when returning
return im, annot
#5 set blit=False
anim = animation.FuncAnimation(fig, updatefig, frames=steps, blit=False)
Related
I have been trying to create a horizontal bar chart where the title and data change during each frame. The issue I am running into is that if I use blit=True, the data updates but not the title. When I use blit=False the title changes but not the data (it only increases).
I have read through dozens of answers and tried everything including set_title and set_text but I am at a total loss. Thank you for your help.
%matplotlib
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
import csv
people = ('','Jim', 'Dan')
plt.rcdefaults()
fig, ax = plt.subplots()
y_pos = np.arange(len(people))
ax.set_xlim(0,10)
ax.set_yticks(y_pos)
ax.set_yticklabels(people)
ax.invert_yaxis()
ax.set_xlabel('Skill')
titleList=['Basketball','Hockey']
df=[[0,5,7],[0,4,9]]
title = ax.text(0.5,0.95, "Test", bbox={'facecolor':'w', 'alpha':0.5, 'pad':5},transform=ax.transAxes, ha="center")
def animate(i):
# Example data
while i<2:
ax.set_yticks(y_pos)
ax.set_yticklabels(people)
ax.set_xlabel(titleList[i])
performance=df[i]
title.set_text(str(titleList[i]))
line= ax.barh(y_pos, performance, align='center',
color='blue', ecolor='None')
return line
ani = animation.FuncAnimation(fig,animate, frames=5, blit=True
,interval=2000,repeat=False)
plt.show()
You call a FuncAnimation() with frames=5, animate(i) will thus try to set label and titel via titleList[i] which however only has 2 entries. Especially with blit=True, this will throw errors.
Your animate() functions returns line; if we print(line), we find it is rather a <BarContainer object of 3 artists> than a line, i.e. the three rectangles of barh(). You should rather store the barh() in rects and then return [rect for rect in rects], see this question
Complete Code:
import pandas as pd
import matplotlib as mpl ## uncomment this if you are running this on a Mac
mpl.use('TkAgg') ## and want to use blit=True
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
import csv
people = ('','Jim', 'Dan')
plt.rcdefaults()
fig, ax = plt.subplots()
y_pos = np.arange(len(people))
ax.set_xlim(0,10)
ax.set_yticks(y_pos)
ax.set_yticklabels(people)
ax.invert_yaxis()
ax.set_xlabel('Skill')
titleList=['Basketball','Hockey']
df=[[0,5,7],[0,4,9]]
def animate(i):
# Example data
while i<2:
ax.set_yticks(y_pos)
ax.set_yticklabels(people)
ax.set_xlabel(titleList[i])
performance=df[i]
title = ax.text(0.5,0.95,str(titleList[i]), bbox={'facecolor':'w', 'alpha':0.5, 'pad':5},transform=ax.transAxes, ha="center")
rects = ax.barh(y_pos, performance, align='center',
color='blue', ecolor='None')
return [rect for rect in rects] + [title]
ani = animation.FuncAnimation(fig,animate, frames=2, blit=True
,interval=2000,repeat=False)
plt.show()
I have an array with a lot of variables. Each row represents the data needed to calculate one frame of y, while x is numpy.linspace array.
When I use plt.plot(x, np.conj(bildevector[i,:])*bildevector[i,:] in a for-loop, I get exactly the the images I want, but I want them as a video file.
I have tried using ArtistAnimation, but I cannot get it to work.
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
""" Plotter de lagrede verdiene """
fig = plt.figure()
ims = []
for i in range(Nx):
plt.grid(True)
#plt.plot(x, np.conj(bildevector[i,:])*bildevector[i,:])
im = plt.imshow(plt.plot(x,
np.conj(bildevector[i,:])*bildevector[i,:]), animated=True)
ims.append([im])
#plt.show()
#plt.close()
ani = animation.ArtistAnimation(fig, ims, interval=50, blit=True,
repeat_delay=1000)
The parts with # are the ones that work.
I have also tried this:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
def f(u,v):
return np.conj(bildevector[v,:])*bildevector[v,:]
fig = plt.figure()
ax = plt.axes(xlim=(0,L),ylim=(0, 8*10^7))
ims = []
for i in range(340):
im = plt.imshow((x, np.real(np.conj(bildevector[i,:])*bildevector[i,:])), animated=True)
ims.append([im])
ani = animation.ArtistAnimation(fig, ims, interval=50, blit=True,
repeat_delay=1000)
ani.save('dynamic_images.mp4')
plt.show()
I expect to see the final image of the graph in the console, but all I get is an empty picture with the y-axis right, and the x-axis in one point, 0. I also expect to get a file with some actual movement.
I think you got confused by the matplotlib example that is using plt.imshow() to create its artists. You seem to be wanting to create a plot using plt.plot(), so you don't have to use imshow:
fig = plt.figure()
ims = []
for i in range(Nx):
plt.grid(True)
p = plt.plot(x,
np.conj(bildevector[i,:])*bildevector[i,:]), animated=True)
ims.append(p)
ani = animation.ArtistAnimation(fig, ims, interval=50, blit=True,
repeat_delay=1000)
Note that there is one subtlety in the fact that plt.plot() returns a list of artists, while most other plotting functions (including plt.imshow()) usually only return one Artist object. You have to keep this in mind when you want to append the returned values to a list.
I trying to make HTML(anim.to_html5_video) animation work in jupyter with seaborn heatmap.
First, I get working working samples from documentation, and make "pure matplotlib" image map animated example, it worked, with small problem ("parasite output" in animation cell)
Then, I tried to make it work with seaborn.heatmap… but failed. Animation looks like "infinite mirror" — obviously something wrong with matplotlib axes/plot composition, but I can't get it.
Common initialization cell:
import pandas as pd
import seaborn as sns
import numpy as np
%matplotlib inline
#%matplotlib notebook # Tried both, not needed for animation.
import matplotlib.pyplot as plt
from matplotlib import animation, rc
from IPython.display import HTML
Animation worked, but "unwanted static output image exists":
fig, ax = plt.subplots()
nx = 50
ny = 50
line2d, = ax.plot([], [], lw=2)
def init():
line2d.set_data([], [])
ax.imshow(np.zeros((nx, ny)))
return (line2d,)
def animate(i):
data = np.random.rand(nx, ny)
ax.set_title('i: ' + str(i))
ax.imshow(data)
return (line2d,)
anim = animation.FuncAnimation(fig, animate, init_func=init, frames=10, interval=1000, blit=False)
HTML(anim.to_html5_video())
So, looks that all OK with my jupyter setup (packages, ffmpeg, etc).
But, I cannot get how to make it with seaborn.heatmap:
fig, ax = plt.subplots()
nx = 50
ny = 50
line2d, = ax.plot([], [], lw=2)
ax_global = ax
def init_heatmap():
line2d.set_data([], [])
sns.heatmap(np.zeros((nx, ny)), ax=ax_global)
return (line2d,)
def animate_heatmap(i):
data = np.random.rand(nx, ny)
sns.heatmap(data, ax=ax_global)
ax.set_title('Frame: ' + str(i))
return (line2d,)
anim = animation.FuncAnimation(fig, animate_heatmap, init_func=init_heatmap,
frames=10, interval=1000, blit=True)
HTML(anim.to_html5_video())
Both samples ready to test on github
Of course, I want to see animation with random map and "stable heat-axes"
but get this
https://vimeo.com/298786185/
You can toggle the "colorbar". From the Seaborn.heatmap documentation, you need to change sns.heatmap(data, ax=ax_global) to sns.heatmap(data, ax=ax_global, cbar=False) and also do the same inside the init_heatmap().
I am trying to use the ArtistAnimation to create an animation. And everything is working, except set_title isn't working. I don't understand why blit=False doesn't work.
Do I need to go to a FuncAnimation?
for time in np.arange(-0.5,2,0.01):
writer.UpdatePipeline(time=time)
df=pd.read_csv(outputPath + '0.csv', sep=',')
df['x'] = 1E6*df['x']
df['Efield'] = 1E-6*df['Efield']
line3, = ax3.plot(df['x'], df['Efield'])
line1A, = ax1.semilogy(df['x'], df['em_lin'])
line1B, = ax1.semilogy(df['x'], df['Arp_lin'])
line2A, = ax2.plot(df['x'], df['Current_em'])
line2B, = ax2.plot(df['x'], df['Current_Arp'])
ax1.set_title('$t = ' + str(round(time, n)))
ims.append([line1A, line1B, line2A, line2B, line3])
im_ani = animation.ArtistAnimation(fig, ims, interval=50, blit=False)
im_ani.save(outputPath + 'lines.gif', writer='imagemagick', fps=10, dpi=100)
plt.show()
Two problems. The immeadiate is that the title is not part of the list of artists to update, hence the animation cannot know that you want to update it.
The more profound problem is that there is only a single title per axes. Hence even if you include the title in the list of artists, it will always show the text that it has last been set to.
The solution would be not to use the axes' title to update, but other text elements, one per frame.
import matplotlib.pyplot as plt
from matplotlib import animation
import numpy as np
a = np.random.rand(10,10)
fig, ax=plt.subplots()
container = []
for i in range(a.shape[1]):
line, = ax.plot(a[:,i])
title = ax.text(0.5,1.05,"Title {}".format(i),
size=plt.rcParams["axes.titlesize"],
ha="center", transform=ax.transAxes, )
container.append([line, title])
ani = animation.ArtistAnimation(fig, container, interval=200, blit=False)
plt.show()
For reference the same as FuncAnimation would look as follows, where the title can be set directly as usual.
import matplotlib.pyplot as plt
from matplotlib import animation
import numpy as np
a = np.random.rand(10,10)
fig, ax=plt.subplots()
ax.axis([-0.5,9.5,0,1])
line, = ax.plot([],[])
def animate(i):
line.set_data(np.arange(len(a[:,i])),a[:,i])
ax.set_title("Title {}".format(i))
ani = animation.FuncAnimation(fig,animate, frames=a.shape[1], interval=200, blit=False)
plt.show()
I am trying to animate a violinplot, so I have started off with something I think should be very basic, but it is not working. I think the problem is that violinplot doesn't accept set_data, but I don't otherwise know how to pass the changing data to violinplot. For this example I would like a plot where the mean slowly shifts to higher values. If I am barking up the wrong tree, please advise on a code which does work to animate violinplot.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots()
data = np.random.rand(100)
def animate(i):
v.set_data(data+i) # update the data
return v
v = ax.violinplot([])
ax.set_ylim(0,200)
v_ani = animation.FuncAnimation(fig, animate, np.arange(1, 200),
interval=50, blit=True)
Indeed, there is no set_data method for the violinplot. The reason is probably, that there is a lot of calculations going on in the background when creating such a plot and it consists of a lot of different elements, which are hard to update.
The easiest option would be to simply redraw the violin plot and not use blitting.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots()
data = np.random.normal(loc=25, scale=20, size=100)
def animate(i, data):
ax.clear()
ax.set_xlim(0,2)
ax.set_ylim(0,200)
data[:20] = np.random.normal(loc=25+i, scale=20, size=20)
np.random.shuffle(data)
ax.violinplot(data)
animate(0)
v_ani = animation.FuncAnimation(fig, animate, np.arange(1, 200),
fargs=(data,), interval=50, blit=False)
plt.show()