Error when closing tkinter app with figure and canvas - python

I get the following error when I resize the right expand the window and then close it:
_tkinter.TclError: invalid command name ".!scrollbar"
Maybe it's related to the canvas widget.
I tried a lot, but without results.
can anyone help me?
The code is below
import tkinter as tk
from matplotlib.figure import Figure
import numpy as np
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
root=tk.Tk()
vscrollbar = tk.Scrollbar(root)
canvasF2= tk.Canvas(root,yscrollcommand=vscrollbar.set)
vscrollbar.config(command=canvasF2.yview)
vscrollbar.pack(side=tk.RIGHT, fill=tk.Y)
frame2=tk.Frame(canvasF2) #Create the frame which will hold the widgets
canvasF2.pack(side="left", fill="both", expand=True)
##Updated the window creation
canvasF2.create_window(0,0,window=frame2, anchor='nw')
#
fig = Figure(figsize=(10, 4), dpi=100)
t = np.arange(0, 3, .01)
a = fig.add_subplot(111)
a.plot(t, 2 * np.sin(2 * np.pi * t))
canvas = FigureCanvasTkAgg(fig, frame2) # A tk.DrawingArea.
canvas.draw()
canvas.get_tk_widget().grid(row=4, column=0, columnspan=2, sticky="nswe")
def on_configure(event):
canvasF2.configure(scrollregion=canvasF2.bbox('all'))
root.bind('<Configure>', on_configure)
root.mainloop()

I'm not sure why this error triggers when the window is closed, my guess is somehow after closing the window the widget canvasF2 is not getting destroyed properly. So if we destroy canvasF2 properly before closing the window the error won't trigger. I think there'll be a better way of doing this but here's what I did.
I handled the deletion of the window by using protocol method. I added this to the end of your code and I got no error by destroying canvasF2 before the main window is destroyed.
def close_window():
canvasF2.destroy()
root.destroy()
root.protocol("WM_DELETE_WINDOW", close_window)
Complete Code:
import tkinter as tk
from matplotlib.figure import Figure
import numpy as np
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
root=tk.Tk()
vscrollbar = tk.Scrollbar(root)
canvasF2= tk.Canvas(root,yscrollcommand=vscrollbar.set)
vscrollbar.config(command=canvasF2.yview)
vscrollbar.pack(side=tk.RIGHT, fill=tk.Y)
frame2=tk.Frame(canvasF2) #Create the frame which will hold the widgets
canvasF2.pack(side="left", fill="both", expand=True)
##Updated the window creation
canvasF2.create_window(0,0,window=frame2, anchor='nw')
#
fig = Figure(figsize=(10, 4), dpi=100)
t = np.arange(0, 3, .01)
a = fig.add_subplot(111)
a.plot(t, 2 * np.sin(2 * np.pi * t))
canvas = FigureCanvasTkAgg(fig, frame2) # A tk.DrawingArea.
canvas.draw()
canvas.get_tk_widget().grid(row=4, column=0, columnspan=2, sticky="nswe")
def on_configure(event):
canvasF2.configure(scrollregion=canvasF2.bbox('all'))
root.bind('<Configure>', on_configure)
def close_window():
canvasF2.destroy()
root.destroy()
root.protocol("WM_DELETE_WINDOW", close_window)
root.mainloop()
Hope this helps!

Related

How to change color of matplotlib toolbar in tkinter?

I would like to change the color of the toolbar when making a matplotlib figure in tkinter. I have managed to find and change the color of two parts. There is one remaining.
My code comes directly from https://matplotlib.org/stable/gallery/user_interfaces/embedding_in_tk_sgskip.html?highlight=embedding%20tk with three additional lines to change colors.
import tkinter
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
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().plot(t, 2 * np.sin(2 * np.pi * t))
canvas = FigureCanvasTkAgg(fig, master=root) # A tk.DrawingArea.
canvas.draw()
color = "#d469a3"
toolbar = NavigationToolbar2Tk(canvas, root, pack_toolbar=False)
toolbar.config(background=color)
toolbar._message_label.config(background=color)
toolbar.update()
button = tkinter.Button(master=root, text="Quit", command=root.quit)
button.pack(side=tkinter.BOTTOM)
toolbar.pack(side=tkinter.BOTTOM)
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1)
tkinter.mainloop()
This gives me the window:
What is the small, grey rectangle I have pointed out? How do I change its color?
It is an empty label. You can get a reference to it via winfo_children:
print (toolbar.winfo_children()[-2])
# .!navigationtoolbar2tk.!label
And to change its color:
toolbar.winfo_children()[-2].config(background=color)

