matplotlib FuncAnimation into tkinter produces 2 graphs - python

I am trying to embed a matplotlib FuncAnimation live updating plot to a tkinter window but I am getting 2 plots (one inside the tkinter window and other as an extra matplotlib plot) as shown below.
But I just need one inside the tkinter window.
This is the part of the code which generates the GUI.
Main Class
from tkinter import Tk
import VisualizationPage1
class VisualizationGui(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
....other code
frame = VisualizationPage1.VisualizationPage1(self.global_container, self)
frame.grid(row=0, column=0, sticky=NSEW)
frame.tkraise()
frame.set_ui()
if __name__ == "__main__":
app = VisualizationGui()
app.mainloop()
VisualizationPage1 Class
class VisualizationPage1():
def __init__(self, parent, controller):
super().__init__(parent, controller)
# Define self.body_sublabelframe2 and other frames
plotting_function()
def plotting_function(self):
x_val = []
y_val = []
figure1 = plt.figure(figsize=(8, 8), linewidth=5, edgecolor="#04253a", dpi=35)
ax1 = figure1.add_subplot(111)
canvas_1 = FigureCanvasTkAgg(figure1, self.body_sublabelframe2)
canvas_1.get_tk_widget().grid(row=0, column=0)
ax1.legend(["Some Legend"])
ax1.set_xlabel("X Label")
ax1.set_ylabel("Y Label")
ax1.set_title("Plot Title")
def animate(i):
x_val.append(len(x_val))
y_val.append(2 ** len(y_val))
ax1.cla()
ax1.plot(x_val, y_val)
ani1 = FuncAnimation(figure1, animate, interval=100)
plt.show()
The data is just some dummy data which I need to replace with some live data which is why I need this live updating feature in matplotlib.

Related

How to correctly close a tk.Toplevel that includes a matplotlib animation chart inside

I have created a Tkinter GUI that when a button is pushed, creates a tk.Toplevel. This tk.Toplevel includes a matplotlib animated chart that is inserted as a tk widget. My problem is that when I close the tk.Toplevel and I push the button to create another tk.Toplevel, a matplotlib figure is generated apart from the desired tk.Toplevel.
I have tried using 'WM_DELETE_WINDOW' tk.Toplevel closing protocol and do:
plt.close()
tk.Toplevel.destroy()
But doing this both the main GUI and the tk.Toplevel were closed.
The next lines of code represent a simplification of what I have reached with my program:
import tkinter as tk
import matplotlib
import matplotlib.figure as mf
import matplotlib.pyplot as plt
import matplotlib.animation as ani
from matplotlib.animation import FuncAnimation
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
class SampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
container = tk.Frame(self)
container.pack()
frame = MAININTERFACE(parent=container, controller=self)
frame.grid(row=0, column=0, sticky="nsew")
def graph(self):
grafica1=GRAPHICATION(controller=self)
grafica1.Graph()
class MAININTERFACE(tk.Frame):
def __init__(self,parent,controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.button=tk.Button(self, text='GRAPH', command=lambda: self.controller.graph())
self.button.pack(pady=20)
class GRAPHICATION(tk.Frame):
def __init__(self,controller):
tk.Frame.__init__(self)
self.controller=controller
self.x=[]
self.y=[]
def animation_frame(self, i):
if i==0.0:
self.time=0
self.energy=0
else:
self.time=self.time+1
self.energy=self.energy+1
self.x.append(self.time)
self.y.append(self.energy)
self.line.set_data(self.x,self.y)
self.ax.axis([0,10,0,10])
def Graph(self):
self.graphtoplevel=tk.Toplevel(self.controller)
self.graphtoplevel.title('Toplevel')
self.fig, self.ax = plt.subplots()
self.graph=FigureCanvasTkAgg(self.fig, self.graphtoplevel)
self.image=self.graph.get_tk_widget()
plt.ion()
self.line, = self.ax.plot(self.x,self.y)
self.image.grid(row=0, column=0, sticky='nsew')
self.animation=FuncAnimation(self.fig,func=self.animation_frame,frames=np.arange(0,11,1),interval=500, repeat=False)
if __name__ == "__main__":
app = SampleApp()
app.geometry('500x200')
app.title('MAIN GUI')
app.mainloop()
Hope that my question is understood. Thank you.
This issue can be solved by deleting plt.ion() or doing plt.ioff() when closing the tk.Toplevel.

Animated Matplotlib Plot in Tkinter

I'm really struggling with an live Plot using FuncAnimation and Tkinter. I'm not sure what info is needed to reproduce the problem. Please forgive me if anything is missing.
In a stripped down version it works fine:
from numpy.random import normal
from numpy import exp
def fn(x):
A=3e-8
B=6e3
Inoise = 1e-10
if x == 0:
I = Inoise
elif A*x**2*exp(-B/x) < Inoise:
I = Inoise
else:
I = A*x**2*exp(-B/x)
return normal(1,0.4,1)[0]*I
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg,NavigationToolbar2Tk
from matplotlib.animation import FuncAnimation
from matplotlib.pyplot import Figure
from tkinter import Frame
from queue import Queue
class LivePlotWindow(Frame):
def __init__(self, master,xlabel='',ylabel='',logx=False,logy=False):
Frame.__init__(self, master)
self.pack(fill='both', expand=1)
self.fig = Figure()
self.ax = self.fig.add_subplot(111)
self.canvas = FigureCanvasTkAgg(self.fig, master=self)
self.toolbar = NavigationToolbar2Tk(self.canvas, self)
self.toolbar.update()
self.canvas.get_tk_widget().pack()
self.xlabel = xlabel
self.ylabel = ylabel
self.logx = logx
self.logy = logy
self.q = Queue()
self._axini()
def _axini(self):
self.ax.cla()
self.ax.set_xlabel(self.xlabel)
self.ax.set_ylabel(self.ylabel)
if self.logx==True:
self.ax.set_xscale('log', nonposx='clip')
if self.logy==True:
self.ax.set_yscale('log', nonposy='clip')
self.fig.tight_layout()
def _update(self,i):
print(i)
[x,y] = self.q.get(block=True)
print([x,y])
self.ax.plot(x,y,'b.') # update the data
def run(self,ncalls):
self._axini()
print(ncalls)
self.ani = FuncAnimation(self.fig, self._update, range(ncalls-1), interval=100, repeat=False)
print(ncalls)
def addP(self,x,y):
self.q.put([x,y])
def stop(self):
self.ani.event_source.stop()
from tkinter import Tk
root = Tk()
root.title('Kennlinien-Messprogramm')
root.geometry('700x500')
plot = LivePlotWindow(root,'Voltage in V','Current in A',logy=True)
n=100
plot.run(n)
for i in range(n):
plot.addP(i*10,fn(i*10))
root.mainloop()
But when I'm integrating it in a GUI with menu, its just not calling the animation function. It seems when I pass my Plot object as a command call its not working anymore? If I call the start function directly it wortks. I have no idea why:
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg,NavigationToolbar2Tk
from matplotlib.animation import FuncAnimation
from matplotlib.pyplot import Figure
from tkinter import Tk,Frame,Menu
from queue import Queue
class LivePlotWindow(Frame):
def __init__(self, master,xlabel='',ylabel='',logx=False,logy=False):
Frame.__init__(self, master)
self.pack(fill='both', expand=1)
self.fig = Figure()
self.ax = self.fig.add_subplot(111)
self.canvas = FigureCanvasTkAgg(self.fig, master=self)
self.toolbar = NavigationToolbar2Tk(self.canvas, self)
self.toolbar.update()
self.canvas.get_tk_widget().pack()
self.xlabel = xlabel
self.ylabel = ylabel
self.logx = logx
self.logy = logy
self.q = Queue()
self._axini()
def _axini(self):
self.ax.cla()
self.ax.set_xlabel(self.xlabel)
self.ax.set_ylabel(self.ylabel)
if self.logx==True:
self.ax.set_xscale('log', nonposx='clip')
if self.logy==True:
self.ax.set_yscale('log', nonposy='clip')
self.fig.tight_layout()
def _update(self,i):
print(i)
[x,y] = self.q.get(block=True)
print([x,y])
self.ax.plot(x,y,'b.') # update the data
def run(self,ncalls):
self._axini()
print(ncalls)
self.ani = FuncAnimation(self.fig, self._update, range(ncalls-1), interval=100, repeat=False)
print(ncalls)
def addP(self,x,y):
self.q.put([x,y])
def stop(self):
self.ani.event_source.stop()
from tkinter import Tk
root = Tk()
root.title('Kennlinien-Messprogramm')
root.geometry('700x500')
plot = LivePlotWindow(root,'Voltage in V','Current in A',logy=True)
def start(menu,plot):
menu.entryconfig('\u25B6', state='disabled')
menu.entryconfig('\u25A0', state='normal')
n=100
plot.run(n)
for i in range(n):
plot.addP(i*10,fn(i*10))
def stop(menu,plot):
plot.stop
menu.entryconfig('\u25B6', state='normal')
menu.entryconfig('\u25A0', state='disabled')
menu = Menu(root)
root.config(menu=menu)
menu.add_command(label='\u25B6', command=lambda: start(menu,plot))
#menu.entryconfig(1, fg='green')
menu.add_command(label='\u25A0', command=lambda: stop(menu,plot))
menu.entryconfig('\u25A0', state='disabled')
#start(menu,plot)
root.mainloop()
Any help would be highly appreciated! ;)
I've found a solution, although I don't know how smart it is... Now I just continously plot xdata and ydata. Since I pass the reference, its updating any changes outside the plot class. However, I redraw the hole plot every time...
from tkinter import Frame
from matplotlib.pyplot import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.animation import FuncAnimation
class LivePlotWindow(Frame):
def __init__(self, master, xdata, ydata, xlabel='',ylabel='',logx=False,logy=False):
Frame.__init__(self, master)
self.pack(fill='both', expand=1)
self.fig = Figure()
self.ax = self.fig.add_subplot(111)
self.ax.set_xlabel(xlabel)
self.canvas = FigureCanvasTkAgg(self.fig, master=self)
self.canvas.get_tk_widget().pack()
self.xdata = xdata
self.ydata = ydata
self.xlabel = xlabel
self.ylabel = ylabel
self.logx = logx
self.logy = logy
self.ani = FuncAnimation(self.fig, self._update, interval=500)
def _update(self,i):
self.ax.cla()
self.ax.set_xlabel(self.xlabel)
self.ax.set_ylabel(self.ylabel)
if self.logx:
self.ax.set_xscale('log', nonposx='clip')
if self.logy:
self.ax.set_yscale('log', nonposy='clip')
#if self.millikan:
# self.ax.plot(1/self.xdata,self.ydata)
#else:
self.ax.plot(self.xdata,self.ydata,'b.') # update the data
self.fig.tight_layout()

Tkinter plt.figure() does not plot, but Figure() does

I started building a Tkinter application and was initially using matplotlib's Figure and figure.add_subplot. With that everything works perfectly. For more customization, I now want to move to pyplot and subplot2grid, but in doing so, suddenly all of my tkinter variable stop working.
In my MWE, the variable gArrChoice tracks which radio button is selected and should default to the first option. Based on this option, the graph should plot a line hovering around 0.1. If the second option gets selected, the graph should change to hover around 5. The graph auto-updates ever 2.5 seconds. If you comment out the 3 lines below "Working" and use the 3 "Not Working" lines instead, the default settings of the variable stops working and switching between radio buttons has no effect anymore. Declaring a inside the animate function does not change the problem.
How can I use plt with Tkinter and not destroy my variables?
MWE:
import tkinter as tk
import matplotlib
matplotlib.use("TkAgg") #make sure you use the tkinter backend
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.animation as animation
import numpy as np
gArrChoice = 0
#Working - using Figure and add_subplot
from matplotlib.figure import Figure
f = Figure()
a = f.add_subplot(121)
#Not Working - using plt and subplot2grid
# from matplotlib import pyplot as plt
# f = plt.figure()
# a = plt.subplot2grid((10, 7), (0, 0), rowspan=10, colspan=5)
class BatSimGUI(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
self.frames = {}
frame = StartPage(container,self)
self.frames[StartPage] = frame
frame.grid(row=0, column=0, sticky="nsew")
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
#Set defaults for global variable
global gArrChoice
gArrChoice = tk.IntVar()
gArrChoice.set(1)
radioArr1 = tk.Radiobutton(self, variable=gArrChoice, text="Exponential", value=1, command= lambda: print(gArrChoice.get()))
radioArr1.grid(row=2, column=0)
radioArr2 = tk.Radiobutton(self, variable=gArrChoice, text="Normal", value=2, command= lambda: print(gArrChoice.get()))
radioArr2.grid(row=3, column=0)
#Add Canvas
canvas = FigureCanvasTkAgg(f, self)
canvas.draw()
canvas.get_tk_widget().grid(row=1, column=1, columnspan=7, rowspan = 10)
def animate(i):
global gArrChoice
if gArrChoice.get() == 1:
lam = np.random.exponential(scale=.1, size = 100).reshape(-1,1)
else:
lam = np.random.normal(loc=5, scale=1, size = 100).reshape(-1,1)
a.clear()
a.step(list(range(100)), list(lam))
#Actually run the interface
app = BatSimGUI()
app.geometry("800x600")
ani = animation.FuncAnimation(f, animate, interval = 2500)
app.mainloop()
I'think that an OO approach it'would be better.
See below, I've use thread and queue to manage the plot animation, you can even set time interval and change on fly the graph type
Good job anyway, very interesting
#!/usr/bin/python3
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
import threading
import queue
import time
from matplotlib.figure import Figure
import matplotlib.animation as animation
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
try:
from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk as nav_tool
except:
from matplotlib.backends.backend_tkagg import NavigationToolbar2TkAgg as nav_tool
import numpy as np
class MyThread(threading.Thread):
def __init__(self, queue, which, ops, interval):
threading.Thread.__init__(self)
self.queue = queue
self.check = True
self.which = which
self.ops = ops
self.interval = interval
def stop(self):
self.check = False
def run(self):
while self.check:
if self.which.get() ==0:
lam = np.random.exponential(scale=.1, size = 100).reshape(-1,1)
else:
lam = np.random.normal(loc=5, scale=1, size = 100).reshape(-1,1)
time.sleep(self.interval.get())
args = (lam, self.ops[self.which.get()])
self.queue.put(args)
else:
args = (None, "I'm stopped")
self.queue.put(args)
class Main(ttk.Frame):
def __init__(self, parent):
super().__init__()
self.parent = parent
self.which = tk.IntVar()
self.interval = tk.DoubleVar()
self.queue = queue.Queue()
self.my_thread = None
self.init_ui()
def init_ui(self):
f = ttk.Frame()
#create graph!
self.fig = Figure()
self.fig.suptitle("Hello Matplotlib", fontsize=16)
self.a = self.fig.add_subplot(111)
self.canvas = FigureCanvasTkAgg(self.fig, f)
toolbar = nav_tool(self.canvas, f)
toolbar.update()
self.canvas._tkcanvas.pack(fill=tk.BOTH, expand=1)
w = ttk.Frame()
ttk.Button(w, text="Animate", command=self.launch_thread).pack()
ttk.Button(w, text="Stop", command=self.stop_thread).pack()
ttk.Button(w, text="Close", command=self.on_close).pack()
self.ops = ('Exponential','Normal',)
self.get_radio_buttons(w,'Choice', self.ops, self.which,self.on_choice_plot).pack(side=tk.TOP, fill=tk.Y, expand=0)
ttk.Label(w, text = "Interval").pack()
tk.Spinbox(w,
bg='white',
from_=1.0, to=5.0,increment=0.5,
justify=tk.CENTER,
width=8,
wrap=False,
insertwidth=1,
textvariable=self.interval).pack(anchor=tk.CENTER)
w.pack(side=tk.RIGHT, fill=tk.BOTH, expand=1)
f.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
def launch_thread(self):
self.on_choice_plot()
def stop_thread(self):
if self.my_thread is not None:
if(threading.active_count()!=0):
self.my_thread.stop()
def on_choice_plot(self, evt=None):
if self.my_thread is not None:
if (threading.active_count()!=0):
self.my_thread.stop()
self.my_thread = MyThread(self.queue,self.which, self.ops, self.interval)
self.my_thread.start()
self.periodiccall()
def periodiccall(self):
self.checkqueue()
if self.my_thread.is_alive():
self.after(1, self.periodiccall)
else:
pass
def checkqueue(self):
while self.queue.qsize():
try:
args = self.queue.get()
self.a.clear()
self.a.grid(True)
if args[0] is not None:
self.a.step(list(range(100)), list(args[0]))
self.a.set_title(args[1], weight='bold',loc='left')
else:
self.a.set_title(args[1], weight='bold',loc='left')
self.canvas.draw()
except queue.Empty:
pass
def get_radio_buttons(self, container, text, ops, v, callback=None):
w = ttk.LabelFrame(container, text=text,)
for index, text in enumerate(ops):
ttk.Radiobutton(w,
text=text,
variable=v,
command=callback,
value=index,).pack(anchor=tk.W)
return w
def on_close(self):
if self.my_thread is not None:
if(threading.active_count()!=0):
self.my_thread.stop()
self.parent.on_exit()
class App(tk.Tk):
"""Start here"""
def __init__(self):
super().__init__()
self.protocol("WM_DELETE_WINDOW", self.on_exit)
self.set_title()
self.set_style()
Main(self)
def set_style(self):
self.style = ttk.Style()
#('winnative', 'clam', 'alt', 'default', 'classic', 'vista', 'xpnative')
self.style.theme_use("clam")
def set_title(self):
s = "{0}".format('Simple App')
self.title(s)
def on_exit(self):
"""Close all"""
if messagebox.askokcancel("Simple App", "Do you want to quit?", parent=self):
self.destroy()
if __name__ == '__main__':
app = App()
app.mainloop()
There seems to be a bug on updating the IntVar() when you use pyplot instead. But you can workaround it if you force a change in value in your radio buttons:
radioArr1 = tk.Radiobutton(self, variable=gArrChoice, text="Exponential", value=1, command= lambda: gArrChoice.set(1))
radioArr2 = tk.Radiobutton(self, variable=gArrChoice, text="Normal", value=2, command= lambda: gArrChoice.set(2))
Or you can make your IntVar as an attribute of StartPage instead which seems to work just fine.
import tkinter as tk
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.animation as animation
import numpy as np
from matplotlib import pyplot as plt
class BatSimGUI(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
self.frames = {}
self.start_page = StartPage(container,self)
self.frames[StartPage] = self.start_page
self.start_page.grid(row=0, column=0, sticky="nsew")
self.start_page.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.gArrChoice = tk.IntVar()
self.gArrChoice.set(1)
radioArr1 = tk.Radiobutton(self, variable=self.gArrChoice, text="Exponential", value=1)
radioArr1.grid(row=2, column=0)
radioArr2 = tk.Radiobutton(self, variable=self.gArrChoice, text="Normal", value=2)
radioArr2.grid(row=3, column=0)
self.f = plt.figure()
self.a = plt.subplot2grid((10, 7), (0, 0), rowspan=10, colspan=5)
canvas = FigureCanvasTkAgg(self.f, self)
canvas.draw()
canvas.get_tk_widget().grid(row=1, column=1, columnspan=7, rowspan = 10)
def animate(self,i):
if self.gArrChoice.get() == 1:
lam = np.random.exponential(scale=.1, size = 100).reshape(-1,1)
else:
lam = np.random.normal(loc=5, scale=1, size = 100).reshape(-1,1)
self.a.clear()
self.a.step(list(range(100)), list(lam))
app = BatSimGUI()
app.geometry("800x600")
ani = animation.FuncAnimation(app.start_page.f, app.start_page.animate, interval=1000)
app.mainloop()
It seems the problem is to replace
# Not Working - using plt and subplot2grid
from matplotlib import pyplot as plt
f = plt.figure()
a = plt.subplot2grid((10, 7), (0, 0), rowspan=10, colspan=5)
in a pyplot- independent fashion. One option is the use of gridspec:
from matplotlib.figure import Figure
f = Figure()
gs = f.add_gridspec(10,7)
a = f.add_subplot(gs[:, :5])

Putting an continuously updating animated plot with other components inside a tkinter gui

I want to show a plot within one of the pages in my GUI. The data plotted, theData will constantly change. Whenever theData is changed, the value of valueChangedtheData is written to 1. The mapping of time and data is here:
xsize=100
xdata,ydata = [],[]
def data_gen():
t = data_gen.t
global theData
global valueChangedtheData
while True:
if (valueChangedtheData == 1):
valueChangedtheData = 0;
t+=0.1
val=float(theData);
if val>1000:
continue
yield t, val
else: pass
def animate(data):
t, val = data
if t>-1:
xdata.append(t)
ydata.append(val)
if t>xsize: # Scroll to the left.
a.set_xlim(t-xsize, t)
line.set_data(xdata, ydata)
return line,
def on_close_figure(event):
sys.exit(0)
data_gen.t = -1
f = plt.figure()
f.canvas.mpl_connect('close_event', on_close_figure)
#f = Figure(figsize=(5,5), dpi=100)
a = f.add_subplot(111)
line, = a.plot([], [], lw=2)
a.set_ylim(0, 250)
a.set_xlim(0, xsize)
a.grid()
I've defined my the container of my GUI as such:
class Gui(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
container = Frame(self)
container.pack(side="top", fill = "both", expand = TRUE)
container.grid_rowconfigure(0, weight = 1)
global theData;
self.MyReading = StringVar()
self.frames={}
for F in (StartPage, PageOne):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row = 0, column = 0, sticky = "nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
and the page showing the plot:
class StartPage(Frame): #The Graphical Page
def __init__(self, parent, controller):
Frame.__init__(self,parent)
label = Label(self, text="StartPage")
label.pack()
label1 = Label(self, textvariable = controller.theData)
label1.pack
canvas = FigureCanvasTkAgg(f, self)
canvas.draw() #changed from show to draw
canvas.get_tk_widget().pack(side=BOTTOM, fill=BOTH, expand=True)
toolbar = NavigationToolbar2Tk(canvas, self)
toolbar.update()
canvas._tkcanvas.pack(side=TOP, fill=BOTH, expand=True)
And in order to start the animation and Gui:
root = Gui()
update_reading()
ani = animation.FuncAnimation(f, animate, data_gen, blit = False, interval=100, repeat = False)
root.mainloop()
With update_reading() updating the label:
def update_reading():
global theData
global valueChangedtheData
theData = randint(1,20) #This is just an example of the changing value
print(theData)
valueChangedtheData = 1;
root.MyReading.set(str(theData));
root.after(100,update_reading)
However, after adding the canvas on the page, all of the labels that rely on the variable classes would refuse to shop-up, including the value for theData but the plot is graphing. Also, labels that show images would also refuse to show-up. After commenting the canvas, data mapping and animation, the label would appear back. Am I missing an important initializing code? Also, during plotting, there is a considerable "sluggishness" happening in the gui window. Could this be alleviated through a better code writing? Thanks
Imports:
from tkinter import *
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
import matplotlib.animation as animation
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import style
from random import randint
EDIT: Added missed and necessary code
EDIT2: Included all the imports

Embedding a matplotlib animation into a tkinter frame

For a project I am working on a simple harmonic motion simulator (How a mass oscillates over time). I have got the data produced correctly and already have a graph produced within a tkinter frame work. At the moment it only shows a static graph where my objective is to display the graph as an animation over time.
So for ease sake I have created a mock up of the programme using the following code:
#---------Imports
from numpy import arange, sin, pi
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import tkinter as Tk
from tkinter import ttk
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
#---------End of imports
fig, ax = plt.subplots()
x = np.arange(0, 2*np.pi, 0.01) # x-array
line, = ax.plot(x, np.sin(x))
def animate(i):
line.set_ydata(np.sin(x+i/10.0)) # update the data
return line,
ani = animation.FuncAnimation(fig, animate, np.arange(1, 200), interval=25, blit=False)
#plt.show() #What I want the object in tkinter to appear as
root = Tk.Tk()
label = ttk.Label(root,text="SHM Simulation").grid(column=0, row=0)
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.show()
canvas.get_tk_widget().grid(column=0,row=1)
Tk.mainloop()
This code will display the animation that I want in the tkinter frame work when the plt.show() is uncommented. I would like to be able to place that animation within the framework of tkinter.
I have also been on the matplotlib website and viewed all of the animation examples and none of them have helped. I have also looked on Embedding an animated matplotlib in tk and that has placed the tkinter button within pyplot figure, whereas I would like to place the figure within a tkinter frame.
So just to clarify, I would like to be able to place the animation produced when plt.show() is uncommented in a tkinter frame, ie root = tk().
I modified your code:
#---------Imports
from numpy import arange, sin, pi
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import tkinter as Tk
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
#---------End of imports
fig = plt.Figure()
x = np.arange(0, 2*np.pi, 0.01) # x-array
def animate(i):
line.set_ydata(np.sin(x+i/10.0)) # update the data
return line,
root = Tk.Tk()
label = Tk.Label(root,text="SHM Simulation").grid(column=0, row=0)
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.get_tk_widget().grid(column=0,row=1)
ax = fig.add_subplot(111)
line, = ax.plot(x, np.sin(x))
ani = animation.FuncAnimation(fig, animate, np.arange(1, 200), interval=25, blit=False)
Tk.mainloop()
Based on the answer of user151522 that didnt work for me at the first try, i made a few modifications to work in python 3.7:
#---------Imports
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tkinter as tk
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
#---------End of imports
from tkinter import Frame,Label,Entry,Button
class Window(Frame):
def __init__(self, master = None):
Frame.__init__(self, master)
self.master = master
self.init_window()
def Clear(self):
print("clear")
self.textAmplitude.insert(0, "1.0")
self.textSpeed.insert(0, "1.0")
def Plot(self):
self.v = float(self.textSpeed.get())
self.A = float(self.textAmplitude.get())
def animate(self,i):
self.line.set_ydata(self.A*np.sin(self.x+self.v*i)) # update the data
return self.line,
def init_window(self):
self.master.title("Use Of FuncAnimation in tkinter based GUI")
self.pack(fill='both', expand=1)
#Create the controls, note use of grid
self.labelSpeed = Label(self,text="Speed (km/Hr)",width=12)
self.labelSpeed.grid(row=0,column=1)
self.labelAmplitude = Label(self,text="Amplitude",width=12)
self.labelAmplitude.grid(row=0,column=2)
self.textSpeed = Entry(self,width=12)
self.textSpeed.grid(row=1,column=1)
self.textAmplitude = Entry(self,width=12)
self.textAmplitude.grid(row=1,column=2)
self.textAmplitude.insert(0, "1.0")
self.textSpeed.insert(0, "1.0")
self.v = 1.0
self.A = 1.0
self.buttonPlot = Button(self,text="Plot",command=self.Plot,width=12)
self.buttonPlot.grid(row=2,column=1)
self.buttonClear = Button(self,text="Clear",command=self.Clear,width=12)
self.buttonClear.grid(row=2,column=2)
self.buttonClear.bind(lambda e:self.Clear)
tk.Label(self,text="SHM Simulation").grid(column=0, row=3)
self.fig = plt.Figure()
self.x = 20*np.arange(0, 2*np.pi, 0.01) # x-array
self.ax = self.fig.add_subplot(111)
self.line, = self.ax.plot(self.x, np.sin(self.x))
self.canvas = FigureCanvasTkAgg(self.fig, master=self)
self.canvas.get_tk_widget().grid(column=0,row=4)
self.ani = animation.FuncAnimation(self.fig, self.animate, np.arange(1, 200), interval=25, blit=False)
root = tk.Tk()
root.geometry("700x400")
app = Window(root)
tk.mainloop()
This answer will hopefully be allowed. It is an answer to what I was actually interested in, when I initially found this question, that is, 'Embedding a Matplotlib animation into a tkinter based GUI'.
The code that gave the previous screenshot has been extended, in this code the canvas has been placed inside a class definition, together with some code for two command buttons, these buttons don't actually do "anything" but the structure is there for possible further development.
The following screenshot was produced with the aid of the extended code
A screenshot of the SHM animation running from within a tkinter based GUI
The extended code used for the above screenshot is given below.
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tkinter as tk
from tkinter import Frame,Label,Entry,Button
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
class Window(Frame):
def __init__(self, master = None):
Frame.__init__(self, master)
self.master = master
self.init_window()
def Clear(self):
x=0
# def Plot(self):
# x=0
def init_window(self):
def animate(i):
self.line.set_ydata(np.sin(self.x+i/10.0)) # update the data
return self.line,
self.master.title("Use Of FuncAnimation in tkinter based GUI")
self.pack(fill='both', expand=1)
#Create the controls, note use of grid
self.labelSpeed = Label(self,text="Speed (km/Hr)",width=12)
self.labelSpeed.grid(row=0,column=1)
self.labelAmplitude = Label(self,text="Amplitude",width=12)
self.labelAmplitude.grid(row=0,column=2)
self.textSpeed = Entry(self,width=12)
self.textSpeed.grid(row=1,column=1)
self.textAmplitude = Entry(self,width=12)
self.textAmplitude.grid(row=1,column=2)
# self.buttonPlot = Button(self,text="Plot",command=self.Plot,width=12)
self.buttonPlot = Button(self,text="Plot",width=12)
self.buttonPlot.grid(row=2,column=1)
self.buttonClear = Button(self,text="Clear",command=self.Clear,width=12)
self.buttonClear.grid(row=2,column=2)
# self.buttonClear.bind(lambda e:self.Plot)
self.buttonClear.bind(lambda e:self.Clear)
tk.Label(self,text="SHM Simulation").grid(column=0, row=3)
self.fig = plt.Figure()
self.x = np.arange(0, 2*np.pi, 0.01) # x-array
self.ax = self.fig.add_subplot(111)
self.line, = self.ax.plot(self.x, np.sin(self.x))
self.canvas = FigureCanvasTkAgg(self.fig, master=self)
self.canvas.get_tk_widget().grid(column=0,row=4)
self.ani = animation.FuncAnimation(self.fig, animate, np.arange(1, 200), interval=25, blit=False)
root = tk.Tk()
root.geometry("700x400")
app = Window(root)
tk.mainloop()

Categories