Related
I would like to show in an animated plot bunches of images (which I assign to specific subplots). To this reason I wrote the following (I am using a jupyter notebook):
import matplotlib.pyplot as plt
from matplotlib import animation, rc
img1 = np.random.rand(1, 4)
img2 = np.random.rand(2, 4)
img3 = np.random.rand(3, 4)
img4 = np.random.rand(4, 4)
images = [[img1, img2], [img3, img4]]
%matplotlib auto #The only way I found so that this does not show an extra chart
def gen_anim():
fig, ax = plt.subplots(1, 2)
ims = []
for i in range(2):
im_1 = ax.ravel()[0].imshow(images[i][0], animated = True)
ax.ravel()[0].axis('off')
im_2 = ax.ravel()[1].imshow(images[i][1], animated = True)
ax.ravel()[1].axis('off')
ims.append([im_1, im_2])
plt.suptitle(t = 'Image set' + str(i), fontsize = 20)
return fig, ims
fig, ims = gen_anim()
rc('animation', html='html5')
anim = animation.ArtistAnimation(fig, ims, interval=1000, blit = True, repeat_delay = 1000)
anim
This works great except for the fact that I cannot update the suptitle of each set of images. I have seen examples where the title of each of the charts gets updated (here) but (based on my limited understanding of matplotlib) the suptitle is not the same.
So my question would be: Is it possible to update the suptitle in the current setting or should I try my luck with FuncAnimation?
(My efforts with FuncAnimation so far have been disappointing)
def animate(i):
to_plot_imgs = images[i]
fig, ax = plt.subplots(1, 2)
for j in range(2):
ax.ravel()[j].imshow(to_plot_imgs[j], interpolation='bilinear', animated = True)
ax.ravel()[j].axis('off')
plt.suptitle(t = 'Image set' + str(i), fontsize = 20)
anim = animation.FuncAnimation(plt.gcf(), animate, frames=len(images), interval=500)
anim
The title can also be changed dynamically by using the Funcanimation function. Initially, we set no title and font size, and update it with set_text during the animation.
import matplotlib.pyplot as plt
from matplotlib import animation, rc
from IPython.display import HTML
import numpy as np
img1 = np.random.rand(1, 4)
img2 = np.random.rand(2, 4)
img3 = np.random.rand(3, 4)
img4 = np.random.rand(4, 4)
images = [[img1, img2], [img3, img4]]
# %matplotlib auto #The only way I found so that this does not show an extra chart
fig, ax = plt.subplots(1, 2)
title = plt.suptitle(t='', fontsize = 20)
def animate(i):
to_plot_imgs = images[i]
# fig, ax = plt.subplots(1, 2)
for j in range(2):
ax.ravel()[j].imshow(to_plot_imgs[j], interpolation='bilinear', animated = True)
ax.ravel()[j].axis('off')
title.set_text('Image set{}'.format(i))
anim = animation.FuncAnimation(fig, animate, frames=len(images), interval=1500, repeat=False)
plt.close()
HTML(anim.to_html5_video())
I am trying to implement the solution presented by #Joe Kington in the following stack overflow thread:
Hiding lines after showing a pyplot figure
I have 16 lines I am trying to plot with this method, each of which have their own unique color and label. While #Joe Kington's solution works fine to get the plot to display, I cannot transfer his interactive method to my code with the interactive functionality of clicking on the legend entries to hide a line. That is, my code plots my data without error but there is no interactive functionality occurring.
I have tried:
setting the unique labels to be raw strings and regular strings
setting up every line to plot to be iterated over as in #Joe Kington's solution
using smaller legend labels
rearranging blocks of my code in the event that the functions defined in #Joe Kington's solution are out of scope
#Joe Kington's solution is below, which works fine:
import numpy as np
import matplotlib.pyplot as plt
def main():
x = np.arange(10)
fig, ax = plt.subplots()
for i in range(1, 31):
ax.plot(x, i * x, label=r'$y={}x$'.format(i))
ax.legend(loc='upper left', bbox_to_anchor=(1.05, 1),
ncol=2, borderaxespad=0)
fig.subplots_adjust(right=0.55)
fig.suptitle('Right-click to hide all\nMiddle-click to show all',
va='top', size='large')
leg = interactive_legend()
return fig, ax, leg
def interactive_legend(ax=None):
if ax is None:
ax = plt.gca()
if ax.legend_ is None:
ax.legend()
return InteractiveLegend(ax.get_legend())
class InteractiveLegend(object):
def __init__(self, legend):
self.legend = legend
self.fig = legend.axes.figure
self.lookup_artist, self.lookup_handle = self._build_lookups(legend)
self._setup_connections()
self.update()
def _setup_connections(self):
for artist in self.legend.texts + self.legend.legendHandles:
artist.set_picker(10) # 10 points tolerance
self.fig.canvas.mpl_connect('pick_event', self.on_pick)
self.fig.canvas.mpl_connect('button_press_event', self.on_click)
def _build_lookups(self, legend):
labels = [t.get_text() for t in legend.texts]
handles = legend.legendHandles
label2handle = dict(zip(labels, handles))
handle2text = dict(zip(handles, legend.texts))
lookup_artist = {}
lookup_handle = {}
for artist in legend.axes.get_children():
if artist.get_label() in labels:
handle = label2handle[artist.get_label()]
lookup_handle[artist] = handle
lookup_artist[handle] = artist
lookup_artist[handle2text[handle]] = artist
lookup_handle.update(zip(handles, handles))
lookup_handle.update(zip(legend.texts, handles))
return lookup_artist, lookup_handle
def on_pick(self, event):
handle = event.artist
if handle in self.lookup_artist:
artist = self.lookup_artist[handle]
artist.set_visible(not artist.get_visible())
self.update()
def on_click(self, event):
if event.button == 3:
visible = False
elif event.button == 2:
visible = True
else:
return
for artist in self.lookup_artist.values():
artist.set_visible(visible)
self.update()
def update(self):
for artist in self.lookup_artist.values():
handle = self.lookup_handle[artist]
if artist.get_visible():
handle.set_visible(True)
else:
handle.set_visible(False)
self.fig.canvas.draw()
def show(self):
plt.show()
if __name__ == '__main__':
fig, ax, leg = main()
plt.show()
Here is a snippet of my code that isn't working that is defined in a module plotting in a function called main() (I have other modifications I need to make such as changing axis labels, unique colors, etc.):
import numpy as np
import matplotlib.pyplot as plt
def main(t,n,timeSpan):
colors = ['blue','red','green','black','magenta','teal','orange','chartreuse','purple','sienna','goldenrod','lightgray','olive','cyan','maroon','pink']
labels = [r'foo1',r'bar2',r'foo3',r'bar4',r'foo5',r'bar6',r'foo7',r'bar8',r'foo9',r'bar10',r'foo11',r'bar12',r'foo13',r'bar14',r'foo15',r'bar16']
fig, ax = plt.subplots(figsize=(15,8))
for i in range(0, len(colors)):
ax.plot(t, n[:,i], color = colors[i], label = labels[i])
ax.set_xlabel('x label')
ax.set_ylabel('y label')
ax.set_xlim([0,timeSpan])
ax.set_ylim([0,110])
ax.legend(loc='upper left', bbox_to_anchor=(1.05, 1),
ncol=2, borderaxespad=0)
fig.subplots_adjust(right=0.55)
fig.suptitle('Title\n(Right-click to hide all\nMiddle-click to show all)',
va='top', size='large')
leg = interactive_legend()
plt.show()
def interactive_legend(ax=None):
if ax is None:
ax = plt.gca()
if ax.legend_ is None:
ax.legend()
return InteractiveLegend(ax.get_legend())
class InteractiveLegend(object):
def __init__(self, legend):
self.legend = legend
self.fig = legend.axes.figure
self.lookup_artist, self.lookup_handle = self._build_lookups(legend)
self._setup_connections()
self.update()
def _setup_connections(self):
for artist in self.legend.texts + self.legend.legendHandles:
artist.set_picker(10) # 10 points tolerance
self.fig.canvas.mpl_connect('pick_event', self.on_pick)
self.fig.canvas.mpl_connect('button_press_event', self.on_click)
def _build_lookups(self, legend):
labels = [t.get_text() for t in legend.texts]
handles = legend.legendHandles
label2handle = dict(zip(labels, handles))
handle2text = dict(zip(handles, legend.texts))
lookup_artist = {}
lookup_handle = {}
for artist in legend.axes.get_children():
if artist.get_label() in labels:
handle = label2handle[artist.get_label()]
lookup_handle[artist] = handle
lookup_artist[handle] = artist
lookup_artist[handle2text[handle]] = artist
lookup_handle.update(zip(handles, handles))
lookup_handle.update(zip(legend.texts, handles))
return lookup_artist, lookup_handle
def on_pick(self, event):
handle = event.artist
if handle in self.lookup_artist:
artist = self.lookup_artist[handle]
artist.set_visible(not artist.get_visible())
self.update()
def on_click(self, event):
if event.button == 3:
visible = False
elif event.button == 2:
visible = True
else:
return
for artist in self.lookup_artist.values():
artist.set_visible(visible)
self.update()
def update(self):
for artist in self.lookup_artist.values():
handle = self.lookup_handle[artist]
if artist.get_visible():
handle.set_visible(True)
else:
handle.set_visible(False)
self.fig.canvas.draw()
def show(self):
plt.show()
t = np.arange(1000)
n = np.zeros([1000,16])
for i in range(0, 1000):
for j in range(0, 16):
n[i][j] = t[i] * j
timeSpan = 1000
# if __name__ == "__main__":
# main(t,n,timeSpan)
colors = ['blue','red','green','black','magenta','teal','orange','chartreuse','purple','sienna','goldenrod','lightgray','olive','cyan','maroon','pink']
labels = [r'foo1',r'bar2',r'foo3',r'bar4',r'foo5',r'bar6',r'foo7',r'bar8',r'foo9',r'bar10',r'foo11',r'bar12',r'foo13',r'bar14',r'foo15',r'bar16']
fig, ax = plt.subplots(figsize=(15,8))
for i in range(0, len(colors)):
ax.plot(t, n[:,i], color = colors[i], label = labels[i])
ax.set_xlabel('x label')
ax.set_ylabel('y label')
ax.set_xlim([0,timeSpan])
ax.set_ylim([0,110])
ax.legend(loc='upper left', bbox_to_anchor=(1.05, 1),
ncol=2, borderaxespad=0)
fig.subplots_adjust(right=0.55)
fig.suptitle('Title\n(Right-click to hide all\nMiddle-click to show all)',
va='top', size='large')
leg = interactive_legend()
plt.show()
I have determined that when I leave the code as is it works, but when I comment out lines 113-131 and call main() from lines 111 and 112 insead, it no longer works. Is this a scope issue? Not sure why the way I am calling the interactive_legend() function determines whether it works or not.
I'm not quite getting how to create a class for animating data. Here is the gist:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
x = np.arange(100).reshape((100, 1))
y = np.random.randn(100, 1)
xy = np.hstack((x, y))
class PlotData:
def __init__(self):
fig, ax = plt.subplots()
fig.set_size_inches((11, 9))
self.fig = fig
self.ax = ax
self.ln0, = ax.plot([], [])
def init(self):
self.ln0.set_data([], [])
return(self.ln0, )
def update(self, frame_no):
data = xy[0:frame_no + 1]
self.ln0.set_data(data[:, 0], data[:, 1])
return(self.ln0, )
if __name__ == '__main__':
my_plot = PlotData()
anim = animation.FuncAnimation(my_plot.fig, my_plot.update,
init_func=my_plot.init, blit=True,
frames=99, interval=50)
plt.show()
This only produces the init method output but not the update, so ends up a blank plot with no animation. What is going on?
For me your code works perfectly fine. The only problem is that most of the data are outside of the plotting limits. If you adjust your plot limits like this:
class PlotData:
def __init__(self):
fig, ax = plt.subplots(figsize = (11,9))
self.fig = fig
self.ax = ax
self.ax.set_xlim([0,100])
self.ax.set_ylim([-3,3])
self.ln0, = ax.plot([], [])
The line is animated just fine. If you want that the x- and y-limits are adjusted automatically, see this question on how to do it. However, if I recall correctly, this will only work properly with blit=False.
I'm a begginner at matplotlib/Python and I am building an animated scatter plot. I want to place a button below the plot so the user can start/pause/resume the animation. This is the plot (I know it's a mess, still working on it):
As a start I tried to add a button named Play to replay the animation after its first automatic execution, but I'm having trouble understanding how positioning it below the plot works, for it is "dragging" everything with it (the title, xlabel, ylabel and so on):
Here's the code:
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import mplcursors
from matplotlib.animation import FuncAnimation
from matplotlib.widgets import Slider, Button, RadioButtons
df = pd.read_excel('nations2.xls')
df.head()
uniqueYears = df['Ano'].unique()
font = {'family': 'sans-serif',
'color': 'black',
'weight': 'normal',
'size': 16,
}
fig, ax = plt.subplots()
def animate(frames):
ax.clear()
data = df[df['Ano'] == uniqueYears[frames]]
ax.scatter(y = data['ExpecVida'],
x = data['PIBperCapita'],
s = data['PopX1000']/40000,
c = data['Regiao'].astype('category').cat.codes,
cmap = cm.viridis,
edgecolors = 'none',
alpha = 0.5)
ax.set_xlim([0,50000], auto=True)
ax.set_ylim([0,100], auto=True)
plt.title('Wealth and Health of Nations', fontsize=18)
plt.xlabel('GDP per Capita ($)', fontsize=14)
plt.ylabel('Life Expectancy (years)', fontsize=14)
plt.grid(color = '#A9A9A9')
plt.text(2, 0.65, uniqueYears[frames], fontdict=font)
for i, txt in enumerate(data['Pais']):
ax.annotate(txt, (data['PIBperCapita'].iat[i], data['ExpecVida'].iat[i]), fontsize = 6)
anim = FuncAnimation(fig, animate, frames=len(uniqueYears),interval = 200, repeat=False)
class Index(object):
ind = 0
def play(self, event):
FuncAnimation(fig, animate, frames=len(uniqueYears),interval = 200, repeat=False)
callback = Index()
axplay = plt.axes([0.81, 0.05, 0.1, 0.075])
bplay = Button(axplay, 'Play')
bplay.on_clicked(callback.play)
I am trying to animate different objects in the same graph using pyplot's funcanimation.
It works almost as I expect it to, except for the order in which the different elements are displayed in. So the plot curve, text and legend are shown behind the image where they are barely seen.
Here is my (not so) minimal working example:
#! /usr/bin/python
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
import random
def init():
line_simu.set_data([], [])
time_text.set_text('')
imobj.set_data(np.zeros((100, 100)))
imobj.set_zorder(0)
time_text.set_zorder(10)
return line_simu, time_text, imobj
def animate(i):
imobj.set_zorder(0)
time_text.set_zorder(10)
y_simu = np.linspace(0,100, 100)
x_simu = np.linspace(-10, 10, 100)
line_simu.set_data(x_simu, y_simu)
time_text.set_text('time = %.1f' % i )
global data
imobj.set_data( data + np.random.random((100,1)) * 0.5 )
return line_simu, time_text, imobj
def forceAspect(ax,aspect=1):
im = ax.get_images()
extent = im[0].get_extent()
ax.set_aspect(abs((extent[1]-extent[0])/(extent[3]-extent[2]))/aspect)
fig = plt.figure()
ax = plt.axes(xlim=(-15,15), ylim=(-110, 0) , aspect=1)
data = np.random.random((100,100)) - .5
imobj = ax.imshow( data , extent=[-15,15, -110, 0.0], origin='lower', cmap=plt.cm.gray, vmin=-2, vmax=2, alpha=.7, zorder=0, aspect=1)
line_simu, = ax.plot([], [],"r--", lw=2, markersize=4 , label = "Some curve" , zorder= 2 )
time_text = ax.text(-14.9, -108, '', zorder=10)
l = plt.legend(loc='lower right', prop={'size':8} )
l.set_zorder(200)
forceAspect(ax,aspect=1)
anim = animation.FuncAnimation(fig, animate, init_func=init, frames=range( 50), interval=3000, blit=True)
plt.show()
Without animation, I can easily control the order of the different elements with set_zorder, but when the animation updates the image, this order is lost. I tried to set the zorder in the init function and again in the animate function, without success.
I am very thankful for any help on that matter.
To answer my own question: It seems that the order in witch the init() and animate() functions return objects controls the order in which they are displayed. Additionally those functions should return the legend object in order to include it in the animation.
Here is my corrected code:
#! /usr/bin/python
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
import random
def init():
imobj.set_data(np.zeros((100, 100)))
line_simu.set_data([], [])
time_text.set_text('time = 0.0')
return imobj , line_simu, time_text, l
def animate(i):
global data
imobj.set_data( data + np.random.random((100,1)) * 0.5 )
imobj.set_zorder(0)
y_simu = np.linspace(-100,-10, 100)
x_simu = np.linspace(-10, 10, 100)
line_simu.set_data(x_simu, y_simu)
time_text.set_text('time = %.1f' % i )
return imobj , line_simu, time_text, l
def forceAspect(ax,aspect=1):
im = ax.get_images()
extent = im[0].get_extent()
ax.set_aspect(abs((extent[1]-extent[0])/(extent[3]-extent[2]))/aspect)
fig = plt.figure()
ax = plt.axes(xlim=(-15,15), ylim=(-110, 0) , aspect=1)
data = np.random.random((100,100)) - .5
imobj = ax.imshow( data , extent=[-15,15, -110, 0.0], origin='lower', cmap=plt.cm.gray, vmin=-2, vmax=2, alpha=1.0, zorder=1, aspect=1)
line_simu, = ax.plot([], [],"r--", lw=2, markersize=4 , label = "Some curve" , zorder= 1 )
time_text = ax.text(-14.0, -108, '', zorder=10)
forceAspect(ax,aspect=1)
l = plt.legend(loc='lower right', prop={'size':8} )
anim = animation.FuncAnimation(fig, animate, init_func=init, frames=range( 50), interval=500, blit=True)
plt.show()