Reputation: 1263
I've been using the animation method outlined at Managing dynamic plotting in matplotlib Animation module to create animations and place them on a tkinter FigureCanvas.
I'm having difficulties animating a sequence of barplots in such a way that the y-axis tickmark labels appear as I want them to. My animation will have 100 frames, each consisting of a barplot using four x-values. The data is stored in a 100-by-4 matrix, whose entries are random and fall between zero and one. Each frame of the animation appears on a FigureCanvas. Below is a summary of what I've done so far, where Player
is the class defined at the link above.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from tkinter import Tk, TOP, BOTH
root=Tk()
root.geometry('1000x1000')
fig=Figure()
# Place canvas on figure. Each frame of the animation will place a barplot on the
#canvas.
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.draw()
canvas.get_tk_widget().pack(side=TOP,fill=BOTH,expand=1)
# Random 100-by-4 matrix, each row of which corresponds to an instant in time.
M=np.random.rand(100,4)
labels=['a','b','c','d']
num_times=M.shape[0]
def update_bar(i):
ax=fig.add_subplot(111)
ax.bar(labels,list(M[i,:]))
# Want y ticks to be labelled 0, .2, .4, .6, .8 for each frame.
ax.set_yticks(np.arange(0, 1, step=0.2))
ani = Player(fig, update_bar, maxi=num_times)
root.mainloop()
When I play the animation, the y-axis labels turn out to be a mess, because the tick marks are recreated in each frame. (See image, which shows the eighth frame.)
Is there a way for me to set the y-tickmark label at all at once before the animation starts?
Upvotes: 1
Views: 165
Reputation: 12496
You should move the line:
ax = fig.add_subplot(111)
out of update_bar
function: you don't need a new ax
in each iteration.
Then, within update_bar
function you should add this line:
ax.cla()
in order to erase the previous plot.
Finally, I suggest to add this line:
ax.set_ylim(0, 1)
in the update_bar
function, in order to keep fixed y axis limits.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from tkinter import Tk, TOP, BOTH
import matplotlib
from matplotlib.animation import FuncAnimation
import mpl_toolkits.axes_grid1
root=Tk()
root.geometry('1000x1000')
fig=Figure()
# Place canvas on figure. Each frame of the animation will place a barplot on the
#canvas.
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.draw()
canvas.get_tk_widget().pack(side=TOP,fill=BOTH,expand=1)
# Random 100-by-4 matrix, each row of which corresponds to an instant in time.
M=np.random.rand(100,4)
labels=['a','b','c','d']
num_times=M.shape[0]
class Player(FuncAnimation):
def __init__(self, fig, func, frames=None, init_func=None, fargs=None,
save_count=None, mini=0, maxi=100, pos=(0.125, 0.92), **kwargs):
self.i = 0
self.min=mini
self.max=maxi
self.runs = True
self.forwards = True
self.fig = fig
self.func = func
self.setup(pos)
FuncAnimation.__init__(self,self.fig, self.func, frames=self.play(),
init_func=init_func, fargs=fargs,
save_count=save_count, **kwargs )
def play(self):
while self.runs:
self.i = self.i+self.forwards-(not self.forwards)
if self.i > self.min and self.i < self.max:
yield self.i
else:
self.stop()
yield self.i
def start(self):
self.runs=True
self.event_source.start()
def stop(self, event=None):
self.runs = False
self.event_source.stop()
def forward(self, event=None):
self.forwards = True
self.start()
def backward(self, event=None):
self.forwards = False
self.start()
def oneforward(self, event=None):
self.forwards = True
self.onestep()
def onebackward(self, event=None):
self.forwards = False
self.onestep()
def onestep(self):
if self.i > self.min and self.i < self.max:
self.i = self.i+self.forwards-(not self.forwards)
elif self.i == self.min and self.forwards:
self.i+=1
elif self.i == self.max and not self.forwards:
self.i-=1
self.func(self.i)
self.fig.canvas.draw_idle()
def setup(self, pos):
playerax = self.fig.add_axes([pos[0],pos[1], 0.22, 0.04])
divider = mpl_toolkits.axes_grid1.make_axes_locatable(playerax)
bax = divider.append_axes("right", size="80%", pad=0.05)
sax = divider.append_axes("right", size="80%", pad=0.05)
fax = divider.append_axes("right", size="80%", pad=0.05)
ofax = divider.append_axes("right", size="100%", pad=0.05)
self.button_oneback = matplotlib.widgets.Button(playerax, label=u'$\u29CF$')
self.button_back = matplotlib.widgets.Button(bax, label=u'$\u25C0$')
self.button_stop = matplotlib.widgets.Button(sax, label=u'$\u25A0$')
self.button_forward = matplotlib.widgets.Button(fax, label=u'$\u25B6$')
self.button_oneforward = matplotlib.widgets.Button(ofax, label=u'$\u29D0$')
self.button_oneback.on_clicked(self.onebackward)
self.button_back.on_clicked(self.backward)
self.button_stop.on_clicked(self.stop)
self.button_forward.on_clicked(self.forward)
self.button_oneforward.on_clicked(self.oneforward)
def update_bar(i):
ax.cla()
ax.bar(labels,list(M[i,:]))
# Want y ticks to be labelled 0, .2, .4, .6, .8 for each frame.
ax.set_yticks(np.arange(0, 1, step=0.2))
ax.set_ylim(0, 1)
ax = fig.add_subplot(111)
ani = Player(fig, update_bar, maxi=num_times)
root.mainloop()
Upvotes: 1