This is code that takes a figure and displays it on a Tkinter window. How do I unpack/delete "canvas" from the GUI?
from Tkinter import *
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
def plot(x, y):
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
ax1.plot(x,y)
return fig
plt = plot(1,1)
root = Tk()
canvas = FigureCanvasTkAgg(plt, master=root)
canvas.get_tk_widget().pack()
If you'd like to remove the plot and free up the parent frame/window, call canvas.get_tk_widget().destroy().
For example:
import Tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
def main():
fig = plot(range(10), range(10))
root = tk.Tk()
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.get_tk_widget().pack()
root.bind_all('<Button-1>', lambda event: remove_plot(canvas))
root.mainloop()
def plot(x, y):
fig = Figure()
ax1 = fig.add_subplot(1,1,1)
ax1.plot(x,y)
return fig
def remove_plot(canvas):
canvas.get_tk_widget().destroy()
main()
This only destroys the figure's tk widget. The figure still exists and could be added again, assuming it hasn't gone out of scope and been garbage collected. (Also, the figure will be garbage collected just like any other object as we're not using the pyplot state machine.)
Related
Update canvas fig embedded in tkinter window when fig carried out from mplfinance. How I can update fig in tkinter window
'''from tkinter import *
import datetime as dt
import yfinance as yf
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
import mplfinance as mpf
gui = Tk()
gui.geometry("800x700")
stock = 'SPY'
end = dt.datetime.now()
start = end - dt.timedelta(days=6)
df = yf.download(stock.upper(), start, end, interval = '5m')
def Plot_data(type='candle', grid= '-', style='nightclouds'):
fig = plt.Figure(figsize=(7,5), dpi=100)
chart = fig.add_subplot(111)
chart_canvas = FigureCanvasTkAgg(fig, master=gui)
chart_canvas.get_tk_widget().place(relx = .05, rely=.25)
s = mpf.make_mpf_style(base_mpf_style=style, gridstyle=grid)
mpf.plot(df, ax= chart, type = type, style = s)
Plot_data()
mainloop()'''
I'm writing a script using tkinter and matplotlib for data processing, some parts of the code requires polygon selector to choose a region of interest. However, PolygonSelector fails to detect the motion of cursor.
It should be noted that this issue occurs when the interactive mode of matplotlib figure is on.
Simplified code and result are shown below:
#!/usr/bin/env python3
import matplotlib
matplotlib.use("TkAgg")
import tkinter as tk
import matplotlib.pyplot as plt
from matplotlib.widgets import PolygonSelector
root = tk.Tk()
def draw():
fig = plt.figure()
ax = fig.add_subplot(111)
plt.ion() # interactive mode is on
plt.show()
def onselect(data_input):
print(data_input)
PS = PolygonSelector(ax, onselect)
tk.Button(root, text='draw', command=draw).pack()
root.mainloop()
This is the plot after clicking 'draw' button on tkinter GUI, the starting point of polygon stucks at (0,0), it is expected to move with cursor:
When I call draw() outside of tkinter, PolygonSelector works fine:
def draw():
fig = plt.figure()
ax = fig.add_subplot(111)
plt.ion() # interactive mode is on
plt.show()
def onselect(data_input):
print(data_input)
PS = PolygonSelector(ax, onselect)
a = input() # prevent window from closing when execution is done
draw()
The simple solution would be to make sure you make your Polygon Selector a global variable. This will keep the selector visually updating.
#!/usr/bin/env python3
import tkinter as tk
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.widgets import PolygonSelector
matplotlib.use("TkAgg")
root = tk.Tk()
ps = None
def draw():
global ps
fig = plt.figure()
ax = fig.add_subplot(111)
plt.ion()
plt.show()
ps = PolygonSelector(ax, on_select)
def on_select(data_input):
print(data_input)
tk.Button(root, text='draw', command=draw).pack()
root.mainloop()
If you build this into a class then you can avoid the use of global and get the behavior you want by apply the Polygon Selector as a class attribute.
#!/usr/bin/env python3
import tkinter as tk
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.widgets import PolygonSelector
matplotlib.use("TkAgg")
class GUI(tk.Tk):
def __init__(self):
super().__init__()
self.ps = None
tk.Button(self, text='draw', command=self.draw).pack()
def draw(self):
fig = plt.figure()
ax = fig.add_subplot(111)
plt.ion()
plt.show()
self.ps = PolygonSelector(ax, self.on_select)
def on_select(self, data_input):
print(data_input)
if __name__ == "__main__":
GUI().mainloop()
Results:
I'm trying to import a matplotlib 3D animation inside a Tkinter window but I got stuck on an error. My Tkinter window has many frames and I try to plot the animation on one of this frame. Finally, I create all elements inside a class init function. So here is a simplified version of my code:
from tkinter import *
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.animation as anim
import random
class UI:
def __init__(self):
self.back = Frame(master=self.window, width=1200, height=800, bg='white')
self.rightBack = Frame(master=self.back, width=300, height=800)
self.rightBack.pack(side='right',padx=2,pady=2)
self.fig = Figure()
self.axes = Axes3D(self.fig)
self.ani = anim.FuncAnimation(self.fig, self.updateDisplay, interval=100, blit=False) # Creating animation
self.canvas = FigureCanvasTkAgg(self.fig, master=self.displayBack)
self.canvas.get_tk_widget().pack(side=TOP,fill=BOTH,expand=True)
# Starting application
self.window.mainloop()
def updateDisplay(self,num):
self.axes.clear()
self.axes.plot([0,1,random.random()],[0,1,random.random()],[0,1,random.random()],'o',color='k',markersize='5')
But when I run the following in python
self.ani = anim.FuncAnimation(self.fig, self.updateDisplay, interval=100, blit=False)
I get this error
File "matplotlib/animation.py", line 1703, in __init__
TimedAnimation.__init__(self, fig, **kwargs)
File "matplotlib/animation.py", line 1465, in __init__
event_source = fig.canvas.new_timer()
AttributeError: 'NoneType' object has no attribute 'new_timer'
So I think I understood the error as animation needs a timer but I don't know how to fix it. Any help? Thank's in advance
The animation needs to reside in a figure that has a canvas. In your code you define the canvas only after the animation. I suppose it would work as expected if you reverse the order of the animation and the canvas.
# First define the canvas
self.canvas = FigureCanvasTkAgg(self.fig, ...)
# only then define the animation
self.ani = anim.FuncAnimation(self.fig, ...)
Error:
/usr/bin/python3.5 /root/PycharmProjects/Capstone2/main.py
Traceback (most recent call last):
File "/root/PycharmProjects/Capstone2/main.py", line 62, in
canvas.show()
File "/usr/lib/python3/dist-packages/matplotlib/backends/backend_tkagg.py", line 353, in draw
self._master.update_idletasks()
AttributeError: 'NoneType' object has no attribute 'update_idletasks'
Process finished with exit code 1
Relevant Code:
# GUI imports
from tkinter import *
# Graph imports
import matplotlib.pyplot as plt
import matplotlib
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import matplotlib.animation as animation
from matplotlib import style
# Time imports
import time
def animate(i):
a.clear()
a.plot(data)
data = []
# GUI Set-Up
ROOT = Tk()
ROOT.attributes("-fullscreen", True)
matplotlib.use("TkAgg")
style.use('ggplot')
f = Frame(ROOT)
fig = Figure(figsize=(12, 7), dpi=100)
a = fig.add_subplot(221)
a.set_title("Data")
canvas = FigureCanvasTkAgg(fig)
canvas.get_tk_widget().pack(side=BOTTOM, fill=BOTH, expand=True)
canvas._tkcanvas.pack(side=TOP, fill=BOTH, expand=True)
canvas.show()
Frame.pack(f)
LOOP_ACTIVE = True
ROOT.config(cursor="none")
ani = animation.FuncAnimation(fig, animate, interval=1000)
plt.show()
for x in range(0, 10):
data.append(x)
ROOT.update()
time.sleep(1)
My Understanding:
Every second one more data point should be added. Every second the plot should update to reflect this.
My Thoughts:
From some google searches hint that AttributeError pops up because I am trying to call canvas.show and canvas is nothing but I think my line "canvas = FigureCanvasTkAgg(fig)" is correct and earlier lines like "canvas.get_tk_widget().pack(side=BOTTOM, fill=BOTH, expand=True)" do not cause errors
If I omit "canvas.show()" then the error occurs later "Exception in Tkinter callback "
If I modify my code so canvas = FigureCanvasTkAgg(fig, master=ROOT) and match some examples I've found, no errors occur but the animation never does anything. I couldn't find any documentation on FigureCanvasTkAgg, only examples so I'm not too sure what I'm doing by modifying this line
Full code can be seen here (warning: it's pretty gross): https://github.com/AdamVen/Capstone/blob/fasterGraph/main.py
The code I've based mine on is here: https://pythonprogramming.net/how-to-embed-matplotlib-graph-tkinter-gui/
I'm not sure where I'm going wrong. Any advice would be appreciated
Some parts of the code are unclear to me: The use of canvas.show() (which shouldn't even exist) and the use of plt.show() (which doesn't makes sense, given that you haven't created any pyplot figure at all)
Given that canvas = FigureCanvasTkAgg(fig, master=ROOT) is used in most examples, I wouldn't know why not to use it.
The loop at the end of the file doesn't seem to be useful. Instead you have the animation loop to change your data.
Finally, you forgot to actually start the program, i.e. ROOT.mainloop().
from Tkinter import *
import matplotlib
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import matplotlib.animation as animation
# Data simulation imports
import time
def animate(i):
a.clear()
data.append(i)
a.plot(data)
data = []
# GUI Set-Up
ROOT = Tk()
f = Frame(ROOT)
plt.style.use('ggplot')
fig = Figure(figsize=(12, 7), dpi=100)
a = fig.add_subplot(221)
a.set_title("Data")
canvas = FigureCanvasTkAgg(fig, master=ROOT)
canvas.get_tk_widget().pack(side=BOTTOM, fill=BOTH, expand=True)
canvas._tkcanvas.pack(side=TOP, fill=BOTH, expand=True)
Frame.pack(f)
ani = animation.FuncAnimation(fig, animate, interval=1000)
ROOT.mainloop()
I have snippets of code to generate a basemap, as well as the (very rough) start of a GUI. However, I cannot find that "one-liner" will allow me to display the map as a figure in the GUI. Code snippets are as follows; ideally, I would like a couple of things to happen:
An image of the 'basemap' to relace the plot of sin(2*pi*t)
I would really like for the code to record the (pixel) location on the graphic, if I were to click on the plot shown (the hope is that you could click anywhere on the map, and the script would record the latitude and longitude of where you clicked).
Regarding the 1st step, I've tried things such as setting the figure variable, f, equal to the Basemap; this killed the GUI portion altogether, and simply showed an image of the map in another window.
I've tried to address #2 by trying to implement a couple routines I found on stackexchange, but never got it to work fully.
Basemap:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
## User-chosen datapoint
#lons = [10]; lats = [20];
# Define map projection
m = Basemap(projection='cyl',llcrnrlat=-90,urcrnrlat=90,\
llcrnrlon=-180,urcrnrlon=180,resolution='c')
m.drawcoastlines()
Rough GUI:
import matplotlib
matplotlib.use('TkAgg')
from numpy import arange, sin, pi
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
# implement the default mpl key bindings
from matplotlib.backend_bases import key_press_handler
from Tkinter import *
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
from mpl_toolkits.basemap import Basemap
from matplotlib.figure import Figure
import sys
if sys.version_info[0] < 3:
import Tkinter as Tk
else:
import tkinter as Tk
root = Tk.Tk()
root.wm_title("Embedding in TK")
f = Figure(figsize=(5, 4), dpi=100)
a = f.add_subplot(111)
t = arange(0.0, 3.0, 0.01)
s = sin(2*pi*t)
a.plot(t, s)
# a tk.DrawingArea
canvas = FigureCanvasTkAgg(f, master=root)
canvas.show()
canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
toolbar = NavigationToolbar2TkAgg(canvas, root)
toolbar.update()
canvas._tkcanvas.pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
def on_key_event(event):
print('you pressed %s' % event.key)
key_press_handler(event, canvas, toolbar)
canvas.mpl_connect('key_press_event', on_key_event)
def _quit():
root.quit() # stops mainloop
root.destroy() # this is necessary on Windows to prevent
# Fatal Python Error: PyEval_RestoreThread: NULL tstate
button = Tk.Button(master=root, text='Quit', command=_quit)
button.pack(side=Tk.BOTTOM)
Tk.mainloop()
The question how to include a basemap plot into Tkinter has actually not been answered yet. So the idea is to create an axes ax in a figure and add the Basemap to the axes. This is done with the ax argument,
m = Basemap(..., ax=ax)
The figure is then added to the canvas in the usual way; below is a complete example.
from mpl_toolkits.basemap import Basemap
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import sys
if sys.version_info[0] < 3:
import Tkinter as Tk
else:
import tkinter as Tk
root = Tk.Tk()
root.wm_title("Embedding in TK")
fig = Figure(figsize=(5, 4), dpi=100)
ax = fig.add_subplot(111)
m = Basemap(projection='cyl',llcrnrlat=-90,urcrnrlat=90,\
llcrnrlon=-180,urcrnrlon=180,resolution='c', ax=ax)
m.drawcoastlines()
# a tk.DrawingArea
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.show()
canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
toolbar = NavigationToolbar2TkAgg(canvas, root)
toolbar.update()
canvas._tkcanvas.pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
def _quit():
root.quit() # stops mainloop
root.destroy() # this is necessary on Windows to prevent
# Fatal Python Error: PyEval_RestoreThread: NULL tstate
button = Tk.Button(master=root, text='Quit', command=_quit)
button.pack(side=Tk.BOTTOM)
Tk.mainloop()
Note: In newer versions of matplotlib you should use NavigationToolbar2Tk instead of NavigationToolbar2TkAgg.
One simple technique is to define an on_click function, show an image, then overwrite that image. The on_click function will still read the mouse click location, despite the image change.
A sample code follows, demonstrating this is as shown:
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
def on_click(event):
if event.inaxes is not None:
print event.xdata, event.ydata
else:
print 'Clicked ouside axes bounds but inside plot window'
fig, ax = plt.subplots()
fig.canvas.callbacks.connect('button_press_event', on_click)
plt.show()
#Define map projection
m = Basemap(projection='cyl', llcrnrlat=-90, urcrnrlat=90,
llcrnrlon=-180, urcrnrlon=180, resolution='c')
m.drawcoastlines()