So I've created a GUI and I need a vertical scrollbar, however the scrollbar seems to be taller than its parent frame, and doesn't scroll. I have no idea why but here's the code I have so far:
import tkinter as Tk
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
root = Tk.Tk()
RightPane = Tk.Frame(root)
RightPane.grid()
class Graph:
#Draws and return a placeholder for a graph
##param parent is the Tk.Frame parent obj
##param title is the (string) title for the graph
def __init__(self, parent, title=''):
#Initialise graph
self.title = title
self.fig = Figure(figsize=(4,4))
self.plot = self.fig.add_subplot()
self.plot.set_title(self.title, fontsize=10)
self.plot.set_ylim(top=1)
self.plot.set_xlim(right=255)
self.plot.set_ylabel("Certainty", fontsize=8)
self.plot.set_xlabel("Pixel Value", fontsize=8)
#Draw
self.canvas = FigureCanvasTkAgg(self.fig, master=parent)
self.canvas.get_tk_widget().pack()
self.canvas.draw()
return
#Result Graphs -------------------------------------------
ResultFrame = Tk.Frame(RightPane)
ResultFrame.grid_columnconfigure(0, weight=1)
ResultFrame.grid(row=2, column=2, rowspan=14, padx=(10,0), pady=(10,0), sticky='nwe')
ResultScrollable = Tk.Canvas(ResultFrame)
ResultScrollable.grid(row=0, column=0, padx=(5,0), sticky='we')
graphCollection = []
for i in range(10):
title = 'Certainty that image is digit: {}'.format(i)
graphCollection.append(Graph(ResultScrollable, title=title))
ResultFrameVbar = Tk.Scrollbar(ResultFrame, orient='vertical', command=ResultScrollable.yview)
ResultFrameVbar.grid(row=0, column=1, sticky='nswe')
ResultScrollable.config(yscrollcommand=ResultFrameVbar.set, scrollregion=ResultScrollable.bbox('all'))
root.mainloop()
Couldn't find anything on the internet, so any help would really be appreciated. Thank you in advance.
You are creating canvases (self.canvas) inside canvas (ResultScrollable) and using pack() on those canvases. So ResultScrollable.bbox('all') will not include those canvases. You should create an internal Frame and assocate it with create_window(), then put those canvases inside the internal Frame:
internalFrame = Tk.Frame(ResultScrollable)
ResultScrollable.create_window(0, 0, window=internalFrame, anchor='nw')
graphCollection = []
for i in range(10):
title = 'Certainty that image is digit: {}'.format(i)
graphCollection.append(Graph(internalFrame, title=title))
Related
I've been having problems with embedding my MatPlotLib Graph in Tkinter, and after doing some searching on Google, and the MatPlotLib website, the best I could get was the standard method:
import tkinter
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
fig = Figure(figsize=(5, 4), dpi=100)
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.draw()
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1)
toolbar = NavigationToolbar2Tk(canvas, root) toolbar.update()
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1)
Now if I try to replace the packing layout with a .grid (and remove the .pack() parameters), I get a bunch of errors, and no matter how many Google searches I have tried, all of the methods of embedding a MatPlotLib graph in Tkinter are only using the pack method. Can someone help me out with this? I want to embed the graph, but using the grid method, as the rest of the layout of my GUI application is .grid layout.
Another problem I'm having with the navigation toolbar in Tkinter is the fact that the navigation toolbar can apparently be customized (At least according to SentDex [5:18]). He doesn't seem to go over how I can do this, which makes it difficult for me, because I'm not very happy with MatPlotLib's buttons (They look very archaic and outdated).
Can someone please help me out with this? When I only put the graph in, it seems to work just fine, but I get issues when trying to put in the Navigation Toolbar with the graph as well. Any help on this would be appreciated. Thanks!
Here is a simple plot and navigation toolbar inside tkinter window using grid geometry manager only.
import tkinter as tk
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
window = tk.Tk()
btn = tk.Label(window, text='A simple plot')
btn.grid(row=0, column=0, padx=20, pady=10)
x = ['Col A', 'Col B', 'Col C']
y = [50, 20, 80]
fig = plt.figure(figsize=(4, 5))
plt.bar(x=x, height=y)
# You can make your x axis labels vertical using the rotation
plt.xticks(x, rotation=90)
# specify the window as master
canvas = FigureCanvasTkAgg(fig, master=window)
canvas.draw()
canvas.get_tk_widget().grid(row=1, column=0, ipadx=40, ipady=20)
# navigation toolbar
toolbarFrame = tk.Frame(master=window)
toolbarFrame.grid(row=2,column=0)
toolbar = NavigationToolbar2Tk(canvas, toolbarFrame)
window.mainloop()
Output GUI
I have not worked on customizing the navigation toolbar so I haven't included any solution for that part. But I'll look into it surely and update you if I find something useful. Hope you find this helpful.
I managed to get a simple app to control a matplotlib graph with window resizing.
I haven't used the navigation bar feature so that might be an addition to this framework
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from google.cloud import bigquery
import os, json, sys
from time import time
import pandas as pd
class SliderGraph:
def __init__(self, master):
self.master = master
# with open(self.resource_path(config_file)) as fp:
# self.config = json.load(fp)
# os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = self.resource_path(creds_file)
# self.projectID = self.config['projectID']
# self.datasetID = self.config['datasetID']
# last_used_freq = self.config['last_used_frequency']
# last_used_delta = self.config['last_used_delta']
self.bounds = 1
self.frame = tk.Frame(master)
self.fig = Figure()
row = 0
self.ax = self.fig.add_subplot(111)
self.ax.set_xlabel("Run Numbers")
self.ax.set_ylabel("UDD")
self.ax.set_ylim([-self.bounds,self.bounds])
self.canvas = FigureCanvasTkAgg(self.fig, master=master) # , width=win_width, height=(win_height-50))
self.canvas.draw()
self.canvas.get_tk_widget().grid(row=row, columnspan=2, sticky='nsew')
row+=1
self.table_label = tk.Label(master, text="Enter BigQuery Table Name")
self.table_label.grid(row=row, column=0)
# row += 1
self.table_name = tk.Entry(master)
# self.table_name.insert(0,self.config['last_used_table'])
self.table_name.grid(row=row, column=1, sticky='ew')
row += 1
self.get_table_button = tk.Button(master, text="Get Table Data and Plot", command=self.plot_data)
self.get_table_button.grid(row=row, columnspan=2)
row += 1
self.frequency_slider = tk.Scale(master, from_=400, to=4500, orient=tk.HORIZONTAL, command=self.update_plot)
# self.frequency_slider.set(last_used_freq)
self.frequency_slider.grid(row=row,columnspan=2, sticky="nsew")
row += 1
self.frequency_entry = tk.Entry(master)
# self.frequency_entry.insert(0,last_used_freq)
self.frequency_entry.grid(row=row, columnspan=2)
row += 1
self.delta_slider = tk.Scale(master, from_=-500, to=500, orient=tk.HORIZONTAL, command=self.update_plot)
# self.delta_slider.set(last_used_delta)
self.delta_slider.grid(row=row, columnspan=2, sticky="ensw")
row += 1
self.delta_entry = tk.Entry(master)
# self.delta_entry.insert(0, last_used_delta)
self.delta_entry.grid(row=row, columnspan=2)
row += 1
self.get_table_button = tk.Button(master, text="Autoscale", command=self.autoscale)
self.get_table_button.grid(row=row,columnspan=2)
row += 1
tk.Grid.columnconfigure(master, 0, weight=1)
tk.Grid.columnconfigure(master, 1, weight=1)
tk.Grid.rowconfigure(master, 0, weight=5)
for x in range(1,row):
tk.Grid.rowconfigure(master, x, weight=0)
master.protocol('WM_DELETE_WINDOW', self.close)
self.df = None
self.frequency_list = []
self.series = None
self.elapsed = time()*1000
def plot_data(self):
self.ax.clear()
self.client = bigquery.Client(project=self.projectID)
self.tableID = f"`{self.datasetID}.{self.table_name.get()}`"
QUERY = (
f"SELECT * EXCEPT (testCount,elapsed_run_time_ms_,moleculeValue,onboardTemp1,onboardTemp2,temp,tempControl,\
logamp, txrx,timestamp) FROM {self.tableID} ORDER BY runCount ASC;"
)
# query_job = self.client.query(QUERY)
# rows = query_job.result()
self.df = pd.read_gbq(QUERY,self.projectID)
for col in self.df.columns:
if 'runCount' in col:
continue
self.frequency_list.append(int(col.replace('_','')))
self.frequency_slider.configure(from_=min(self.frequency_list),to=max(self.frequency_list))
self.delta_slider.configure(from_=-max(self.frequency_list),to=max(self.frequency_list))
freq = f'_{self.frequency_slider.get()}'
freq2 = f'_{self.frequency_slider.get() + self.delta_slider.get()}'
self.series = self.df[freq] - self.df[freq2]
self.series.plot(ax=self.ax)
self.ax.set_ylim([-self.bounds,self.bounds])
self.fig.canvas.draw()
self.fig.canvas.flush_events()
pass
def update_plot(self, newslider):
try:
self.ax.clear()
freq = f'_{self.frequency_slider.get()}'
freq2 = f'_{self.frequency_slider.get() + self.delta_slider.get()}'
self.series = self.df[freq] - self.df[freq2]
self.series.plot(ax=self.ax)
zero = self.series.mean()
self.ax.set_ylim([zero-self.bounds, zero+self.bounds])
self.fig.canvas.draw()
self.fig.canvas.flush_events()
# self.master.update_idletasks()
if ((time()*1000)-self.elapsed > 100):
self.elapsed = time()*1000
self.frequency_entry.delete(0,'end')
self.frequency_entry.insert(0,freq.replace('_',''))
self.delta_entry.delete(0,'end')
self.delta_entry.insert(0,self.delta_slider.get())
except:
pass
def play_runs(self):
pass
def autoscale(self):
self.ax.clear()
self.series.plot(ax=self.ax)
self.ax.relim()
self.ax.autoscale()
zero = self.series.mean()
self.bounds = self.series.max() - self.series.min()
self.ax.set_ylim([zero-self.bounds,zero+self.bounds])
self.fig.canvas.draw()
self.fig.canvas.flush_events()
def close(self):
self.master.destroy()
if __name__=="__main__":
root = tk.Tk()
slider_fig = SliderGraph(root)
root.mainloop()
I want to show a plot within one of the pages in my GUI. The data plotted, theData will constantly change. Whenever theData is changed, the value of valueChangedtheData is written to 1. The mapping of time and data is here:
xsize=100
xdata,ydata = [],[]
def data_gen():
t = data_gen.t
global theData
global valueChangedtheData
while True:
if (valueChangedtheData == 1):
valueChangedtheData = 0;
t+=0.1
val=float(theData);
if val>1000:
continue
yield t, val
else: pass
def animate(data):
t, val = data
if t>-1:
xdata.append(t)
ydata.append(val)
if t>xsize: # Scroll to the left.
a.set_xlim(t-xsize, t)
line.set_data(xdata, ydata)
return line,
def on_close_figure(event):
sys.exit(0)
data_gen.t = -1
f = plt.figure()
f.canvas.mpl_connect('close_event', on_close_figure)
#f = Figure(figsize=(5,5), dpi=100)
a = f.add_subplot(111)
line, = a.plot([], [], lw=2)
a.set_ylim(0, 250)
a.set_xlim(0, xsize)
a.grid()
I've defined my the container of my GUI as such:
class Gui(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
container = Frame(self)
container.pack(side="top", fill = "both", expand = TRUE)
container.grid_rowconfigure(0, weight = 1)
global theData;
self.MyReading = StringVar()
self.frames={}
for F in (StartPage, PageOne):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row = 0, column = 0, sticky = "nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
and the page showing the plot:
class StartPage(Frame): #The Graphical Page
def __init__(self, parent, controller):
Frame.__init__(self,parent)
label = Label(self, text="StartPage")
label.pack()
label1 = Label(self, textvariable = controller.theData)
label1.pack
canvas = FigureCanvasTkAgg(f, self)
canvas.draw() #changed from show to draw
canvas.get_tk_widget().pack(side=BOTTOM, fill=BOTH, expand=True)
toolbar = NavigationToolbar2Tk(canvas, self)
toolbar.update()
canvas._tkcanvas.pack(side=TOP, fill=BOTH, expand=True)
And in order to start the animation and Gui:
root = Gui()
update_reading()
ani = animation.FuncAnimation(f, animate, data_gen, blit = False, interval=100, repeat = False)
root.mainloop()
With update_reading() updating the label:
def update_reading():
global theData
global valueChangedtheData
theData = randint(1,20) #This is just an example of the changing value
print(theData)
valueChangedtheData = 1;
root.MyReading.set(str(theData));
root.after(100,update_reading)
However, after adding the canvas on the page, all of the labels that rely on the variable classes would refuse to shop-up, including the value for theData but the plot is graphing. Also, labels that show images would also refuse to show-up. After commenting the canvas, data mapping and animation, the label would appear back. Am I missing an important initializing code? Also, during plotting, there is a considerable "sluggishness" happening in the gui window. Could this be alleviated through a better code writing? Thanks
Imports:
from tkinter import *
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
import matplotlib.animation as animation
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import style
from random import randint
EDIT: Added missed and necessary code
EDIT2: Included all the imports
I am making an embeded matplotlib graph GUI program.
I want to make overlaid graphs in upper graph window when users click the "Update" button.
But, There is no response when I click "Update" button.
I am using Spyder Python 3.6 Version.
Below is what I wrote.
import matplotlib.pyplot as plt
import csv
import numpy as np
import tkinter as tk
from tkinter import ttk
import matplotlib as plt
plt.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
from matplotlib import style
class Analysis_app(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
tk.Tk.wm_title(self, "SIM Analyser")
right_f = tk.Frame(self)
right_f.pack(side=tk.RIGHT)
self.entry1 = ttk.Entry(right_f).pack()
self.entry2 = ttk.Entry(right_f).pack()
self.entry3 = ttk.Entry(right_f).pack()
self.entry4 = ttk.Entry(right_f).pack()
self.entry5 = ttk.Entry(right_f).pack()
Button1 = ttk.Button(right_f, text='Update', command=self.plot).pack(side=tk.BOTTOM)
self.left_f = tk.Frame(self)
self.left_f.pack(side=tk.LEFT)
f = Figure(figsize=(10,6), dpi=100)
self.upplot = f.add_subplot(211)
self.botplot = f.add_subplot(212)
a =self.upplot
a.clear()
a.plot([1,2,3],[1,2,3])
canvas = FigureCanvasTkAgg(f, self)
canvas.show()
canvas.get_tk_widget().pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True)
toolbar = NavigationToolbar2TkAgg(canvas, self)
toolbar.update()
canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
self.mainloop()
def plot(self):
a =self.upplot
a.clear()
a.plot([1,2,3],[5,2,5])
You get no response because the canvas is not redrawn after changing the content of the plot.
The solution is to replace canvas = FigureCanvasTkAgg(f, self) by self.canvas = FigureCanvasTkAgg(f, self) so that you can redraw the canvas in self.plot:
def plot(self):
a = self.upplot
a.clear()
a.plot([1,2,3],[5,2,5])
self.canvas.draw_idle()
And then you should see the change after clicking on the update button.
I have a programme with various controls, a live webcam feed and a matplotlib figure based on the webcam data. I would like all the controls to be located in a left column, and the webcam and matplotlib figure to be located in a right column.
Attempts to use .grid() methods, canvases and frames etc don't work - the python script just fails to execute and hangs indefinitely.
How can this be achieved?
Minimum working example:
import Tkinter as tk
import cv2
from PIL import Image, ImageTk
import numpy as np
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
root = tk.Tk()
root.bind('<Escape>', lambda e: root.quit())
lmain = tk.Label(root)
lmain.pack()
class Controller(tk.Frame):
def __init__(self, parent=root, camera_index=0):
self.camera_index = 0
frame = tk.Frame.__init__(self, parent,relief=tk.GROOVE,width=100,height=100,bd=1)
self.pack()
self.parent = parent
self.var = tk.IntVar()
self.parent.title('Laser Beam Profiler')
labelframe = tk.LabelFrame(parent, text="This is a LabelFrame")
labelframe.pack(fill="both", expand="yes") #.grid(row=0, column=0)
self.plot = tk.Button(labelframe, text = "Plot", command = self.refresh_plot)
self.plot.pack()
self.exit = tk.Button(labelframe, text = "Exit", command = self.close_window, compound = tk.BOTTOM)
self.exit.pack()
self.init_camera()
self.show_frame() #initialise camera
self.make_fig()
def make_fig(self):
self.fig = Figure(figsize=(4,4), dpi=100)
self.ax = self.fig.add_subplot(111)
canvas = FigureCanvasTkAgg(self.fig, self)
canvas.show()
canvas.get_tk_widget().pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True)
toolbar = NavigationToolbar2TkAgg(canvas, self)
toolbar.update()
canvas._tkcanvas.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True)
def refresh_plot(self):
self.ax.plot(self.img[0])
self.fig.canvas.draw()
self.ax.clear()
print 'updated plot'
def init_camera(self):
width, height = 400, 300
self.cap = cv2.VideoCapture(self.camera_index)
if not self.cap:
raise Exception("Camera not accessible")
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
def show_frame(self):
_, frame = self.cap.read()
cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
cv2.putText(cv2image,"Laser Beam profiler", (50,50), cv2.FONT_HERSHEY_SIMPLEX, 1, (255,255,255))
dim = np.shape(cv2image)
img = Image.fromarray(cv2image)
imgtk = ImageTk.PhotoImage(image=img)
lmain.imgtk = imgtk
lmain.configure(image=imgtk)
lmain.after(10, self.show_frame)
self.img = frame
def close_window(self):
self.parent.quit()
self.parent.destroy()
Controller().mainloop()
For the minimum working example I am trying to get the 'plot' and 'exit' buttons on the left hand side, and the webcam view with the matplotlib figure on the right. Currently it just places the widgets in a long column that spills off the screen.
The line
labelframe = tk.LabelFrame(parent, text="This is a LabelFrame")
probably should have self instead of parent. After this
labelframe.pack(fill="both", expand="yes", tk.LEFT)
should work. Since the Controller and the labelframe had the same parent and pack method for the Controller was called first, it placed restrictions where the labelframe could appear.
In Controller.__init__() there is a call to self.pack() in a little bit unusual place, it is usually left to be called after the widget has been instantiated i.e.
c = Controller(root)
c.pack()
root.mainloop()
I'm embedding a pyplot graph in a Tkinter frame. There's a thick black line around the entire figure that I'd like to get rid of. It only appears when my Tkinter window is actively selected:
I'm using Python 2.7.6 on Mac OS 10.9. Here's my code:
from Tkinter import *
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
class PlotFrame(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.fig = plt.figure()
self.plot = self.fig.add_subplot(1, 1, 1)
self.canvas = FigureCanvasTkAgg(self.fig, master=self)
self.canvas.show()
self.canvas.get_tk_widget().grid(row=0, column=0, sticky=(N), padx=20, pady=20)
root = Tk()
plot_frame = PlotFrame(root)
plot_frame.grid(row=0, column=0)
root.mainloop()
Thanks!
That is controlled by the highlightthickness and highlightcolor attributes of the canvas widget. Set highlightthickness to zero if you don't want to see it.