Closing the window doesn't kill all processes - python

I have a very simple programme that displays a simple plot on press of a button. My problem is when I close the application window, the programme keeps running until I kill it from the terminal. Below is my code and my investigation showed the issue is caused by
matplotlib.use('TkAgg')
But I don't know how to fix it! If it helps, I'm running on OSX.
#!/usr/bin/python
from Tkinter import *
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
# ------ End of imports
class Ops:
def show_plot(self):
self.f, self.figarray = plt.subplots(1, sharex=True, sharey=True)
self.figarray.plot((1,2,3),(1,2,3))
plt.tight_layout()
self.canvas = FigureCanvasTkAgg(self.f, master=self.mainFrame)
self.canvas._tkcanvas.config(background='white', borderwidth=0, highlightthickness=0)
self.canvas._tkcanvas.pack(side=TOP, fill=BOTH)
class GUI(Ops):
def __init__(self, master):
self.master = master
self.width = self.master.winfo_screenwidth() # Width of the screen
self.height = self.master.winfo_screenheight() # Height of the screen
self.x = (self.width / 2)
self.y = (self.height / 2)
self.master.geometry("%dx%d+%d+%d" % (self.width, self.height, self.x, self.y))
self.mainFrame = Frame(self.master) # Generate the main container
self.mainFrame.pack()
# ---------- TOP FRAME ----------
self.topFrame = Frame(self.mainFrame)
self.topFrame.pack()
self.browse_button = Button(self.topFrame, text="Plot", command=self.show_plot)
self.browse_button.grid()
class App:
def __init__(self):
self.file_handler = Ops()
self.root = Tk()
self.gui_handler = GUI(self.root)
def run(self):
self.root.mainloop()
Application = App()
Application.run()

You need to call root.quit() to end the Tk.mainloop(). For example, see the answer here.

The solution is simple. Just use
from matplotlib.figure import Figure
instead of
import matplotlib.pyplot as plt

Use root.mainloop outside of a function, that should solve your problems.

Related

How can I customize the Matplotlib Toolbar in tkinter using "toolmanager"?