How to embed matplotlib graph in some area of a tkinter canvas?

Hello I am trying to embed a matplotlib graph to an especific area of a canvas, and I am having some problems trying to make the toolbar work properly. Here is what I have so far:
import matplotlib
import matplotlib.pyplot as plt
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import tkinter as tk
def on_closing():
root.quit()
root.destroy()
root = tk.Tk()
root.protocol("WM_DELETE_WINDOW", on_closing)
canvas = tk.Canvas(root, width = 500, height=400, highlightthickness=0)
canvas.pack()
# create Frame
x0, y0, x1, y1 = (10, 10, 490, 390)
frame = tk.Frame(canvas, width=x1-x0, height=y1-y0)
window = canvas.create_window(x0, y0, anchor="nw",
window=frame, width=x1-x0, height=y1-y0)
# Create matplotlib graph
figure, ax = plt.subplots()
ax.plot([1,2,3,4,5,6,7,8],[5,6,1,3,8,9,3,5])
ax.set(xlabel='something', ylabel='something',
title='something')
ax.grid()
canvas_matplotlib = FigureCanvasTkAgg(figure, frame)
canvas_matplotlib.draw()
canvas_matplotlib.get_tk_widget().pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True)
toolbar = NavigationToolbar2Tk(canvas_matplotlib, frame)
toolbar.update()
canvas_matplotlib._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
root.mainloop()
I dont understand why the toolbar wont appear.
EDIT:
What I want is for the plot and the toolbar to occupy exactly the area of the canvas defined.

matplotlib add to panedwindow in tkinter

I do not understand how correctly add matplotlib into panedwindow of tkinter.
I want to have on one side list box and matplotlib interactive plot, on the other side
I need to resize listbox and plot by dragging boundary between them
this is what is desirable
I tried this but I cannot add correctly matplot lib to paned window
from tkinter import *
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 matplotlib.pyplot as plt
import numpy as np
root = Tk()
m = PanedWindow(root)
m.pack(fill=BOTH, expand=1)
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=RIGHT, fill=BOTH, expand=1)
toolbar = NavigationToolbar2Tk(canvas, root)
toolbar.update()
canvas.get_tk_widget().pack(side=RIGHT, fill=BOTH, expand=1)
m.add(toolbar)
m.add(canvas)
lstbox2 = Listbox(selectmode=MULTIPLE, width=20, height=10)
m.add(lstbox2)
root.mainloop()
Put your canvas and toolbar in a Frame instead, and add that Frame to your paned window:
root = Tk()
...
canvas_frame = Frame(root)
canvas = FigureCanvasTkAgg(fig, master = canvas_frame)
canvas.draw()
canvas.get_tk_widget().pack()
toolbar = NavigationToolbar2Tk(canvas, canvas_frame)
toolbar.update()
toolbar.pack_configure(expand=True)
m.add(canvas_frame)
lstbox2 = Listbox(selectmode=MULTIPLE, width=20, height=10)
m.add(lstbox2)
root.mainloop()

How to overlay multiple plots on the same chart tkinter

