I am writing a GUI application using TKinter.
Basically I have a menu where I can select different functions. One of this is supposed to plot a graph, so it opens a figure plot.
On the main GUI I placed a "QUIT" button to close the application.
Here is a sample of my code:
Main.py
from Tkinter import *
import ALSV_Plots
tk = Tk()
tk.title('ALS Verification v01-00')
tk.geometry('500x282')
def doneButton():
tk.quit()
def plotCoarseX():
plot = ALSV_Plots.CoarseXPlot(showImage = True)
plot.plotFunction()
menubar = Menu(tk)
plotMenu = Menu(menubar, tearoff=0)
plotMenu.add_command(label="Coarse X plot", command=plotCoarseX)
quitButton = Button(tk,
compound = LEFT,
image = exitIcon,
text =" QUIT",
font = ('Corbel', 10),
command = doneButton)
quitButton.place(x = 400, y = 240)
tk.mainloop()
ALSV_Plots.py
import pylab
import sharedVar
class CoarseXPlot():
def __init__(self, showImage = True):
self.show = showImage
def plotFunction(self):
xSrcSlice, xLightSetSlice] = sharedVar.coarseXResult
pylab.ioff()
figNum = getFigNumber()
fig = pylab.figure(figNum, figsize=(10.91954, 6.15042))
text = 'Coarse X determination\nX=%.5e, beam 4-Sigma=%.5e' % (beamPosition, beam4SigmaSize)
fig.text(0.5, 0.95, text, horizontalalignment='center', verticalalignment='center')
pylab.xlabel('X')
pylab.ylabel('Light')
pylab.plot(xSrcSlice, xLightSetSlice, 'bd')
pylab.grid(True)
if self.show:
pylab.show()
pylab.close()
return fig
Problem: when I select the plot function from the menu the figure is correctly displayed. I close it manually, but when I try to quit the application by clicking the "quit" button I have to press it twice in order to close the application.
Do you have any idea why this is happening?
I found the solution myself. Apparently the "show()" method in my matplotlib was set as blocking by default. So I solved the issue by forcing the "block" parameter to "False":
pylab.show(block = False)
I also removed the calls to:
pylab.ioff()
pylab.close()
Related
I need some help for my current code. I want to create a window by tkinter and show a plot in a canvas I created by matplotlib before. This point I reached yet. My problem is that I want to clear this canvas by hitting a button. To clear a canvas I suppose to initialize this before I can fill it with the plot.
So my question is: How can I fill a plot in a created canvas?
Below you can find a small code that shows my state of arts.
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,
NavigationToolbar2Tk)
def plot():
fig = Figure(figsize = (5, 5), dpi = 100)
y = [i**2 for i in range(101)]
# adding the subplot
plot1 = fig.add_subplot(111)
# plotting the graph
plot1.plot(y)
# creating the Tkinter canvas
# containing the Matplotlib figure
output = FigureCanvasTkAgg(fig, master = window)
output.draw()
# placing the canvas on the Tkinter window
output.get_tk_widget().pack()
def clear_plot():
canvas.delete('all')
# the main Tkinter window
window = Tk()
# setting the title
window.title('Plotting in Tkinter')
# dimensions of the main window
window.geometry("700x700")
canvas = Canvas(window, width=500, height=500)
canvas.pack()
# button that displays the plot
plot_button = Button(master = window, command = plot, height = 2, width = 10, text = "Plot")
clear_button = Button(master = window, command = clear_plot, height = 2, width = 10, text = "clear", background = "yellow")
# place the button
plot_button.pack()
clear_button.pack()
# run the gui
window.mainloop() ```
There is no direct way to clear the figure from the math plot canvas. So you can instead clear the canvas by destroying the widget itself using destroy method of tkinter canvas (note you cannot destroy the mathplot canvas itself as it doesn't have any methods such as destroy).
To place math plot canvas on tkinter canvas just set master as canvas object (output = FigureCanvasTkAgg(fig, master = canvas))
(Here is your corrected code)
from tkinter import *
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,
NavigationToolbar2Tk)
def plot():
global output, fig
fig = Figure(figsize = (5, 5), dpi = 100)
y = [i**2 for i in range(101)]
# adding the subplot
plot1 = fig.add_subplot(111)
# plotting the graph
plot1.plot(y)
# creating the Tkinter canvas
# containing the Matplotlib figure
output = FigureCanvasTkAgg(fig, master = canvas)
output.draw()
# placing the canvas on the Tkinter window
output.get_tk_widget().pack()
def clear_plot():
global output
if output:
for child in canvas.winfo_children():
child.destroy()
# or just use canvas.winfo_children()[0].destroy()
output = None
# the main Tkinter window
window = Tk()
output = None
fig = None
# setting the title
window.title('Plotting in Tkinter')
# dimensions of the main window
window.geometry("700x700")
canvas = Canvas(window, width=500, height=500, bg='white')
canvas.pack()
# button that displays the plot
plot_button = Button(master = window, command = plot, height = 2, width = 10, text = "Plot")
clear_button = Button(master = window, command = clear_plot, height = 2, width = 10, text = "clear", background = "yellow")
# place the button
plot_button.pack()
clear_button.pack()
# run the gui
window.mainloop()
Or you could use
def clear_plot():
global output
if output:
output.get_tk_widget().destroy()
output = None
here the output is your FigureCanvasTkAgg instance for anyone else looking to achieve this. And you just want to temporarily hide the plot output.get_tk_widget().pack_forget() and to display it again output.get_tk_widget().pack()
update
output.get_tk_widget() Return the Tk widget used to implement FigureCanvasTkAgg which means you can also use all the methods of
canvas. So,output.get_tk_widget().delete('all') works as well
I have a fully functioning tkinter GUI (or so it seems...) but I would like to clean up the code a bit by creating classes and methods for those classes. Would someone be able to go through this code and re-organise it into classes and methods so it still functions in the same way, but if possible could you also annotate/ include notes to a certain degree? I would like this to be a learning experience for me as well.
One other thing that is causing an issue in the program is python freezing when I try to use the quit button. I don't believe this is a result of the quit button code because python had the same issue when I simply closed the GUI window prior to adding the quit function. Is this a result of me being on a mac, and perhaps some functioning issues related to that? Or is this an issue that can be resolved in my code?
On a side note to this: If anyone knows of any cool aesthetic things I can do with a GUI on a mac please answer with ideas! This program currently starts at a main window that has a descriptive label, a combobox of graph options, a button that launches a new window with the selected graph to look at concerning the data this is modeled off of, a gif that adds some pzazz to the window, and a quit button. I am using the arc theme which has made it look nicer, but would welcome any suggestions or ideas, in particular with graphing because I only really have the data to make bar graphs it seems but would love to be making bubble plots or something cool like that if I could do so with discrete, qualitative data.
Thanks for any help!
Here's my code:
import tkinter as tk
from ttkthemes import ThemedStyle
from tkinter import *
from tkinter.ttk import *
from PIL import ImageTk,Image
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.figure import Figure
final_year_totals_df = get_totals_df(df, "Service Year")
regional_totals_df = get_totals_df(df, "Location")
ethnicity_totals_df = get_totals_df(df, "Ethnicity")
age_totals_df = get_totals_df(df, "Age band")
service_type_totals_df = get_service_df(df)
top_10_types_df = get_top_10(service_type_totals_df)
cause_totals_df = get_totals_df(df, "Cause of injury")
top_10_causes_df = get_top_10(cause_totals_df)
def pick_graphs():
graph = ACC_combo.get()
result = None
title = None
if graph == "Service Year":
result = final_year_totals_df
title = "ACC Concussion Claims by Year 2010-2019"
elif graph == "Service Type":
result = top_10_types_df
title = "ACC Concussion Claims for Top 10 Treatment Service Types 2010-2019"
elif graph == "Cause of Injury":
result = top_10_causes_df
title = "ACC Concussion Claims for Top 10 Causes of Injury 2010-2019"
elif graph == "Location":
result = regional_totals_df
title = "ACC Concussion Claims by Region 2010-2019"
elif graph == "Age Band":
result = age_totals_df
title = "ACC Concussion Claims by Age Bracket 2010-2019"
elif graph == "Ethnicity":
result = ethnicity_totals_df
title = "ACC Concussion Claims by Ethnicity 2010-2019"
return result, title
def display_graph():
window = tk.Toplevel()
window.geometry("1500x1200")
style = ThemedStyle(window)
style.set_theme("arc")
graph, title = pick_graphs()
fig = Figure(figsize = (3,3), dpi = 100)
fig.subplots_adjust(left = 0.2)
a = fig.add_subplot(111)
a.xaxis.set_ticks_position('none')
a.yaxis.set_ticks_position('none')
a.xaxis.set_tick_params(pad=5)
a.yaxis.set_tick_params(pad=10)
a.grid(b=True, color='grey', linestyle='-.', linewidth=0.5, alpha=0.2)
a.set_title(title, loc='left', pad=10)
graph.plot(kind='barh', legend=False, ax=a, color='crimson')
canvas = FigureCanvasTkAgg(fig, master=window)
canvas.draw()
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
toolbar = NavigationToolbar2Tk(canvas, window)
toolbar.update()
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
exit_button = Button(window, text = "Back To Main", command = window.destroy)
exit_button.pack()
def _quit():
root.quit()
root.destroy()
root = Tk()
root.title('ACC Concussion Data 2010-2019')
root.geometry("600x500")
style = ThemedStyle(root)
style.set_theme("arc")
root_label = Label(root, text = 'Welcome to my GUI!' + '\n' +
'Please select the variable you would like to explore from the menu options below.' +
'\n' + 'The resulting pop-up window will display an interactive graph of a specific
aspect' + '\n' + 'of the data compiled by the ACC about concussion claims filed from
2010-2019.')
root_label.pack(side=TOP)
graph_button = Button(root, text = "Graph it!", command = display_graph)
graph_button.pack()
quit_button = Button(root, text="Exit", command=_quit)
quit_button.pack(side=BOTTOM)
ACC_combo = Combobox(root, values = ["Service Year", "Service Type", "Cause of Injury",
"Location", "Age Band", "Ethnicity"])
ACC_combo.set("Service Year")
ACC_combo.pack()
photo = PhotoImage(file = "/Users/oscarevans/Desktop/concussion_GIF.gif")
label = Label(image = photo)
label.pack()
root.mainloop()
I have made a GUI in which on user values a line is drawn, and when you click on the line another window opens where you can select something. And when you press ok it should print on the terminal. I think because i am creating a new tkinter window when i click on the line, i am unable to retrieve the user selection.How can i solve this problem? Your help is greatly appreciated.
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from matplotlib import style
import tkinter as tk
from tkinter import ttk
from tkinter import *
import random
import numpy as np
LARGE_FONT = ('Verdana',12)
style.use('ggplot')
from matplotlib import pyplot as plt
class PageOne(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
label = tk.Label(self,text='Experiment', font = LARGE_FONT)
label.pack(pady=10,padx=10)
self.adding_widgets()
button2= ttk.Button(self,text='Validate', command=self.draw)
button2.pack()
button3= ttk.Button(self,text='Erase', command=self.dlet)
button3.pack()
self.lines_pts=[]
self.f1= Figure(figsize=(5,5),dpi=100)
self.b=self.f1.add_subplot(1,1,1)
self.b.set_xlim([0,10])
self.b.set_ylim([0,10])
self.canvas = FigureCanvasTkAgg(self.f1, self)
self.canvas.draw()
self.canvas.get_tk_widget().pack(side=tk.TOP,fill=tk.BOTH,expand=True)
self.f1.canvas.mpl_connect('pick_event', self.on_pick)
self.lines =[]
def adding_widgets(self,*args):
labfram=ttk.LabelFrame(self,width=100,height=100,text='Enter Member Coordinates',labelanchor=N)
labfram.pack()
l1=ttk.Label(labfram,text='x0')
l1.pack(side='left')
self.x0=Entry(labfram,width=10,)
self.x0.pack(side='left')
l2=ttk.Label(labfram,text='y0')
l2.pack(side='left')
self.y0=Entry(labfram,width=10)
self.y0.pack(side='left')
l3=ttk.Label(labfram,text='x1')
l3.pack(side='left')
self.x1=Entry(labfram,width=10)
self.x1.pack(side='left')
l4=ttk.Label(labfram,text='y1')
l4.pack(side='left')
self.y1=Entry(labfram,width=10)
self.y1.pack(side='left')
def draw(self):
p0 = float(self.x0.get()), float(self.y0.get())
p1 = float(self.x1.get()), float(self.y1.get())
self.lines_pts.append((p0,p1))
for p0,p1 in self.lines_pts:
x0, y0, x1, y1 = *p0, *p1
X = x0,x1
Y = y0,y1
ax = self.b.plot(X,Y, 'r', linewidth=4,picker=5 )
self.lines.append(ax)
self.canvas.draw()
def dlet(self):
self.b.lines.remove(self.b.lines[-1])
self.canvas.draw()
def on_pick(self,event):
w=Tk()
w.title('Channel Select')
w.geometry('250x50')
n = StringVar()
ch = ttk.Combobox(w, width = 15 , textvariable = n)
ch['values'] = ('CH1','CH2','CH3','CH4','CH5','CH6','CH7','CH8','CH9','CH10')
ch.grid(column=1,row=0)
ch_label = ttk.Label(w,text='Select Your Channel')
ch_label.grid(column=0,row=0)
ch_button = ttk.Button(w,text='OK',command=lambda: print ('value is:'+ n.get()))
ch_button.grid(column=1,row=1)
for line in self.lines:
line = event.artist
xdata, ydata = line.get_data()
ind = event.ind
print('on pick line:', np.array([xdata[ind], ydata[ind]]).T)
app = PageOne()
app.mainloop()
It's here do you really want to create a new window?
def on_pick(self,event):
w=Tk()
w.title('Channel Select')
w.geometry('250x50')
Take note that when you do this w=Tk() you are creating an instance of an object a NEW OBJECT which is Tk() so that's why it creates a new window. What is your goal are you really trying to pop up a window whenever you click cause if not you I think you can remove this.
----UPDATE---
So the reason why your window keeps popping out is because on your function you create an instance of an object with it. Then I tried putting the w=Tk() away from the function but still it would create or show that object cause it is within the mainloop.
Alternative solution is you can check if once that window exists or its state is normal then you can just do a focus.
Here is the code that I've added only on your on_pick function. I also added on your __init__ method a self.w = None to just set the w variable to None initially.
OVERALL this are only the changes made
def on_pick(self,event):
try:
if self.w.state() == "normal":
self.w.focus()
except BaseException as on_pick_error:
self.w=Toplevel()
self.w.title('Channel Select')
self.w.geometry('250x50')
n = StringVar()
ch = ttk.Combobox(self.w, width = 15 , textvariable = n)
ch['values'] = ('CH1','CH2','CH3','CH4','CH5','CH6','CH7','CH8','CH9','CH10')
ch.grid(column=1,row=0)
ch_label = ttk.Label(self.w,text='Select Your Channel')
ch_label.grid(column=0,row=0)
ch_button = ttk.Button(self.w,text='OK',command=lambda: print ('value is:'+ n.get()))
ch_button.grid(column=1,row=1)
for line in self.lines:
line = event.artist
xdata, ydata = line.get_data()
ind = event.ind
print('on pick line:', np.array([xdata[ind], ydata[ind]]).T)
You might wonder what this does, it just checks if the state of your self.w which happens to be your window that pops out, it just checks if its state is equal to "normal" meaning that it is active, then when it is it will just do a .focus() and focus on that current window. Else it would create a new window which is this self.w=Toplevel() and so on.
try:
if self.w.state() == "normal":
self.w.focus()
except BaseException as on_pick_error:
self.w=Toplevel()
self.w.title('Channel Select')
self.w.geometry('250x50')
Why is it Toplevel instead of Tk?
I would suggest to have it Toplevel however it is up to you to decide since Toplevel and Tk I think might just have the same properties but do note they are not the same so it's up to you.
I've written a pretty complex plotting utility for user interaction. interaction is handled via fig.canvas.mpl_connect('key_press_event', on_key) and fig.canvas.mpl_connect('pick_event', onpick)
What i'd like is using the key_press_event when the user presses 'c' it will raise a tkinter.Entry window for the user to add a comment.
This comment is then extracted and stored, and the user moved on.
The tkinter.Entry implementation works perfectly fine in isolation.
So i have tkComment.py here:
import tkinter
from tkinter import Entry, StringVar
class tkComment(object):
def __init__(self):
root = self.root = tkinter.Tk()
tkinter.Button(root,text="save comment",command=root.destroy).pack(anchor=tkinter.S,side=tkinter.BOTTOM)
entryVar = self.entryVar = tkinter.StringVar()
entry = self.entry = Entry(master=root, width=60, textvariable=self.entryVar)
entry.pack(side=tkinter.TOP)
I'm using IPython for easy testing (but the problem persists outside of Ipython). I import this tkComment and have a few functions for testing
from __future__ import print_function
import matplotlib
matplotlib.use("TkAgg") #necessary for use with tkinter
from tkComment import tkComment
def runtkc():
global tkC
tkC = tkComment()
tkC.root.mainloop()
def print_comment():
global tkC
print('comment for this source:', tkC.entryVar.get())
from matplotlib import pyplot as plt
def on_key(event):
global tkC
if event.key == 'c':
runtkc()
if event.key == 'C':
print_comment()
def test_plot():
fig,ax = plt.subplots(1)
ax.plot(range(10))
keyID = fig.canvas.mpl_connect('key_press_event', on_key)
plt.show()
Now, the problem:
if I just call runtkc() then print_comment() everything works perfectly fine.
if I call these functions through a key-press in test_plot() the stored string is empty. Even when printed from within tkComment.tkComment !!
How do I get these fellas to cooperate?
I think the problem is that matplotlib is creating its own instance of tkinter. So when you call root = self.root = tkinter.Tk() in tkComment.__init__ you have two instances of tkinter, and matplotlib is talking to a different one.
if you instead call
root = self.root = tkinter.Toplevel()
this problem is avoided.
I am having a problem printing the value of a slider bar. I created a button below the slider to do so, but it prints before it is pressed and doesn't print when pressed.
I also have the problem of making teh slider horizontal. I know it is "orient=HORIZONTAL" and it works when it is not in a class but when it is in a class it gives me trouble.
Here is the code, and as always, thank you so much!! :)
import Tkinter as tki # Tkinter -> tkinter in Python 3
import tkMessageBox
class GUI(tki.Tk):
def __init__(self):
tki.Tk.__init__(self)
self.wm_title("Menu Title")
RWidth=500
RHeight=300
self.geometry(("%dx%d")%(RWidth,RHeight))
menubar = tki.Menu(self)
# create a pulldown menu, and add it to the menu bar
menu0 = tki.Menu(menubar, tearoff=0)
menu0.add_command(label="Run Slider", command=self.slider_prompt)
menu0.add_separator()
menu0.add_command(label="Exit", command=self.quit)
menubar.add_cascade(label="Slider", menu=menu0)
# display the menu
self.config(menu=menubar)
def slider_prompt(self, msg='This is a slider'):
slider_window= self.top = tki.Toplevel(self)
slider_window.title("Slider Title")
RWidth=300
RHeight=200
slider_window.geometry(("%dx%d")%(RWidth,RHeight))
label0 = tki.Label(slider_window, text=msg)
label0.pack()
slider = tki.Scale(slider_window, from_=100, to=1000) # have to make horizontal
slider.pack()
#put button here that starts the program (pass the slider value)
button_cheat = tki.Button( slider_window, text='Print Value', command=self.print_slide_value(slider.get()) )
button_cheat.pack()
button_close = tki.Button(slider_window, text='Exit', command=lambda: self.top.destroy())
button_close.pack()
def print_slide_value(self, slider_value):
print slider_value
gui = GUI()
gui.mainloop()
When you use a command argument you have to pass there a reference to a callable object. Function that will be called after the event. What you're doing is that you're passing there the result from self.print_slide_value(slider.get()), which is None.
Instead do:
def print_slide_value():
print slider.get()
button_cheat = tki.Button(slider_window, text='Print Value', command=print_slide_value)
Also I had no problems with this:
slider = tki.Scale(slider_window, from_=100, to=1000, orient=tki.HORIZONTAL)