I'm trying to implement matplotlib toolmanager custom tools from this Tool Manager Example in my tkinter app (which is based partly on this other MPL Example), but I'm running into issues when trying to access fig.canvas.manager.toolmanager.
Here's a pared-down version of my tkinter application
import matplotlib as mpl
import matplotlib.backends.backend_tkagg as mptk
import matplotlib.pytlot as plt
import numpy as np
import tkinter as tk
from matplotlib.backend_tools import ToolBase # for custom tools, as per linked TM example
from tkinter import ttk
mpl.use('TkAgg') # do I need this? What is this doing?
plt.rcParams['toolbar'] = 'toolmanager' # as per the linked TM example
class Root(tk.Tk):
def __init__(self):
super().__init__()
self.title('Plot App')
self.geometry('1920x1080')
self.main_frame = MainFrame(self)
self.main_frame.pack(expand=True, fill=tk.BOTH)
class MainFrame(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
self.label_frame = ttk.LabelFrame(self, text='Data Viewer')
self.label_frame.pack(expand=True, fill=tk.BOTH)
self.plot() # just plot immediately at init
def plot(self):
# nonsense data to plot
t = np.arange(0, 10, 0.1)
a = np.sin(8 * np.pi * t)
b = np.cos(4 * np.pi * t)
self.fig, self.ax = plt.subplots()
self.ax.plot(t, a, t, b)
self.tk_canvas = mptk.FigureCanvasTkAgg(self.fig, master=self.label_frame)
self.tk_canvas.draw()
self.tk_canvas.get_tk_widget().pack(expand=True, fill=tk.BOTH)
# init custom toolbar
self.toolbar = Toolbar(self.fig, self.label_frame)
self.toolbar.update()
class Toolbar(mptk.NavigationToolbar2Tk): # custom toolbar inherits from NavigationToolbar2Tk
def __init__(self, fig, parent):
super().__init__(fig.canvas, parent)
self.fig = fig
self.tb = self.fig.canvas.manager.toolbar # here is where I run into trouble
self.tm = self.fig.canvas.manager.toolmanager # and likewise, here
if __name__ == '__main__':
app = Root()
app.mainloop()
The line assigning self.tb throws the following exception at run time:
AttributeError: 'NoneType' has no attribute 'toolbar'
I understand that this is because self.fig.canvas.manager is None - what I don't understand is why that's the case when the example does this without issues.
For reference, if I print(fig.canvas.manager.toolmanager) from the Tool Manager Example code, I get (as expected):
<matplotlib.backend_managers.ToolManager object at ~ID~>
It's worth pointing out (though perhaps not surprising) that the assignment to self.tm throws the same exception for much the same reason.
Informational Edit:
A little more digging led me to the method plt.get_current_fig_manager(), which in my case is unfortunately None; I assume this has something to do with using the Tk backend.
What am I doing wrong? How can I use toolmanager effectively with NavigationToolbar2Tk? I'd like to be able to use tm.add_tool() and tm.remove_tool(), but I can't since my toolmanager doesn't exist.
Any help is appreciated much appreciated.

Matplotlib FuncAnimation fails to run and is bypassed in tkinter GUI

I am trying to have two daemonic threads run alongside my tkinter main thread. One of the threads generates data and the other thread runs FuncAnimation that plots the data that is generated. However, I am having issues with getting FuncAnimation to run, I can see that whenever I click the button both print statements are shown in the console meaning FuncAnimation appears to have been skipped or ran but exited due to some missing or bad parameter. I checked the matplotlib documentation to ensure I'm not missing anything and I think I have created all the objects and called the right methods to make this happen but something is obviously amiss. Any help is greatly appreciated.
import tkinter as tk
import time
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.figure import Figure
from matplotlib import animation
import threading
root = tk.Tk()
class dynamicChart():
def __init__(self):
self.x = []
self.y = []
self.buildChart()
def buildChart(self):
self.figure = Figure(figsize=(5, 5), dpi=100)
ax = self.figure.add_subplot(111)
self.scatter = ax.scatter(self.x,self.y)
canvas = FigureCanvasTkAgg(self.figure, root)
canvas.draw()
canvas.get_tk_widget().pack(pady=10)
toolbar = NavigationToolbar2Tk(canvas, root)
toolbar.update()
canvas.get_tk_widget().pack()
def animate(self,i):
print("Enter animate")
self.scatter.set_offsets([self.x, self.y])
def Ani(self):
print("Enter Ani")
self.animation = animation.FuncAnimation(self.figure, self.animate, interval = 2000)
print("Exit Ani")
class genData():
def __init__(self):
self.chart = dynamicChart()
def generate(self):
X = 2
Y = 2
i = 1
while True:
time.sleep(1)
X = X*i
Y = Y + i
self.chart.x.append(X)
self.chart.y.append(Y)
i+=1
StartDataGen_CreateFigureAxes = genData()
genData = threading.Thread(target = StartDataGen_CreateFigureAxes.generate, daemon = True)
genData.start()
ani = threading.Thread(target = StartDataGen_CreateFigureAxes.chart.Ani, daemon = True)
button = tk.Button(root, text="Graph It", command = lambda: ani.start())
button.pack()
root.mainloop()

How do I make tkinter mainloop wait for a matplotlib click event

I am buildin a GUI with an embedded plot using Tkinter and matplotlib. I have embedded a figure in my window and am now looking to use matplotlib's event handler to get two sets of x,y coordinates from the graph and then use these coordinates to create a straight line which is subtracted from the data in the graph. A simplified version of the code looks like this:
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
import tkinter as tk
#ideally this uses matplotlib's event handler and also waits for a click before registering the cooridnates
def choose_points():
points = []
window.bind("<Button-1>", on_click)
points.append(graph_xy)
window.bind("<Button-1>", on_click)
points.append(graph_xy)
return points
def on_click(event):
window.unbind("<Button-1")
window.config(cursor="arrow")
graph_xy[0]=event.x
graph_xy[1]=event.y
def line(x1=0,y1=0,x2=1,y2=1000):
m=(y2-y1)/(x2-x1)
c=y2-m*x2
line_data=[]
for val in range(0,20):
line_data.append(val*m + c)
return line_data
def build_line():
points = []
points = choose_points()
#store line in line_list
line_list=line(points[0],points[1],points[2],points[3])
#lists needed
line_list=[]
graph_xy=[0,0]
#GUI
window=tk.Tk()
window.title("IPES Graphing Tool")
window.geometry('1150x840')
#Make a frame for the graph
plot_frame = tk.Frame(window)
plot_frame.pack(side = tk.TOP,padx=5,pady=5)
#Button for making the straight line
line_btn = ttk.Button(plot_frame,text="Build line", command = build_line)
line_btn.grid(row=4, column=2,sticky='w')
#make empty figure
fig1=plt.figure(figsize=(9,7))
ax= fig1.add_axes([0.1,0.1,0.65,0.75])
#embed matplotlib figure
canvas = FigureCanvasTkAgg(fig1, plot_frame)
mpl_canvas=canvas.get_tk_widget()
canvas.get_tk_widget().pack(padx=20,side=tk.BOTTOM, fill=tk.BOTH, expand=False)
toolbar = NavigationToolbar2Tk(canvas, plot_frame)
toolbar.update()
canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=False)
window.mainloop()
Obviously this example does not plot or use the line in any way, nor are the coordinates correct, since they are not converted to the coordinates of the graph. I tried replacing the window.bind("<Button-1>",wait_click) with plt.connect('button_press_event',on_click) but this does not wait for the click, and so an error occurs since the program tries to access points but it is empty.
I would like to use the functionality of the matplotlib event handling, so that I can use methods such as event.xdata and event.inaxes to avoid unnecessary extra work.
Thank you.
You should use canvas.mpl_connect to trigger your events, and then retrieve the xdata and ydata to plot the line. See below for sample:
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
import tkinter as tk
window=tk.Tk()
window.title("IPES Graphing Tool")
window.geometry('1150x840')
plot_frame = tk.Frame(window)
plot_frame.pack(side = tk.TOP,padx=5,pady=5)
fig1=Figure(figsize=(9,7))
ax= fig1.add_axes([0.1,0.1,0.65,0.75])
canvas = FigureCanvasTkAgg(fig1, window)
canvas.get_tk_widget().pack(padx=20,side=tk.TOP, fill=tk.BOTH, expand=False)
toolbar = NavigationToolbar2Tk(canvas, window)
toolbar.update()
class DrawLine: # a simple class to store previous cords
def __init__(self):
self.x = None
self.y = None
def get_cords(self, event):
if self.x and self.y:
ax.plot([self.x, event.xdata], [self.y, event.ydata])
canvas.draw_idle()
self.x, self.y = event.xdata, event.ydata
draw = DrawLine()
canvas.mpl_connect('button_press_event', draw.get_cords)
window.mainloop()
So, I managed to get the functionality I wanted by tweaking #Henry Yik 's answer. The issue was resolved by simply using canvas.mpl_disconnect(cid).
I basically used a button to use a function called choose_cords() which would take in an object called draw of type DrawLine containing the x and y coordinates to build the line. The function would then issue the command canvas.mpl_connect('button_press_event', draw.get_cords) to start listening for clicks on the graph. Once two clicks had been registered the matplotlib event handler would be disconnected from within the draw object. The code goes like this
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
import tkinter as tk
window=tk.Tk()
window.title("IPES Graphing Tool")
window.geometry('1150x840')
plot_frame = tk.Frame(window)
plot_frame.pack(side = tk.TOP,padx=5,pady=5)
fig1=Figure(figsize=(9,7))
ax= fig1.add_axes([0.1,0.1,0.65,0.75])
canvas = FigureCanvasTkAgg(fig1, window)
canvas.get_tk_widget().pack(padx=20,side=tk.TOP, fill=tk.BOTH, expand=False)
toolbar = NavigationToolbar2Tk(canvas, window)
toolbar.update()
def choose_cords(draw):
draw.cid=canvas.mpl_connect('button_press_event', draw.get_cords)
class DrawLine: # a simple class to store previous cords
def __init__(self):
self.x = None
self.y = None
self.cid = None
def get_cords(self, event):
if self.x and self.y:
ax.plot([self.x, event.xdata], [self.y, event.ydata])
canvas.draw_idle()
canvas.mpl_disconnect(self.cid)
self.__init__()
return
self.x, self.y = event.xdata, event.ydata
draw = DrawLine()
draw_btn = tk.Button(window, text="Draw the Line", command=lambda:choose_cords(draw)).pack(side=tk.TOP,padx=5,pady=5)
window.mainloop()
I added a return after plotting the line because I want a new line every time, and not a continuation.
Thanks again Henry for your answer which spawend this one