I am new to tkinter and python and trying to write a simple program that lets me overlay line charts on the same plot. The code I have written (some taken from the 'matplotlib' site) keeps placing all the charts underneath each other. Is there a simpler way to do this. I cant seem to take the canvas or widgets outside the 'for' loop without it messing something up,my code and test 'csv' files are below.Any help or guidance please as to what I need to change?
from tkinter import ttk
import tkinter
from tkinter import *
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")
current_list = ['Blue', 'Green', 'Yellow']
for item in current_list:
x, y = np.loadtxt(item + '_' + 'Test.csv', skiprows=1, usecols=[2, 3],
unpack=True, delimiter=',')
fig = Figure(figsize=(2, 3), dpi=100)
fig.add_subplot(111).plot(x, y)
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.
csv test files - Blue_Test.csv
Name1,Name2,Name3,Name4,Name5,Name6,Name7,Name8
1,100,19,100,Blue,2000,1.00E-19,1.00E-09
2,110,20,101,Blue,3000,5.00E-19,1.00E+00
3,120,21,102,Blue,4000,9.00E-19,2.00E+00
4,150,24,105,Blue,7000,2.10E-18,5.00E+00
5,160,25,106,Blue,8000,2.50E-18,6.00E+00
## csv test files - Green_Test.csv
Name1,Name2,Name3,Name4,Name5,Name6,Name7,Name8
1,100,19,2000,Green,2000,1.00E-19,1.00E-09
2,110,20,3001,Green,3000,5.00E-19,1.00E+00
3,120,21,4002,Green,4000,9.00E-19,2.00E+00
4,150,24,5005,Green,7000,2.10E-18,5.00E+00
5,160,25,6006,Green,8000,2.50E-18,6.00E+00
## csv test files - Yellow_Test.csv
Name1,Name2,Name3,Name4,Name5,Name6,Name7,Name8
1,100,19,11000,Yellow,2000,1.00E-19,1.00E-09
2,110,20,12001,Yellow,3000,5.00E-19,1.00E+00
3,120,21,13002,Yellow,4000,9.00E-19,2.00E+00
4,150,24,14005,Yellow,7000,2.10E-18,5.00E+00
5,160,25,15006,Yellow,8000,2.50000,5.000000
The problem lies in the for item in current_list: loop, in each iteration of the loop the code creates a new subplot and draws it on the canvas. Instead, you should create one subplot and plot each dataset on it. Then once the loop has completed, draw the populated subplot on the canvas. https://pydatascience.org/2017/11/24/plot-multiple-lines-in-one-chart-with-different-style-python-matplotlib/
import tkinter
import numpy as np
# Implement the default Matplotlib key bindings.
from matplotlib.backend_bases import key_press_handler
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.figure import Figure
root = tkinter.Tk()
root.wm_title("Embedding in Tk")
current_list = ['Blue', 'Green', 'Yellow']
fig = Figure(figsize=(2, 3), dpi=100)
ax = fig.add_subplot(111)
for item in current_list:
x, y = np.loadtxt(item + '_' + 'Test.csv', skiprows=1, usecols=[2, 3],
unpack=True, delimiter=',')
ax.plot(x, y, label=item)
fig.legend()
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.

How to resize matplotlib graph in tkinter window

I'm trying to set up some program that includes a matplotlib graph and tkinter buttons and whatnot below it. However, the matplotlib graph occupies the entire window overlaying the tkinter buttons and stuff.
I'd tried using pack, but it doesn't let me put stuff side by side, so I'd like the tkinter widgets to be arranged with .grid or coordinates.
from tkinter import *
from tkinter import StringVar
import tkinter
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
def eggs():
print("eggs")
root = Tk()
root.geometry("600x600")
root.title("eggs")
fig = Figure(figsize=(10, 6), dpi=100)
x = [1,2,3,4]
y = [1,2,3,4]
AS = [10/2**0]
fig.add_subplot(111).plot(x,y)
#fig.add_subplot(111).plot(AS)
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)
testbutton = Button(root, text = "test button", command = eggs)
testbutton.place(x=100, y=550)
root.mainloop()
Wanting the top part of the window to be occupied by the graph and the buttons and other additional stuff below it.
You can use one Frame to keep graph and its toolbar vertically, and another Frame to keep buttons horizontally. And then you can use pack() to put one Frame at top top and other at the bottom.
The only problem makes figsize=(10, 6) which needs more space than "600x600"
BTW: you can use Button(toolbar, ...) to add button to NavigationToolbar2Tk - see "my tool".
]1
import tkinter as tk
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
# --- functions ---
def eggs():
print("eggs")
# --- main ---
x = [1, 2, 3, 4]
y = [1, 2, 3, 4]
AS = [10/2**0]
# ---
root = tk.Tk()
root.geometry("600x600")
root.title("eggs")
# ---
frame_top = tk.Frame(root)
frame_top.pack(fill='both', expand=True)
fig = Figure(dpi=100) # figsize=(10, 6),
fig.add_subplot(111).plot(x,y)
#fig.add_subplot(111).plot(AS)
canvas = FigureCanvasTkAgg(fig, master=frame_top) # A tk.DrawingArea.
canvas.draw()
canvas.get_tk_widget().pack(fill='both', expand=True)
toolbar = NavigationToolbar2Tk(canvas, frame_top)
toolbar.update()
tool = tk.Button(toolbar, text="my tool")
tool.pack(side='left')#, fill='x', expand=True)
# ---
frame_bottom = tk.Frame(root)
frame_bottom.pack(fill='x')
button1 = tk.Button(frame_bottom, text="button1")
button1.pack(side='left', fill='x', expand=True)
button2 = tk.Button(frame_bottom, text="button2")
button2.pack(side='left', fill='x', expand=True)
button3 = tk.Button(frame_bottom, text="button3")
button3.pack(side='left', fill='x', expand=True)
# ---
root.mainloop()

Categories