I am trying to get two TKinter 'pages' each one with a different plot updated using the matplotlib animation function.
The problem is only one of the pages show correctly the plot.
Here is the code:
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import matplotlib.animation as animation
import Tkinter as tk
import ttk
a = Figure(figsize=(4,4))
plot_a = a.add_subplot(111)
b = Figure(figsize=(4,4))
plot_b = b.add_subplot(111)
x = [1,2,3,4,5]
y_a = [1,4,9,16,25]
y_b = [25,16,9,4,1]
def updateGraphs(i):
plot_a.clear()
plot_a.plot(x,y_a)
plot_b.clear()
plot_b.plot(x,y_b)
class TransientAnalysis(tk.Tk):
def __init__(self,*args,**kwargs):
tk.Tk.__init__(self,*args,**kwargs)
tk.Tk.wm_title(self, "Transient Analysis GUI: v1.0")
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in ( GraphPageA, GraphPageB):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(GraphPageA)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class GraphPageA(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
button1 = ttk.Button(self, text="Show Graph B",
command = lambda: controller.show_frame(GraphPageB))
button1.grid(row=1, column=0,pady=20,padx=10, sticky='w')
canvasA = FigureCanvasTkAgg(a, self)
canvasA.show()
canvasA.get_tk_widget().grid(row=1, column=1, pady=20,padx=10, sticky='nsew')
class GraphPageB(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
button1 = ttk.Button(self, text="Show Graph A",
command = lambda: controller.show_frame(GraphPageA))
button1.grid(row=1, column=0,pady=20,padx=10, sticky='w')
canvasB = FigureCanvasTkAgg(b, self)
canvasB.show()
canvasB.get_tk_widget().grid(row=1, column=1, pady=20,padx=10, sticky='nsew')
app = TransientAnalysis()
app.geometry("800x600")
aniA = animation.FuncAnimation(a, updateGraphs, interval=1000)
aniB = animation.FuncAnimation(b, updateGraphs, interval=1000)
app.mainloop()
To be more specific, the page that show correctly the plot is the last on called in the for loop for F in ( GraphPageA, GraphPageB):
I also tried using different updateGraphs function, one to each plot, but the result is the same.
How can I have two TKinter pages plotting these two different graphs? I am using Python 2.7.
from __future__ import print_function
import matplotlib
import numpy as np
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import matplotlib.animation as animation
import Tkinter as tk
import ttk
a = Figure(figsize=(4, 4))
plot_a = a.add_subplot(111)
plot_a.set_xlim([0, 2*np.pi])
plot_a.set_ylim([-1, 1])
lna, = plot_a.plot([], [], color='orange', lw=5)
b = Figure(figsize=(4, 4))
plot_b = b.add_subplot(111)
plot_b.set_xlim([0, 2*np.pi])
plot_b.set_ylim([-1, 1])
lnb, = plot_b.plot([], [], color='olive', lw=5)
x = np.linspace(0, 2*np.pi, 1024)
def updateGraphsA(i):
lna.set_xdata(x)
lna.set_ydata(np.sin(x + i * np.pi / 10))
print('in A')
def updateGraphsB(i):
lnb.set_xdata(x)
lnb.set_ydata(np.sin(x - i * np.pi / 10))
print('in B')
class TransientAnalysis(tk.Tk):
def __init__(self, *args, **kwargs):
self._running_anim = None
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self, "Transient Analysis GUI: v1.0")
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (GraphPageA, GraphPageB):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(GraphPageA)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
frame.canvas.draw_idle()
class GraphPageA(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
button1 = ttk.Button(self, text="Show Graph B",
command=(
lambda: controller.show_frame(GraphPageB)))
button1.grid(row=1, column=0, pady=20, padx=10, sticky='w')
canvasA = FigureCanvasTkAgg(a, self)
canvasA.show()
canvasA.get_tk_widget().grid(
row=1, column=1, pady=20, padx=10, sticky='nsew')
self.canvas = canvasA
class GraphPageB(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
button1 = ttk.Button(self, text="Show Graph A",
command=(
lambda: controller.show_frame(GraphPageA)))
button1.grid(row=1, column=0, pady=20, padx=10, sticky='w')
canvasB = FigureCanvasTkAgg(b, self)
canvasB.show()
canvasB.get_tk_widget().grid(
row=1, column=1, pady=20, padx=10, sticky='nsew')
self.canvas = canvasB
app = TransientAnalysis()
app.geometry("800x600")
aniA = animation.FuncAnimation(a, updateGraphsA, interval=1000, blit=False)
aniB = animation.FuncAnimation(b, updateGraphsB, interval=1000, blit=False)
app.mainloop()
The problem was that the animation is kicked off the first time that the figure is drawn. For what ever reason (which is going to be some nasty internal detail of either Tk or mpl) the draw event event for the non-initially visible axes never gets fired (or is fired before the animation is created) so the second animation never even gets started. By adding a canvas
attribute to your classes you can explicitly trigger the draw_idle
every time you switch the graph being shown.
Note that I also:
On a more general level, you should probably refactor your GraphPage*
classes to be one parameterized class which is responsible for :
For example:
from __future__ import print_function
import matplotlib
import numpy as np
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import matplotlib.animation as animation
import Tkinter as tk
import ttk
x = np.linspace(0, 2*np.pi, 1024)
class TransientAnalysis(tk.Tk):
pages = ((1, 'Switch to "-"', '-', '+', 'orange'),
(-1, 'Switch to "+"', '+', '-', 'olive'))
def __init__(self, *args, **kwargs):
self._running_anim = None
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self, "Transient Analysis GUI: v1.0")
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for (direction, text, other_key, my_key, color) in self.pages:
frame = MovingSinGraphPage(direction, text, other_key,
my_key, color,
container, self)
self.frames[my_key] = frame
frame.grid(row=0, column=0, sticky="nsew")
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
frame.canvas.draw_idle()
class MovingSinGraphPage(tk.Frame):
def __init__(self, move_dir, text, other_key, my_key,
color, parent, controller):
self._sgn = np.sign(move_dir)
tk.Frame.__init__(self, parent)
button1 = ttk.Button(self, text=text,
command=(
lambda: controller.show_frame(other_key)))
button1.grid(row=1, column=0, pady=20, padx=10, sticky='w')
# make mpl objects
a = Figure(figsize=(4, 4))
plot_a = a.add_subplot(111)
# set up the axes limits and title
plot_a.set_title(my_key)
plot_a.set_xlim([0, 2*np.pi])
plot_a.set_ylim([-1, 1])
# make and stash the plot artist
lna, = plot_a.plot([], [], color=color, lw=5)
self._line = lna
# make the canvas to integrate with Tk
canvasA = FigureCanvasTkAgg(a, self)
canvasA.show()
canvasA.get_tk_widget().grid(
row=1, column=1, pady=20, padx=10, sticky='nsew')
# stash the canvas so that we can use it above to ensure a re-draw
# when we switch to this page
self.canvas = canvasA
# create and save the animation
self.anim = animation.FuncAnimation(a, self.update,
interval=100)
def update(self, i):
self._line.set_xdata(x)
self._line.set_ydata(np.sin(x + self._sgn * i * np.pi / 10))
app = TransientAnalysis()
app.geometry("800x600")
app.mainloop()
This apparently captured my attention today..