I'm trying to get NavigationToolbar2Tk to stop printing the coordinates in red shown below. I want to stop printing becaause the window resizes when the mouse moves over the figure.
My code follows. Even though Frame10 has grid_propagate(False) it resizes slightly when the mouse is moved over it. I want to stop that. My code is given below.
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,
NavigationToolbar2Tk)
from matplotlib.figure import Figure
import tkinter
root = tkinter.Tk()
root.wm_title('Stop toolbar coords')
root.grid_rowconfigure(0, weight=0)
root.grid_rowconfigure(1, weight=0)
root.grid_rowconfigure(2, weight=0)
root.grid_columnconfigure(0, weight=0)
Frame00 = tkinter.Frame(root,width='3.0i',height='3.0i')
Frame10 = tkinter.Frame(root,width='3.0i',height='1.0i')
Frame00.grid(row=0,column=0)
Frame00.grid_rowconfigure(0,weight=0)
Frame00.grid_columnconfigure(0,weight=0)
# stop frame from expanding to size of widget
Frame00.grid_propagate(False)
Frame10.grid(row=1,column=0)
Frame10.grid_rowconfigure(0,weight=0)
Frame10.grid_columnconfigure(0,weight=0)
# stop frame from expanding to size of widget
Frame10.grid_propagate(False)
# initialize matplotlib figure
fig = Figure(figsize=(2.5,2.5),dpi=100)
ax = fig.gca()
ax.plot([0.1,0.2,0.3],[0.5,0.6,0.7],'bo-')
ax.set_title('Title')
canvas = FigureCanvasTkAgg(fig,master=Frame00)
canvas.draw()
canvas.get_tk_widget().grid(row=0,column=0)
toolbar = NavigationToolbar2Tk(canvas,Frame10)
tkinter.mainloop()
The coordinates are displayed by calling the .set_message() method of the NavigationToolbar2Tk. One way to get rid of this behavior is to override this method:
class Toolbar(NavigationToolbar2Tk):
def set_message(self, s):
pass
# ...
toolbar = Toolbar(canvas, Frame10)
Related
Can somebody help me out with threading in python and getting a progress bar working?
Even research gives quite a lot results, i cannot get it working.
I never did threading before and i cannot tell where to put things correctly.
For testing purposes i prepared a simple GUI with a button and a progress bar:
After clicking the button a simple 3d plot will pop-up.
Such plotting may take some computation time and while a user need to wait, id like to have the GUI not frozen and the progressbar animated.
At the moment the GUI freezes until the plot will show up. And after that the progress bar start the animation.
I have read a lot about threading and to put calculations and gui to different threads?
But im just to noobish to get it working.
Is somebody able to explain me more, direct me to similar problems or documentations?
Or maybe, in case is quickly solved, overtake the simple code and correct it the way it should be?
Thanks in advance for any kind of help.
Python script so far:
from time import sleep
from tkinter import EW
import ttkbootstrap as ttk
import numpy as np
import matplotlib.pyplot as plt
def main():
def plot_sample():
sleep(5) # simulate calculation times
x = np.outer(np.linspace(-2, 2, 30), np.ones(30))
y = x.copy().T # transpose
z = np.cos(x ** 2 + y ** 2)
fig = plt.figure()
ax = plt.axes(projection='3d')
ax.plot_surface(x, y, z,cmap='viridis', edgecolor='none')
ax.set_title('Surface plot')
plt.show()
def progressbar_start():
progressbar.grid(row=1, column=0, sticky=EW, padx=10, pady=10) # let progressbar appear in GUI
progressbar.start(interval=10)
def progressbar_stop():
progressbar.stop()
progressbar.grid_forget() # hide progressbar when not needed
def button_clicked():
progressbar_start() # start progressbar before computation begins
plot_sample() # plotting
progressbar_stop() # stop progressbar after plot is done
# GUI
# window size and settings
root = ttk.Window()
# Basic settings for the main window
root.title('Test progressbar')
root.minsize(300, 200)
root.resizable(True, True)
root.configure(bg='white')
# grid settings for the main window in which LabelFrames are sitting in
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
root.rowconfigure(1, weight=1)
# Button fto show 3d-plot
button_calc_3dplot = ttk.Button(root, text='Calculate 3D Plot', command=button_clicked)
button_calc_3dplot.grid(row=0, column=0, padx=5, pady=5)
progressbar = ttk.Progressbar(style='success-striped', mode='indeterminate')
# end of GUI
root.mainloop()
if __name__ == "__main__":
main()
I have one solution. The main problem is, that You can't run the plt.show() function in a Thread. From this follows, that you can't run the window and the plt.show function at the same time.
from time import sleep
from tkinter import EW
import ttkbootstrap as ttk
import numpy as np
import matplotlib.pyplot as plt
from threading import Thread
x, y, z = None, None, None
def main():
def calculate_xyz():
sleep(5) # simulate calculation times
global x, y, z
x = np.outer(np.linspace(-2, 2, 30), np.ones(30))
y = x.copy().T # transpose
z = np.cos(x ** 2 + y ** 2)
def progressbar_start():
progressbar.grid(row=1, column=0, sticky=EW, padx=10, pady=10) # let progressbar appear in GUI
progressbar.start(interval=10)
def progressbar_stop():
progressbar.stop()
progressbar.grid_forget() # hide progressbar when not needed
def button_clicked():
progressbar_start() # start progressbar before computation begins
calculate_xyz_thread = Thread(target=calculate_xyz)
calculate_xyz_thread.start() # calculate x, y and z
root.after(500, wait_for_plotting) # wait for plotting and stop animation after finishing
def wait_for_plotting():
if x is None or y is None or z is None:
root.after(500, wait_for_plotting) # run self again
return # wait for calculation
# calculation is finished
progressbar_stop() # stop progressbar "after" plot is done
ax = plt.axes(projection='3d')
ax.plot_surface(x, y, z, cmap='viridis', edgecolor='none')
ax.set_title('Surface plot')
root.after(500, plot_3d)
def plot_3d():
plt.show()
# GUI
# window size and settings
root = ttk.Window()
# Basic settings for the main window
root.title('Test progressbar')
root.minsize(300, 200)
root.resizable(True, True)
root.configure(bg='white')
# grid settings for the main window in which LabelFrames are sitting in
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
root.rowconfigure(1, weight=1)
# Button fto show 3d-plot
button_calc_3dplot = ttk.Button(root, text='Calculate 3D Plot', command=button_clicked)
button_calc_3dplot.grid(row=0, column=0, padx=5, pady=5)
progressbar = ttk.Progressbar(style='success-striped', mode='indeterminate')
# end of GUI
root.mainloop()
if __name__ == "__main__":
main()
I'm trying to create a GUI that will plot data from an array that is updated when I click a button (in this case move a slider), I can get the data to replot, however, the scale doesn't update and I can't work out how to do it. does anyone have any ideas?
the data is created in a function dataMaker and just returns 2 arrays, one of the time increments and the other of random data.
many thanks
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
import dataMaker as DM
root = tkinter.Tk()
root.wm_title("Embedding in Tk")
fig = Figure(figsize=(5, 4), dpi=100)
rt_data = DM.comboP(1, 1000)
sample = rt_data[1]
torque = rt_data[2]
ax = fig.add_subplot()
line, = ax.plot(sample, torque)
canvas = FigureCanvasTkAgg(fig, master=root) # A tk.DrawingArea.
canvas.draw()
# pack_toolbar=False will make it easier to use a layout manager later on.
toolbar = NavigationToolbar2Tk(canvas, root, pack_toolbar=False)
toolbar.update()
canvas.mpl_connect(
"key_press_event", lambda event: print(f"you pressed {event.key}"))
canvas.mpl_connect("key_press_event", key_press_handler)
button_quit = tkinter.Button(master=root, text="Quit", command=root.destroy)
def update_frequency(new_val):
rt_data = DM.comboP(1, 1000)
sample = rt_data[1]
torque = rt_data[2]
line.set_data(sample, torque)
ax.autoscale_view()
# required to update canvas and attached toolbar!
canvas.draw()
slider_update = tkinter.Scale(root, from_=1, to=5, orient=tkinter.HORIZONTAL,
command=update_frequency, label="Frequency [Hz]")
# Packing order is important. Widgets are processed sequentially and if there
# is no space left, because the window is too small, they are not displayed.
# The canvas is rather flexible in its size, so we pack it last which makes
# sure the UI controls are displayed as long as possible.
button_quit.pack(side=tkinter.BOTTOM)
slider_update.pack(side=tkinter.BOTTOM)
toolbar.pack(side=tkinter.BOTTOM, fill=tkinter.X)
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=True)
tkinter.mainloop()
I've searched the internet but can't find a solution, id like the scale the change to fit the data that's in the array
So for school I'm doing a project where you can trade with shares and changing course and i want to plot a graph which shows the change of the course onto a canvas which is in the same window, as the other buttons and labels, etc. but I couldn't find any working solution on the internet and since I'm not a pro at python I don't understand the concept of figures etc. It'd be very nice, if somebody could help me out with this and give me a way to have a graph on a tkinter canvas which i can plot based of new generated numbers.
Using matplotlib you could do something like this:
from tkinter import *
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,
NavigationToolbar2Tk)
# plot function is created for
# plotting the graph in
# tkinter window
def plot():
# the figure that will contain the plot
fig = Figure(figsize = (5, 5),
dpi = 100)
# list of squares
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
canvas = FigureCanvasTkAgg(fig,
master = window)
canvas.draw()
# placing the canvas on the Tkinter window
canvas.get_tk_widget().pack()
# creating the Matplotlib toolbar
toolbar = NavigationToolbar2Tk(canvas,
window)
toolbar.update()
# placing the toolbar on the Tkinter window
canvas.get_tk_widget().pack()
# the main Tkinter window
window = Tk()
# setting the title
window.title('Plotting in Tkinter')
# dimensions of the main window
window.geometry("500x500")
# button that displays the plot
plot_button = Button(master = window,
command = plot,
height = 2,
width = 10,
text = "Plot")
# place the button
# in main window
plot_button.pack()
# run the gui
window.mainloop()
Output:
I wanna place a pie chart on my Tkinter GUI such that the legend and the labels never reach outside of the displayed widget. Currently, my legend and some of the labels are not shown in full.
This is the code I have:
import tkinter as tk
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
#GUI code
window = tk.Tk()
window.title("Test App")
canvas = tk.Canvas(window,width=1200,height=600)
canvas.grid(columnspan=2,rowspan=4)
figure, axes = plt.subplots()
axes.pie([2,3,6], labels=["Cat","Dog","Leopardddddddddddddddddddddddddddddddddddddddddddddddddddddddd"], normalize = True)
axes.legend(["Cat","Dog","Leopardddddddddddddddddddddddddddddddddddddddddddddddddddddddd"],bbox_to_anchor=(0, 1),fontsize=8)
figCanvas = FigureCanvasTkAgg(figure, master=window)
figCanvas.draw()
figCanvas.get_tk_widget().grid(row=1, column=1, padx=10) #place chart on the tkinter grid
window.mainloop()
I do not want to hardcode something like this:
plt.figure(figsize=(10,5), dpi=100)
Because the length of my labels and the size (both height and width) of my legend may change every time the user opens the application.
Any ideas?
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