Matplotlib in tkinter can not exit - python

I modified some matplotlib examples for test,this is code
#!/usr/bin/env python
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import sys
if sys.version_info[0] < 3:
import Tkinter as Tk
else:
import tkinter as Tk
root = Tk.Tk()
root.wm_title("test in TK")
f = plt.figure(figsize=(3,3),dpi=98)
labels = 'Frogs', 'Hogs', 'Dogs', 'Logs'
sizes = [15, 30, 45, 10]
colors = ['yellowgreen', 'gold', 'lightskyblue', 'lightcoral']
explode = (0, 0.1, 0, 0) # only "explode" the 2nd slice (i.e. 'Hogs')
plt.pie(sizes, explode=explode, labels=labels, colors=colors,
autopct='%1.1f%%', shadow=True, startangle=90)
plt.axis('equal')
canvas = FigureCanvasTkAgg(f, master=root)
canvas.show()
canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
canvas._tkcanvas.pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
button = Tk.Button(master=root, text='Quit', command=sys.exit)
button.pack(side=Tk.BOTTOM)
Tk.mainloop()
I put a pie in tk,it can work,if I press 'Quit' button program will exit normally,if I press tk's 'X' the window will close,but this program in CMD window always waiting,not exit,I need use Ctrl+Break to close it,why?

The pyplot library provides a MATLAB-like plotting framework. It will make life easier for you by creating threads in the background, so that you can communicate with the pyplot user interface at the same time as using a CLI or some other interface. I guess what is happening is that this helper thread does not terminate when you press quit, and therefore the program does not exit. You should probably avoid using pyplot when making your own GUI.
One workaround could be this:
def exit():
plt.close('all')
sys.exit()
which would close all pyplot plots. But the best thing would probably be not to use pyplot in this case.

Related

Are there libraries for data visualization (except for Matplotlib) to use in the Tkinter app Python?

I want to create a Tkinter app in which the user can enter the data (just by choosing the Excel file) and can see the graphs by using Tkinter Check Buttons. So I decided to use Matplotlib for that, but it opens in another new window. It will be excellent if those charts open in the same window. (I also would prefer if that library would show the charts as Plotly Express)
Please upload a minimum reproducible example.
Without seeing that, you need to create a FigureCanvasTkAgg canvas to add your plots to if you haven't (docs).
import tkinter
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
# Implement the default Matplotlib key bindings.
from matplotlib.backend_bases import key_press_handler
from matplotlib.figure import Figure
import numpy as np
root = tkinter.Tk()
root.wm_title("Embedding in Tk")
fig = Figure(figsize=(5, 4), dpi=100)
t = np.arange(0, 3, .01)
ax = fig.add_subplot()
line, = ax.plot(t, 2 * np.sin(2 * np.pi * t))
ax.set_xlabel("time [s]")
ax.set_ylabel("f(t)")
canvas = FigureCanvasTkAgg(fig, master=root) # A tk.DrawingArea.
canvas.draw()
# pack_toolbar=False will make it easier to use a layout manager later on.
toolbar = NavigationToolbar2Tk(canvas, root, pack_toolbar=False)
toolbar.update()
canvas.mpl_connect(
"key_press_event", lambda event: print(f"you pressed {event.key}"))
canvas.mpl_connect("key_press_event", key_press_handler)
button_quit = tkinter.Button(master=root, text="Quit", command=root.quit)
def update_frequency(new_val):
# retrieve frequency
f = float(new_val)
# update data
y = 2 * np.sin(2 * np.pi * f * t)
line.set_data(t, y)
# required to update canvas and attached toolbar!
canvas.draw()
slider_update = tkinter.Scale(root, from_=1, to=5, orient=tkinter.HORIZONTAL,
command=update_frequency, label="Frequency [Hz]")
# Packing order is important. Widgets are processed sequentially and if there
# is no space left, because the window is too small, they are not displayed.
# The canvas is rather flexible in its size, so we pack it last which makes
# sure the UI controls are displayed as long as possible.
button_quit.pack(side=tkinter.BOTTOM)
slider_update.pack(side=tkinter.BOTTOM)
toolbar.pack(side=tkinter.BOTTOM, fill=tkinter.X)
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1)
tkinter.mainloop()

How do I print my seaborn plot to a tkinter window, will show in IDE but doesnt stick to canvas