Why does Python Tkinter Canvas draw causes a freeze? [duplicate]

I have written a piece of code where I have a simple GUI with an canvas. On this canvas I draw a Matplot. The Matplot is updated every second with data from an SQ Lite DB which I fill with some fake Sensor information (just for testing at the moment).
My Problem was that the redrawing of the canvas causes my window/gui to lag every second. I even tried to update the plot in another thread. But even there I get an lag.
With my newest Code i got most of my things working. Threading helps to prevent my GUI/Window from freezing while the Canvas is updated.
The last thing I miss is to make it Thread safe.
This is the message I get:
RuntimeError: main thread is not in main loop
Here is my newest working code with threading:
from tkinter import *
import random
from random import randint
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import time
import threading
from datetime import datetime
continuePlotting = False
def change_state():
global continuePlotting
if continuePlotting == True:
continuePlotting = False
else:
continuePlotting = True
def data_points():
yList = []
for x in range (0, 20):
yList.append(random.randint(0, 100))
return yList
def app():
# initialise a window and creating the GUI
root = Tk()
root.config(background='white')
root.geometry("1000x700")
lab = Label(root, text="Live Plotting", bg = 'white').pack()
fig = Figure()
ax = fig.add_subplot(111)
ax.set_ylim(0,100)
ax.set_xlim(1,30)
ax.grid()
graph = FigureCanvasTkAgg(fig, master=root)
graph.get_tk_widget().pack(side="top",fill='both',expand=True)
# Updated the Canvas
def plotter():
while continuePlotting:
ax.cla()
ax.grid()
ax.set_ylim(0,100)
ax.set_xlim(1,20)
dpts = data_points()
ax.plot(range(20), dpts, marker='o', color='orange')
graph.draw()
time.sleep(1)
def gui_handler():
change_state()
threading.Thread(target=plotter).start()
b = Button(root, text="Start/Stop", command=gui_handler, bg="red", fg="white")
b.pack()
root.mainloop()
if __name__ == '__main__':
app()
Here the idea without a thread:
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
import sqlite3
from datetime import datetime
from random import randint
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
root.update_idletasks()
f = Figure(figsize=(5,5), dpi=100)
x=1
ax = f.add_subplot(111)
line = ax.plot(x, np.sin(x))
def animate(i):
# Open Database
conn = sqlite3.connect('Sensor_Data.db')
c = conn.cursor()
# Create some fake Sensor Data
NowIs = datetime.now()
Temperature = randint(0, 100)
Humidity = randint(0, 100)
# Add Data to the Database
c = conn.cursor()
# Insert a row of data
c.execute("insert into Sensor_Stream_1 (Date, Temperature, Humidity) values (?, ?, ?)",
(NowIs, Temperature, Humidity))
# Save (commit) the changes
conn.commit()
# Select Data from the Database
c.execute("SELECT Temperature FROM Sensor_Stream_1 LIMIT 10 OFFSET (SELECT COUNT(*) FROM Sensor_Stream_1)-10")
# Gives a list of all temperature values
x = 1
Temperatures = []
for record in c.fetchall():
Temperatures.append(str(x)+','+str(record[0]))
x+=1
# Setting up the Plot with X and Y Values
xList = []
yList = []
for eachLine in Temperatures:
if len(eachLine) > 1:
x, y = eachLine.split(',')
xList.append(int(x))
yList.append(int(y))
ax.clear()
ax.plot(xList, yList)
ax.set_ylim(0,100)
ax.set_xlim(1,10)
ax.grid(b=None, which='major', axis='both', **kwargs)
label = tk.Label(root,text="Temperature / Humidity").pack(side="top", fill="both", expand=True)
canvas = FigureCanvasTkAgg(f, master=root)
canvas.get_tk_widget().pack(side="left", fill="both", expand=True)
root.ani = animation.FuncAnimation(f, animate, interval=1000)
if __name__ == "__main__":
root = tk.Tk()
MainApplication(root).pack(side="top", fill="both", expand=True)
root.mainloop()
Here is my DB Schema:
CREATE TABLE `Sensor_Stream_1` (
`Date` TEXT,
`Temperature` INTEGER,
`Humidity` INTEGER
);
Your GUI process must not run in any thread. only the dataacquisition must be threaded.
When needed , the data acquired are transfered to the gui process (or the gui process notified from new data available) . I may need to use a mutex to share data resource between acquisition thread and gui (when copying)
the mainloop will look like :
running = True
while running:
root.update()
if data_available:
copydata_to_gui()
root.quit()
I had the same problem with tkinter and using pypubsub events was my solution.
As comments above suggested, you have to run your calculation in another thread, then send it to the gui thread.
import time
import tkinter as tk
import threading
from pubsub import pub
lock = threading.Lock()
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.label = tk.Label(root, text="Temperature / Humidity")
self.label.pack(side="top", fill="both", expand=True)
def listener(self, plot_data):
with lock:
"""do your plot drawing things here"""
self.label.configure(text=plot_data)
class WorkerThread(threading.Thread):
def __init__(self):
super(WorkerThread, self).__init__()
self.daemon = True # do not keep thread after app exit
self._stop = False
def run(self):
"""calculate your plot data here"""
for i in range(100):
if self._stop:
break
time.sleep(1)
pub.sendMessage('listener', text=str(i))
if __name__ == "__main__":
root = tk.Tk()
root.wm_geometry("320x240+100+100")
main = MainApplication(root)
main.pack(side="top", fill="both", expand=True)
pub.subscribe(main.listener, 'listener')
wt = WorkerThread()
wt.start()
root.mainloop()
This function is called every second, and it is outside the normal refresh.
def start(self,parent):
self.close=False
self.Refresh(parent)
def Refresh(self,parent):
'''your code'''
if(self.close == False):
frame.after( UpdateDelay*1000, self.Refresh, parent)
The function is called alone, and everything that happens inside it does not block the normal operation of the interface.

