Animate Matplotlib graph inside Toplevel in Tkinter - python

I'm new to tkinter, so if there's anything very wrong, I'm sorry. That said...
I'm using python 3.
The title is pretty self-explanatory. I'm trying to animate a graph inside my tk.Toplevel in my application. I've deleted all remaining code to simplify the question. This code doesn't work even if I put animation.FuncAnimation just before root.mainloop().
Here's my code:
import tkinter as tk
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.animation as animation
def running():
#Animate the graph
def animate(i):
global df1
df1 = pd.concat([df1,pd.DataFrame({'Country':[None],'GDP_Per_Capita':[df1['GDP_Per_Capita'].iloc[-1]+10]})],sort=False)
ax1.clear()
ax1.hist(df1['GDP_Per_Capita'], len(df1), density=1, histtype='step',
cumulative=True, label='Empirical')
global df1
t = tk.Toplevel()
t.wm_title("Running")
figure1 = plt.Figure(figsize=(6,5), dpi=100)
ax1 = figure1.add_subplot(111)
bar1 = FigureCanvasTkAgg(figure1, t)
bar1.get_tk_widget().grid(column=0,row=0,columnspan=2)
n, bins, patches = ax1.hist(df1['GDP_Per_Capita'], 5, density=1, histtype='step',
cumulative=True, label='Empirical')
ax1.set_title('Country Vs. GDP Per Capita')
ani = animation.FuncAnimation(figure1, animate, interval=1000)
if __name__ == "__main__":
root = tk.Tk()
root.title("Testing")
#Sample Data
Data1 = {'Country': ['US','CA','GER','UK','FR'],
'GDP_Per_Capita': [10,15,25,45,50]
}
df1 = pd.DataFrame(Data1, columns= ['Country', 'GDP_Per_Capita'])
df1 = df1[['Country', 'GDP_Per_Capita']].groupby('Country').sum()
tk.Button(root,
text='START', command=running, width=9, height=2, activebackground="white", bg="#006d88",bd=4).grid(row=0, column=1, sticky="E",padx=(0,10))
root.mainloop()
This question is not the same as this Python Tkinter Animation since I'm running the animation inside Toplevel. If I was running in root, it would work.

Related

Im trying to add a matplotlib chart on a Tkinter GUI... Can someone help me?

I have this code here where I am trying to import a seaborn ecdf plot on a Tkinter GUI but it is not working. The plot just doesnt pop up when I run the program. Ultimatly, I would like to make the plot updating with real-time data. Is it possible and if yes, how would I go about doing this. Thank you for your time.
from tkinter import *
import datetime as dt
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import seaborn as sns
from data import GetData
"""Initiate Root"""
Root = Tk()
Root.title('Profondeur du marché')
Root.iconphoto(False, PhotoImage(file = 'Logo.png'))
Root.configure(background = '#2E2D2B')
Root.geometry('1000x800')
"""Define df"""
df = GetData()
"""Create Plot"""
fig, ax = plt.subplots()
ax.set_title('Le livre de liquidité du Bitcoin en temps réel')
"""Bids"""
BidsPlot = sns.ecdfplot(x="Price",
weights="Volume",
stat="count",
complementary=True,
data=df.query("Side == 'bids'"),
color="green",
ax=ax)
"""Asks"""
AsksPlot = sns.ecdfplot(x="Price",
weights="Volume",
stat="count",
data=df.query("Side == 'asks'"),
color="red",
ax=ax)
"""Set axes titles"""
ax.set_xlabel('Prix')
ax.set_ylabel('Volume')
"""Initiate Canvas"""
Canvas = FigureCanvasTkAgg(fig,
master = Root)
Canvas.draw()
Root.mainloop()

Deleting matplotlib plot /tkinter canvas from window

I was wondering how you can completely delete a plot from a tkinter window.
Assuming i would have a tkinter project like the following:
import tkinter as tk
from pandas import DataFrame
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
data1 = {'Country': ['US','CA','GER','UK','FR'],
'GDP_Per_Capita': [45000,42000,52000,49000,47000]
}
df1 = DataFrame(data1,columns=['Country','GDP_Per_Capita'])
root= tk.Tk()
figure1 = plt.Figure(figsize=(6,5), dpi=100)
ax1 = figure1.add_subplot(111)
bar1 = FigureCanvasTkAgg(figure1, root)
bar1.get_tk_widget().pack(side=tk.LEFT, fill=tk.BOTH)
df1 = df1[['Country','GDP_Per_Capita']].groupby('Country').sum()
df1.plot(kind='bar', legend=True, ax=ax1)
ax1.set_title('Country Vs. GDP Per Capita')
def close_plot():
plt.close(figure1)
button_delete = Button(root, text='Delete', command = lambda: close_plot()).place(height=30, width = 100, rely=0.02, relx = 0.4)
root.mainloop()
I veen trying to use matplotlib.pyplot.close within a button but it doens't seem to work.
Anyone have a clue how to get rid of this plot.
Thank you very much!
Your figure is embedded in a tkinter widget. You need to keep a reference to that widget, and use its methods to add/remove it from the tkinter window:
Here we use pack_forget(): the widget is removed from the window, but still exists, and could be reused later.
import tkinter as tk
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
def remove_plot():
w.pack_forget() # here you remove the widget from the tk window
# w.destroy()
if __name__ == '__main__':
# data
x, y = [1, 2, 3, 4], [1, 4, 9, 16]
# matplotlib stuff
figure1 = plt.Figure(figsize=(6, 5), dpi=100)
ax1 = figure1.add_subplot(111)
ax1.plot(x, y)
ax1.set_title('Country Vs. GDP Per Capita')
# tkinter stuff
root = tk.Tk()
bar1 = FigureCanvasTkAgg(figure1, root)
w = bar1.get_tk_widget()
w.pack(side=tk.LEFT, fill=tk.BOTH) # here you insert the widget in the tk window
button_delete = tk.Button(root, text='Remove Plot', command=remove_plot)
button_delete.place(height=30, width=100, rely=0.02, relx=0.4) # place is an odd choice of geometry manager, you will have to adjust it every time the title changes
root.mainloop()
Alternatively, if you don't need that figure any longer, you could use w.destroy (commented line) to destroy the widget.

