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?
Advertisement
Answer
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.
Complete Code
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()