How to uncheck a checkbox after the close_event of figure in Python

This is my first question, so please let me know if I make any faux pas.
I'd like to have a check-box that creates and destroys a plot. If that plot is closed using the window's "x", then I'd like the check-box to be unchecked.
I've got it all working except the unchecking upon figure closing.
It looks like my close function is getting called, but it crashes when I try to manipulate the check-box.
from tkFont import *
from Tkinter import *
import matplotlib.pyplot as plt
import numpy as np
class Application(Frame):
def velocity_cb(self):
if self.velocity_var.get()==1:
print "selected Velocity checkbox"
x = np.arange(100.)
self.figureHandles[1] = plt.figure("Velocity")
# set the close event to run my function
self.figureHandles[1].canvas.mpl_connect('close_event', self.handle_close)
plt.plot(x,x)
plt.show()
else:
print "deselected"
plt.close(self.figureHandles[1])
def handle_close(self,event):
#print self.ground_var.get()
print "X'd velocity window"
#print self.figureHandles[1]
#plt.close(self.figureHandles[1])
self.velocity_var.set(0)
def createWidgets(self):
self.figureHandles = [0]*13
self.velocity_var = IntVar()
self.velocity = Checkbutton(self, text="Velocity", variable=self.velocity_var, command=self.velocity_cb)
self.velocity.grid(row=6,column=0,sticky='W')
self.velocity.var = self.velocity_var
def __init__(self,master=None):
Frame.__init__(self,master)
self.pack()
self.createWidgets()
if __name__=='__main__':
root = Tk()
app = Application(master = root)
app.mainloop()

Categories

Resources