I have:
def create_plot():
df = pd.read_json("my_final_data.json")
small_df = df[df.small_airport.isin(['Y'])]
medium_df = df[df.medium_airport.isin(['Y'])]
large_df = df[df.large_airport.isin(['Y'])]
plt.figure(figsize=(35,10))
ax = sns.distplot(small_df['frequency_mhz'], color='red', label='Small Airports')
sns.distplot(medium_df['frequency_mhz'], color='green', ax=ax, label='Medium Airports')
sns.distplot(large_df['frequency_mhz'], ax=ax, label='Large Airports')
plt.legend(loc="upper right")
graph = plt.show()
return graph
#Generating tkinter window
window1 = tk.Tk()
figure = create_plot()
canvas = FigureCanvasTkAgg(figure, master=window1)
canvas.draw()
canvas.get_tk_widget().pack()
tk.mainloop()
Which launches 2(?) empty tkinter windows besides the canvas in one, but the IDE shows the actual graph im trying to print so I know the function is doing its job.
How do I make that returned graph stick to the window?
Youre getting 2 empty windows because you used plt.show() which is not intended to be used from within a tkinter application. The other one is an empty tkinter window (generated via tk.Tk()), without any content.
You also missed to give us sample data (my_final_data.json).
A little bit of research would have brought you a lot of examples for seaborn integration into tkinter
sns.distplot is deprecated as mentioned here, i would recommend using sns.displot or sns.histplot instead (some nice histplot examples)
This example should get you started:
import tkinter as tk
import seaborn as sns
from matplotlib import pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
def create_plot(root):
# create random seaborn displot; replace this part with your own data
figure, ax = plt.subplots(figsize=(6, 6))
penguins = sns.load_dataset("penguins")
sns.histplot(data=penguins, x="flipper_length_mm", ax=ax, hue="species")
# create tkinter canvas from figure
canvas = FigureCanvasTkAgg(figure, master=root)
canvas.draw()
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
# optional: create toolbar
toolbar = NavigationToolbar2Tk(canvas, root)
toolbar.update()
canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
# create your application
window1 = tk.Tk()
# call function to create plot
create_plot(window1)
# mainloop
tk.mainloop()

Matplotlib Event Handlers not being Called when Embedded in Tkinter

Overview
I am in the process of embedding a Matplotlib plot in a Tkinter window. I need to use the Matplotlib event handler functions (described here). When run as a standalone Matplotlib figure, I get the correct behavior: the event handlers perform their correct function on the correct user action. But when embedding in a Tkinter window, the Matplotlib event handlers are no longer being called.
Expected Behavior
The Matplotlib event handler should be called when the figure is embedded in a Tkinter window.
Current Behavior
The Matplotlib event handler is not being called.
Minimal Code Snippet
Without Tkinter
import matplotlib.pyplot as plt
def onpick(event):
print("You selected the line!")
if __name__=='__main__':
### MATPLOTLIB SETUP ###
xs = [0,1,2] #x-values of the graph
ys = [4,3,2] #y-values of the graph
fig, ax = plt.subplots(1)
ax.plot(xs, ys, picker=True)
fig.canvas.mpl_connect('pick_event', onpick)
plt.show()
When this code is run, you can select the line on the plot and the onpick method is called, printing the text to stdout. This is the desired output.
Embedded in Tkinter
import tkinter
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
def onpick(event):
print("You selected the line!")
if __name__=='__main__':
### MATPLOTLIB SETUP ###
xs = [0,1,2] #x-values of the graph
ys = [4,3,2] #y-values of the graph
fig, ax = plt.subplots(1)
ax.plot(xs, ys, picker=True)
fig.canvas.mpl_connect('pick_event', onpick)
### TKINTER SETUP ###
root = tkinter.Tk()
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.draw()
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1)
tkinter.mainloop()
When this code is run and you try to click on the line, the text is never printed meaning the onpick method is never being called.
Versions
python : 3.6.1
matplotlib : 3.3.4
tkinter : 8.6.6
The event listeners of the Matplotlib Figure object cannot be called when embedded in Tkinter. Instead, you have to add the listeners to the FigureCanvasTkAgg (or similar) object. So a working example built on the previous minimal example would be:
import tkinter
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
def onpick(event):
print("You selected the line!")
if __name__=='__main__':
### MATPLOTLIB SETUP ###
xs = [0,1,2] #x-values of the graph
ys = [4,3,2] #y-values of the graph
fig, ax = plt.subplots(1)
ax.plot(xs, ys, picker=True)
#plt.show()
### TKINTER SETUP ###
root = tkinter.Tk()
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.draw()
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1)
canvas.mpl_connect("pick_event", onpick) # Add the listener using this command
tkinter.mainloop()

Unable to call canvas.show()

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()

Using basemap as a figure in a Python GUI

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()

Categories

Resources