Tkinter with matplotlib - Heatmap colorbar not clearing with rest of axes

I'm building a GUI using tkinter and matplotlib (and seaborn) to show a heatmap from a user chosen csv. I want the heatmap to update each time it's loaded, with the appropriate colorbar. I clear my axes each time I load new data in, but the colorbar never goes away, and the new heatmap squishes off to the side. I want the old colorbar to be cleared as well so the new heatmap can fill the space properly.
I made a MWE to show off my problem:
import numpy as np
import tkinter as tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import seaborn as sns
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.state('zoomed')
self.winfo_toplevel().title('App')
frame = tk.Frame(self)
frame.pack()
button_reload = tk.Button(frame, text='Reload data', command=self.reload_data)
button_reload.pack()
self.fig = Figure()
self.ax = self.fig.add_subplot(111)
self.fig.tight_layout() # Small margins
self.ax.axis('off') # Disable axis lines
self.canvas_heatmap = FigureCanvasTkAgg(self.fig, master=frame)
self.canvas_heatmap.get_tk_widget().pack(expand=True, fill='both')
def reload_data(self):
# dummy data for example
data = np.random.rand(3,3)
# Clear old heatmap from axes
self.ax.clear()
# Set up new heatmap
self.ax = sns.heatmap(data, ax=self.ax, linewidth=0.1)
self.canvas_heatmap.draw()
self.canvas_heatmap.get_tk_widget().pack(expand=True, fill='both') # necessary?
def quit_GUI():
root.quit()
root.destroy()
if __name__ == '__main__':
root = App()
root.protocol('WM_DELETE_WINDOW', quit_GUI) # Kill process on clicking 'X'
root.mainloop()
Here are some photos where you can see the colorbars sticking around when I don't want them to.
GOOD SO FAR:
BAD:
WORSE:
I could keep going like this until my heatmap is a sliver.
You need to clear the figure and remake the ax.
import numpy as np
import tkinter as tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import seaborn as sns
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.state('zoomed')
self.winfo_toplevel().title('App')
frame = tk.Frame(self)
frame.pack()
button_reload = tk.Button(frame, text='Reload data', command=self.reload_data)
button_reload.pack()
self.fig = Figure()
self.canvas_heatmap = FigureCanvasTkAgg(self.fig, master=frame)
self.canvas_heatmap.get_tk_widget().pack(expand=True, fill='both')
def reload_data(self):
data = np.random.rand(3,3)
self.fig.clear()
ax = self.fig.add_subplot(111)
ax.axis('off') # Disable axis lines
line = sns.heatmap(data, ax=ax, linewidth=0.1)
self.fig.tight_layout() # Should go after the drawing
self.canvas_heatmap.draw()
# ~ self.canvas_heatmap.get_tk_widget().pack(expand=True, fill='both') # not necessary
if __name__ == '__main__':
root = App()
# ~ root.protocol('WM_DELETE_WINDOW', quit_GUI) # not needed
root.mainloop()

Is it possible to run certain code even when tkinter window is closed in python?

I have an embedded live matplotlib graph that updates every minute in a tkinter window. I want the graph to continue to update even when the window is closed. Also, when a person clicks a button, it will open the same tkinter window in it's updated state. Here is the code:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
import pytz
root=Tk()
root.title("Live Graph")
nextdate = datetime.datetime.today() + datetime.timedelta(days=1)
todaydate = date.today()
def plotcom():
fig = plt.figure(figsize = (8,6))
ax1 = fig.add_subplot(1, 1, 1)
var5 = "TSLA"
def animate(i):
tickerData = yf.Ticker(var5)
dsp = tickerData.history(interval='1m', start = todaydate, end=nextdate)
dspclose = dsp["Close"]
ax1.set_xlabel("Time")
date_form = DateFormatter("%H-%M-%S", tz=pytz.timezone("America/New_York"))
ax1.xaxis.set_major_formatter(date_form)
ax1.set_ylabel("Price")
ax1.set_title(var5+" - Live Graph")
ax1.legend()
canvas = FigureCanvasTkAgg(fig, master = root)
canvas.draw()
canvas.get_tk_widget().pack()
canvas.get_tk_widget().place(x=10, y=240)
ani = animation.FuncAnimation(fig, animate, interval = 60000)
plt.show()
x = Button(root, text="Plot", command=plotcom)
x.pack()
x.place(x=300, y=0)
root.mainloop()

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