I am trying to convert some code from the matplotlib documentation site that embeds a matplotlib graph into tkinter from a module containing defs into a module containing a class. This code is taken directly from matplotlib (https://matplotlib.org/gallery/user_interfaces/embedding_in_tk_sgskip.html) and works exactly as expected. However for my purposes I need this in the format of a class rather than a grouping of defs within a module. Here is the original matplotlib code:
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)
fig.add_subplot(111).plot(t, 2 * np.sin(2 * np.pi * t))
canvas = FigureCanvasTkAgg(fig, master=root) # A tk.DrawingArea.
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)
def on_key_press(event):
print("you pressed {}".format(event.key))
key_press_handler(event, canvas, toolbar)
canvas.mpl_connect("key_press_event", on_key_press)
def _quit():
root.quit() # stops mainloop
root.destroy() # this is necessary on Windows to prevent
# Fatal Python Error: PyEval_RestoreThread: NULL
tstate
button = tkinter.Button(master=root, text="Quit", command=_quit)
button.pack(side=tkinter.BOTTOM)
tkinter.mainloop()
# If you put root.destroy() here, it will cause an error if the window
# is closed with the window manager.
It is easy enough to convert the figure creation portion of this code into a class, but I am unable to figure out how to get the on_key_press and the toolbar sections to code properly.
This is my class code, which creates the plot but all my attempts at adding the toolbar and having key presses work have failed.
class matplotlibAsClass:
def __init__(self, master):
self.master = master
self.frame = Frame(self.master)
self.create_chart()
self.frame.pack(expand=YES, fill=BOTH)
def create_chart(self):
fig = Figure(figsize=(5, 4), dpi=100)
t = np.arange(0, 3, .01)
fig.add_subplot(111).plot(t, 2 * np.sin(2 * np.pi * t))
canvas = FigureCanvasTkAgg(fig, self.master)
canvas.draw()
canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1)
if __name__ == '__main__':
root = Tk()
matplotlibAsClass(root)
root.mainloop()
Any help on how to convert the full matplotlib code, where the key presses and the toolbar works, into a fully functioning class would be greatly appreciated.
Here is code I have written that does not work:
The following code has been corrected and now works properly.
class matplotlibAsClass:
def __init__(self, master):
self.master = master
self.frame = Frame(self.master)
self.create_chart()
self.frame.pack(expand=YES, fill=BOTH)
def create_chart(self):
self.master.wm_title("Embedding in Tk")
fig = Figure(figsize=(5, 4), dpi=100)
t = np.arange(0, 3, .01)
fig.add_subplot(111).plot(t, 2 * np.sin(2 * np.pi * t))
canvas = FigureCanvasTkAgg(fig, self.master)
canvas.draw()
canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1)
canvas.mpl_connect("key_press_event", self.on_key_press)
toolbar = NavigationToolbar2Tk(canvas, self.master)
toolbar.update()
canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1)
self.button = Button(self.master, text="Quit",command=self._quit)
self.button.pack(side=BOTTOM)
def on_key_press(event):
print("you pressed {}".format(event.key))
key_press_handler(event, canvas, toolbar)
def _quit(self):
self.master.quit() # stops mainloop
#self.master.destroy()
if __name__ == '__main__':
root = Tk()
matplotlibAsClass(root)
root.mainloop()
when I run this code I get the following error:
File
"/Users/Me/Mee/python_db_programs/simpletestgui_sc_two.py",
line 104, in create_stockchart
canvas.mpl_connect("key_press_event", on_key_press)
NameError: name 'on_key_press' is not defined
After changing the on_key_press to self.on_key_press I get the following error:
File "/Users/Me/Mee/python_db_programs/simpletestgui_sc_two.py", line
111, in create_stockchart
self.button = Button(self.master,
text="Quit",command=self._quit())
TypeError: _quit() takes 0 positional arguments but 1 was given
After correcting the def _quit() to def _quit(self) I get the following error:
File
"/Users/Me/Mee/python_db_programs/simpletestgui_sc_two.py",
line 111, in create_stockchart
self.button = Button(self.master, text="Quit",
command=self._quit())
_tkinter.TclError: can't invoke "button" command: application has been
destroyed
Based upon the previous error I commented out the self.master.destroy() portion of the def _quit and now the chart loads and the toolbar works but the quit button doesn't actually cause the widget to quit.
The final correction, which I didn't fully catch from a previous comment was to change command=self._quit() to command=self._quit and now everything works correctly.
Related
I have created a GUI where the user can select a date from a drop down using the tkcalendar DateEntry widget. I would like to allow the user the option of not selecting a date and leaving this widget blank. However, even if no date is selected the widget returns the current date.
Is there a way to configure the DateEntry to allow for no selection rather than defaulting to the current date if the user does not select a date?
Below is a subset of my code:
import pandas as pd
from tkinter import *
from tkinter.ttk import *
import tkinter as tk
from tkcalendar import DateEntry
class Window(Frame):
def __init__(self, master):
Frame.__init__(self,master)
master.title('Solar Master Project Tracking')
# create canvas for scrollable window
canvas = Canvas(root)
canvas.grid(row=1,column=0, columnspan=2)
# create vertical scrollbar and connect it to the canvas
scrollBar = tk.Scrollbar(root, orient='vertical', command = canvas.yview)
scrollBar.grid(row=1, column=2, sticky = 'ns')
canvas.configure(yscrollcommand=scrollBar.set)
def update_scroll_region(event):
canvas.configure(scrollregion=canvas.bbox("all"))
def _on_mousewheel(event):
canvas.yview_scroll(int(-1*(event.delta/120)), "units")
# create a frame for the widgets in the scrollable canvas
scroll_frame = Frame(canvas)
scroll_frame.bind("<Configure>", update_scroll_region)
canvas.create_window(0,0, anchor='nw', window = scroll_frame)
canvas.bind_all("<MouseWheel>", _on_mousewheel)
# Proposal Date
self.L18 = Label(scroll_frame, text="Proposal Date:",font=('TKDefaultFont', 8, 'bold'))
self.L18.grid(row=21, column=0, sticky=W)
self.prop_date_selection = DateEntry(scroll_frame, width = 25, background = 'LightCyan3',
foreground ='white',borderwidth=2)
self.prop_date_selection.grid(row=21, column=1,sticky=E)
self.prop_date_selection.delete(0,"end")
# SUBMIT INFORMATION
self.button = tk.Button(root, text="Insert / Update Project",font=('TKDefaultFont', 10, 'bold'),
relief=RAISED, command = self.store_user_inputs, bg = "gray80")
self.button.grid(row=25, column = 0, columnspan=8, sticky = 'EW')
# STORE USER INPUT
def store_user_inputs(self):
prop_date_selection = self.prop_date_selection.get_date()
global params
params = [prop_date_selection]
root.destroy()
if __name__ == "__main__":
root = Tk()
Window(root)
root.mainloop()
You can create a class inheriting from tkcalendar.DateEntry and modify the get_date() method to return None when the DateEntry is empty:
import tkcalendar
class DateEntry(tkcalendar.DateEntry):
def get_date(self):
if not self.get():
return None
self._validate_date()
return self.parse_date(self.get())
For those who find this post.
Modifying get_date() didn't change anything in my case. But the following did:
class MyDateEntry(tkcalendar.DateEntry):
def _validate_date(self):
if not self.get():
return True # IMPORTANT!!! Validation must return True/False otherwise it is turned off by tkinter engine
return super()._validate_date()
The need for two different geometric plotters arises because i want to display a sophisticated GUI with text, entry fields, pictures, buttons, alongside an animated matplotlib graph on a FigureCanvasTkAgg with a NavigationToolbar2Tk. The NavigationToolbar2Tk fails to work when any geometric plotter other than pack() is used. I have tried over the course of the week several different methods of putting the NavigationToolbar2Tk into its own frame, but in the examples, only a single frame exists. I came to the conclusion that ideally, splitting my frame ~ PageOne ~ into two subframes or instantiating two frames which make up page one.
I have several frames as shown in my code below and my knowledge of python is rudimentary so i don't have the imagination or know how to circumvent this problem. I have gutted my code to show the problem concisely and Included my Imports.
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
from matplotlib.pylab import *
from matplotlib import style
import matplotlib.animation as animation
import matplotlib.pyplot as plt
import matplotlib.dates as dates
import matplotlib.ticker as mticker
import tkinter as tk
from tkinter import ttk
from tkinter import *
from PIL import Image, ImageTk
from mpl_toolkits.axes_grid1 import host_subplot
from gpiozero import CPUTemperature
import time
import datetime as dt
import pandas as pd
import numpy as np
#************************************************************************#
# Format Graph_1 [ax1] onto fig1 at subplot [ row = 1, col = 1, index = 1 ]
# Set Figure Text
font = {'size' : 9}
matplotlib.rc('font', **font)
# Setup Figure
fig1 = Figure()
# Define Axis 1 for Vin and Vout
ax1 = fig1.add_subplot(1, 1, 1)
#subplots_adjust(left=0.05, bottom=0.10, right=0.55, top=0.8, wspace=0.2)
ax1.minorticks_on()
ax1.grid(b=True, which='major', color='k', linestyle='-')
ax1.grid(b=True, which='minor', color='k', linestyle=':')
ax1.set_title("PI3740 Paramaters", fontsize = 12)
ax1.set_xlabel("Relative Time (s)", fontsize = 10)
ax1.set_ylabel("Voltage (V)", fontsize =10)
# Define Axis 2 for Iout Which is tied to Axis 1's X-Axis
ax2 = ax1.twinx()
ax2.set_ylabel("Output Current (A)")
# Parameters
x_len = 500 # Resolution [Number of Points in Window]
x_max = 2 # X-Axis Range [ (Step)ms Samp^-1 * (x_len)Samp = X_Range]
y_range = [0, 50] # Range of possible Y values to display
# Create figure for plotting
steps = (x_max/x_len)
stepms = steps * 1000
xs = np.arange(0, x_max, steps) # xs is a list from 0 to 10 in steps of 0.01 [A list refers to a 1D Array]
ys1 = [0] * x_len # ys is a list indexed from ys[0] to ys[999] all containing 0 # Vin
ys2 = [0] * x_len # ys is a list indexed from ys[0] to ys[999] all containing 0 # Vout
ys3 = [0] * x_len # ys is a list indexed from ys[0] to ys[999] all containing 0 # Iout
ax1.set_ylim(y_range) # Y-Axis Voltage Range Set
ax2.set_ylim(0, 10) # Y-Axis Current Range Set
ax1.set_xlim(0, x_max) # X-Axis Shared Relative Time Range Set
# Create a blank line. We will update the line in animate
line1, = ax1.plot(xs, ys1, 'b-', label = "Vin")
line2, = ax1.plot(xs, ys2, 'g-', label = "Vout")
line3, = ax2.plot(xs, ys3, 'r-', label = "Iout")
# Create a Legend
ax1.legend([line1, line2],[line1.get_label(), line2.get_label()])
ax1.legend(bbox_to_anchor = (0.,0.99,1.,.102), loc = 3, ncol = 2, borderaxespad = 0., frameon = False)
ax2.legend([line3],[line3.get_label()])
ax2.legend(bbox_to_anchor = (1.00,0.99), loc = 'lower right', borderaxespad = 0., frameon = False)
#************************************************************************#
#**********************Animation Function********************************#
# This function is called periodically from FuncAnimation
def updateData(self):
# Drop down menu event flags
global ChartLoad
# Graph variables
global xs
global ys1
global ys2
global ys3
if ChartLoad == True:
# Read temperature (Celsius) from TMP102
temp_c = cpu.temperature
temp_c1 = temp_c + 5.0
temp_c2 = temp_c - 35.0
# Add y to list
ys1.append(temp_c)
ys2.append(temp_c1)
ys3.append(temp_c2)
# Limit y list to set number of items
ys1 = ys1[-x_len:]
ys2 = ys2[-x_len:]
ys3 = ys3[-x_len:]
# Update line with new Y values
line1.set_ydata(ys1)
line2.set_ydata(ys2)
line3.set_ydata(ys3)
return line1, line2, line3,
#************************************************************************#
#*******************Tkinter Window Initalization*************************#
class MyApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self,"EMEA Vicor Charging Application ")
img=tk.PhotoImage(file='/home/pi/Pictures/Vicor_Icon1.png')
self.tk.call('wm','iconphoto',self._w,img)
container = tk.Frame(self)
container.pack(side="top", fill="both", expand = True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
#*********************************************************
#******Function Required to Display Seperate Pages********
self.frames = {}
for F in (StartPage, HomePage, 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()
#*********************************************************
#*********************************************************
#Start Page - Agreement
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
#Page One - Primary Terminal
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
tk.Frame.__init__(self, parent, bg = "white")
# Two Frames Need to be Established on Page One (Which is a “Higher Order Frame”)
my_window = Tk()
frame_name = Frame(my_window)
frame_addr = Frame(my_window)
label_f = Label(frame_name, text = "Check")
label_f.grid(row = 0, column = 0)
label_g = Label(frame_addr, text = "Correct")
label_g.grid(row = 0, column = 0)
frame_name.grid(row = 0, column = 0)
frame_addr.grid(row = 0, column = 1)
#my_window.mainloop()
app = MyApp()
app.geometry("1010x700")
ani = animation.FuncAnimation(fig1, updateData, blit = True, interval = stepms)
app.mainloop()
Page One will contain my Canvas for the Graph and Tool Bar. however to simplify the problem into its fundamental, getting two frames to make up "Higher Order" frame PageOne, with each subframe containing a label. When I run the code, another window opens with the two labels displayed. This is more progress than from other solutions that i can't implement/don't understand, producing error messages i can't respond to. I am self taught at python, and have been following tutorials from the community. I just need some help implementing the solution. If I've gutted my code to much i can provide a more comprehensive code snippet. But the essence of the problem is, while displaying one of several frames on a Tkinter Window which move to the foreground when requested, how do you split one of these "High Order" frames into two frames as to allow two different geometric plotters to be used to structure each one.
Resources Used So Far:
https://www.youtube.com/watch?v=Mxk4cMBaH3g&list=PL6lxxT7IdTxGoHfouzEK-dFcwr_QClME_&index=37&t=0s
[Most Recent Attempt - Trying to Simplify the problem]
how to make two split up screen ( canvas) inside the python tkinter window
http://www.openbookproject.net/courses/python4fun/tkphone1.html
matplotlib Navigation Bar error 'FigureCanvasTkAgg' object has no attribute 'manager' in tkinter
Displaying Matplotlib Navigation Toolbar in Tkinter via grid
[Hit the Same Problem - One Master Frame Split Into Two Sub Frames]
[This Link is probably has the answer in it and if that is the case, apologies for repeating a question already asked on stack overflow)
EDIT. To Expand Further for Clarity take the code below:
import tkinter as tk # python 3
from tkinter import font as tkfont # python 3
#import Tkinter as tk # python 2
#import tkFont as tkfont # python 2
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title_font = tkfont.Font(family='Helvetica', size=18, weight="bold", slant="italic")
# the container is where we'll stack a bunch of frames
# on top of each other, then the one we want visible
# will be raised above the others
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, PageOne, PageTwo):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
# put all of the pages in the same location;
# the one on the top of the stacking order
# will be the one that is visible.
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is the start page", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button1 = tk.Button(self, text="Go to Page One",
command=lambda: controller.show_frame("PageOne"))
button2 = tk.Button(self, text="Go to Page Two",
command=lambda: controller.show_frame("PageTwo"))
button1.pack()
button2.pack()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 1", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 2", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
how would i get PageTwo to compose of two different frames so i can use two different geometric plotters (pack() and grid()).
Im trying to recreate a little version of trello in tkinter. Right now im stuck I have a problem when I want to delete frames in a different order. For example: I click on the button and a new frame is generated if I delete that everything works. If I create 3 frames I have to remove them in the same order as I have created them. So I think my problems lies in the pop function but I dont know how to access them manually. When i change the pop function to (1) then I have to delete the second creation first instead of the first.
Here is the code:
from tkinter import *
class Window:
def __init__(self, width, height):
self.root = Tk()
self.width = width
self.height = height
self.root.geometry(width + "x" + height)
class Frames:
def __init__(self):
self.l = Frame(window.root, bg="red", height=300, width=300, relief="sunken")
self.l.place(relwidth=0.3, relheight=0.3)
self.deleteB = Button(self.l, text="X", command=self.delete_frame, bg="blue")
self.deleteB.place(rely=0, relx=0.92)
self.addB = Button(self.l, text="Add", command=self.add_note, bg="blue")
self.addB.place(rely=0, relx=0.65)
def delete_frame(self):
self.l.pack()
self.l.pack_forget()
self.l.destroy()
frames.pop()
def add_note(self):
self.note_Label = Label(self.l, text="Clean the room")
self.note_Label.pack(padx=20, pady=10)
self.delete_Note = Button(self.note_Label, text="X", command=self.del_Note)
self.delete_Note.pack(padx=5, pady=5)
def del_Note(self):
self.note_Label.pack_forget()
self.note_Label.destroy()
class Note:
def __init__(self):
pass
class DragNDrop:
def __init__(self):
pass
def make_draggable(self, widget):
widget.bind("<Button-1>", self.on_drag_start)
widget.bind("<B1-Motion>", self.on_drag_motion)
def on_drag_start(self, event):
widget = event.widget
widget._drag_start_x = event.x
widget._drag_start_y = event.y
def on_drag_motion(self, event):
widget = event.widget
x = widget.winfo_x() - widget._drag_start_x + event.x
y = widget.winfo_y() - widget._drag_start_y + event.y
widget.place(x=x, y=y)
class Buttons:
def __init__(self):
self.button = Button(window.root, width=20, height=20, bg="blue", command=self.add_frames)
self.button.pack()
def add_frames(self):
frames.append(Frames())
print(frames)
window = Window("800", "600")
frames = []
drag = DragNDrop()
button = Buttons()
while True:
for i in frames:
drag.make_draggable(i.l)
window.root.update()
If someone has an Idea or workaround that would be nice to know.
Also I have another Idea instead of destroying them I could just hide them but in the end that makes the programm really slow at some point.
Here is the error: _tkinter.TclError: bad window path name ".!frame2"
Your code needs to remove the frame from the list. Instead, you're calling pop which always removes the last item. That causes you to lose the reference to the last window, and one of the references in frames now points to a window that has been deleted (which is the root cause of the error)
Instead, call remove:
def delete_frame(self):
self.l.destroy()
frames.remove(self)
I've got a small program that creates a GUI using tkinter. It contains a button that loads a .csv file, creates a notebook with as many tabs as columns in the csv file. Then, on every active tab (at least this is my intention) I have a plot created from a Figure.
The program works as expected , the only problem is that when switching Tabs, the memory used increases with each Tab click.
Memory usage was monitored using the Windows Task Manager.
After loading a csv file, I didn't see the used memory dropping when I chose not loading a new file.
If I don't call the plotting function, when creating only the Tabs, there is no memory issue.
I already tried to manually invoke the garbage collector with gc.collect(), but that didn't help. This is the code I have:
import matplotlib
matplotlib.use('TkAgg')
import pandas as pd
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import os
import sys
import tkinter as tk
from tkinter import messagebox as msg
from tkinter import ttk, filedialog
##import gc
class Graphs(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side='top', fill='both', expand=1)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.protocol('WM_DELETE_WINDOW', self._destroyWindow)
self.frames = {}
frame = StartPage(parent=container, controller=self)
self.frames[StartPage] = frame
frame.grid(row=0, column=0, sticky='nsew')
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
def _destroyWindow(self):
self.quit() # stops mainloop
self.destroy()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
# initialize lists
self.tabs_list = []
self.hdrs = []
self.figs_list = []
self.ax_list = []
self.canvas_list = []
self.toolbars_list = []
# initialize Data Frame
self.df = pd.DataFrame()
self.nb = None
self.canvas = None
self.toolbar = None
# create LOAD button
self.btn = tk.Button(self, text = 'Load file', command=self.load_csv)
self.btn.pack()
def load_csv(self):
'''
Reset Data Frame;
Destroy notebook if exists;
Load CSV file.
'''
# reset Data Frame
self.df = pd.DataFrame()
# destroy notebook if exists
if self.nb:
self.nb.pack_forget()
self.nb.destroy()
self.nb = None
## gc.collect()
# Select CSV file
self.file_path = filedialog.askopenfilename()
if not self.file_path:
msg.showinfo('Select CSV file', "No file chosen.")
return
try:
# read csv file (exemple.csv)
self.df = pd.read_csv(self.file_path, header=0)
except:
msg.showinfo('Select CSV file', 'Not a csv file / corrupt file.')
return
print(self.df.head())
print(self.df.shape)
# get dimensions
self.m, self.n = self.df.shape
# build the abscissa x from first column
self.x = self.df.iloc[:,0]
# create the notebook
self.nb = ttk.Notebook(self)
# allow Tab navigation
self.nb.enable_traversal()
# add Tabs
for k in range(1, self.n):
hdr = self.df.columns[k]
self.hdrs.append(hdr)
tab = tk.Frame(self.nb, name=hdr.lower())
self.nb.add(tab, text=hdr)
self.tabs_list.append(tab)
self.nb.pack(fill='both', expand=1)
# virtual event after a new tab is selected
self.nb.bind("<<NotebookTabChanged>>", self.plotTH)
def plotTH(self, event):
'''
Plot each Column from Data Frame on its own Tab/Figure
'''
# get path of the selected Tab
tab_path = event.widget.nametowidget(event.widget.select())
# add selected Tab to the list of Tabs
self.tabs_list.append(tab_path)
# get the Tab index;
# When there are no tabs, .select() returns an empty string,
# but .index('current') throws an exception;
# nb.select() returns the Tab NAME (string) of the current selection
if self.nb.select():
i = self.nb.index('current')
# get the Tab text
tab_text = self.nb.tab(i)['text']
else:
return
# remove previous figures ... not sure...
# the used memory as seen in Task Manager still increases
if self.canvas_list:
for cnv in self.canvas_list:
cnv.figure.get_axes().clear()
cnv.get_tk_widget().pack_forget()
cnv.get_tk_widget().destroy()
cnv._tkcanvas.pack_forget()
cnv._tkcanvas.destroy()
cnv = None
if self.figs_list:
for fig in self.figs_list:
fig.delaxes(fig.gca())
plt.cla()
fig.clf()
fig.clear()
plt.close(fig)
self.figs_list = []
# remove toolbar
for widget in tab_path.winfo_children():
widget.pack_forget()
widget.destroy()
self.nb.update() #!!!!!!!!!!!!
######## gc.collect()
# prepare plotting
fig = Figure(figsize=(7, 5), dpi=100)
ax = fig.add_subplot(111)
ax.plot(self.x, self.df.iloc[:,i+1], 'b-', linewidth=1, label=tab_text)
ax.set_xlabel('index')
ax.set_title(self.hdrs[i], fontsize = 8)
ax.legend(loc='best')
ax.grid()
# add to list of figures
self.figs_list.append(fig)
# add to list of axes
self.ax_list.append(ax)
canvas = FigureCanvasTkAgg(fig, master=tab_path)
# add to list of canvases
self.canvas_list.append(canvas)
## self.canvas.draw()
canvas.draw_idle()
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
toolbar = NavigationToolbar2Tk(canvas, tab_path)
# add to list of toolbars
self.toolbars_list.append(toolbar)
toolbar.update()
canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
def clearPlot(self):
""" not used"""
pass
app = Graphs()
app.title('CSV Plots')
app.geometry('800x600+400+150')
app.resizable(True, True)
app.mainloop();
I created a working csv file using this code:
import numpy as np
import pandas as pd
np.random.seed(0)
df = pd.DataFrame(np.random.randn(50,4), columns=['ALPHA', 'BETA', 'GAMMA', 'DELTA'])
df.index.names = ['Rec']
df.index = df.index + 1
df.to_csv('example.csv', index=True)
print(df)
I'm sorry for the long post. I really don't know were to go from here, so any help would be greatly appreciated.
None of the following lists are ever purged; they hold references to large objects that cannot be garbage collected.
self.tabs_list = []
self.hdrs = []
self.figs_list = []
self.ax_list = []
self.canvas_list = []
self.toolbars_list = []
You probably should create an object that holds the data for each tab, and destroy/purge/reassign to None that when changing tabs.
I changed the code; I realized I was creating a new figure (with all the Artists) and a new canvas (FigureCanvasTkAgg) with every TabChanged event. Instead, I create a figure and its canvas at the moment of Tab addition to the notebook; I do the plot only when the TabChanged occurs and I take care to close all the plots and to destroy the previous notebook when performing a new CSV file load. Destroying the notebook removes the canvases too. Here is the code:
import matplotlib
import matplotlib.style as mplstyle
matplotlib.use('TkAgg')
import pandas as pd
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import os, sys, time
import tkinter as tk
from tkinter import messagebox as msg
from tkinter import ttk, filedialog
mplstyle.use('fast')
class Graphs(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side='top', fill='both', expand=1)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.protocol('WM_DELETE_WINDOW', self._destroyWindow)
self.frames = {}
frame = StartPage(parent=container, controller=self)
self.frames[StartPage] = frame
frame.grid(row=0, column=0, sticky='nsew')
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
def _destroyWindow(self):
self.quit() # stops mainloop
self.destroy()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
# initialize Data Frame
self.df = pd.DataFrame()
self.nb = None
self.fig = None
self.canvas = None
self.toolbar = None
# create LOAD button
self.btn = tk.Button(self, text = 'Load file', command=self.load_csv)
self.btn.pack()
def load_csv(self):
''' Close the plots;
Reset Data Frame;
Destroy notebook if exists;
Load CSV file.
'''
# Setting interactive mode off
if plt.isinteractive():
plt.ioff()
plt.close("all")
# reset Data Frame
self.df = pd.DataFrame()
# initialize list
self.hdrs = []
try:
# destroy notebook if exists
self.nb.pack_forget()
self.nb.destroy()
except:
pass
self.nb = None
# Select CSV file
self.file_path = filedialog.askopenfilename()
if not self.file_path:
msg.showinfo('Select CSV file', "No file chosen.")
return
try:
# read csv file (exemple.csv)
self.df = pd.read_csv(self.file_path, header=0)
except:
msg.showinfo('Select CSV file', 'Not a csv file / corrupt file.')
return
# get dimensions
self.m, self.n = self.df.shape
# build the abscissa x from first column
self.x = self.df.iloc[:,0]
# create the notebook
self.nb = ttk.Notebook(self)
# allow Tab navigation
self.nb.enable_traversal()
# add Tabs
for k in range(1, self.n):
hdr = self.df.columns[k]
self.hdrs.append(hdr)
tab = tk.Frame(self.nb, name=hdr.lower())
self.nb.add(tab, text=hdr)
self.fig = plt.figure(num=hdr.lower(), clear=True, figsize=(7, 5), dpi=100)
# self.ax = self.fig.add_subplot()
self.canvas = FigureCanvasTkAgg(self.fig, master=tab)
self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
toolbar = NavigationToolbar2Tk(self.canvas, tab)
toolbar.update()
self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
self.nb.pack(fill='both', expand=1)
# virtual event after a new tab is selected
self.nb.bind("<<NotebookTabChanged>>", self.plotTH)
def plotTH(self, event):
'''
Plot each Column from Data Frame on its own Tab/Figure
'''
# Setting interactive mode on is essential: plt.ion()
if not plt.isinteractive():
plt.ion()
# tab index
i = self.nb.index('current')
# tab text
tab_text = self.nb.select().split('.')[-1]
# set current figure
cf = plt.figure(tab_text)
plt.clf()
# plotting
ax = plt.subplot(111)
ax.plot(self.x, self.df.iloc[:,i+1], 'b-', linewidth=1, label=tab_text)
ax.set_xlabel('index')
ax.set_title(self.hdrs[i], fontsize = 8)
ax.legend(loc='best')
ax.grid()
cf.canvas.draw()
app = Graphs()
app.title('CSV Plots')
app.geometry('800x600+400+150')
app.resizable(True, True)
app.mainloop()
I am using tkinter and winsound.
I want the sound and the countdown timer to work simultaneously.
Right now, once the clicking sound is over the timer appears.
I have seen some countdown timers examples which use "after". Ex: self.after(1000, self.countdown). But I need both in simoultaneous.
import tkinter as tk
from tkinter import Tk
from nBackTools.NBackTools import *
from nBackTools.ZBack import *
#To play sounds
import winsound
from winsound import *
import numpy as np
class NBack:
def __init__(self, master):
##Title of the window
self.master = master
master.title("N-Back")
##It measures the screen size (width x height + x + y)
##The opened window will be based on the screen size
master.geometry("{0}x{1}-0+0".format(master.winfo_screenwidth(), master.winfo_screenheight()))
self.canvas = tk.Canvas(master, width=master.winfo_screenwidth(), height=master.winfo_screenheight(), \
borderwidth=0, highlightthickness=0, bg="grey")
self.canvasWidth = master.winfo_screenwidth()
self.canvasHeight = master.winfo_screenheight()
##If removed, a white screen appears
self.canvas.grid()
"""
BREAK TIMER
"""
self.play()
self.canvas.create_text(((self.canvasWidth/2), (self.canvasHeight/2)-130), text="LET'S TAKE A BREAK!", font=(None, 90))
self.display = tk.Label(master, textvariable="")
self.display.config(foreground="red", background = "grey", font=(None, 70), text= "00:00")
self.display.grid(row=0, column=0, columnspan=2)
def play(self):
return PlaySound('clock_ticking.wav', SND_FILENAME)
root = Tk()
my_gui = NBack(root)
root.mainloop()
Doing 2 things at once is called "asynchronous". To enable that mode in winsound you need the ASYNC flag:
def play(self):
PlaySound('clock_ticking.wav', SND_FILENAME | SND_ASYNC)
You will still need to use after to get the countdown to work.