I am trying to have two daemonic threads run alongside my tkinter main thread. One of the threads generates data and the other thread runs FuncAnimation that plots the data that is generated. However, I am having issues with getting FuncAnimation to run, I can see that whenever I click the button both print statements are shown in the console meaning FuncAnimation appears to have been skipped or ran but exited due to some missing or bad parameter. I checked the matplotlib documentation to ensure I'm not missing anything and I think I have created all the objects and called the right methods to make this happen but something is obviously amiss. Any help is greatly appreciated.
import tkinter as tk
import time
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.figure import Figure
from matplotlib import animation
import threading
root = tk.Tk()
class dynamicChart():
def __init__(self):
self.x = []
self.y = []
self.buildChart()
def buildChart(self):
self.figure = Figure(figsize=(5, 5), dpi=100)
ax = self.figure.add_subplot(111)
self.scatter = ax.scatter(self.x,self.y)
canvas = FigureCanvasTkAgg(self.figure, root)
canvas.draw()
canvas.get_tk_widget().pack(pady=10)
toolbar = NavigationToolbar2Tk(canvas, root)
toolbar.update()
canvas.get_tk_widget().pack()
def animate(self,i):
print("Enter animate")
self.scatter.set_offsets([self.x, self.y])
def Ani(self):
print("Enter Ani")
self.animation = animation.FuncAnimation(self.figure, self.animate, interval = 2000)
print("Exit Ani")
class genData():
def __init__(self):
self.chart = dynamicChart()
def generate(self):
X = 2
Y = 2
i = 1
while True:
time.sleep(1)
X = X*i
Y = Y + i
self.chart.x.append(X)
self.chart.y.append(Y)
i+=1
StartDataGen_CreateFigureAxes = genData()
genData = threading.Thread(target = StartDataGen_CreateFigureAxes.generate, daemon = True)
genData.start()
ani = threading.Thread(target = StartDataGen_CreateFigureAxes.chart.Ani, daemon = True)
button = tk.Button(root, text="Graph It", command = lambda: ani.start())
button.pack()
root.mainloop()
I am currently working to create a tkinter GUI which shows a matplotlib figure with two sliders. The sliders are to be used for selecting a range of data (they are connected to vertical lines on the plot).
The issue I am running into is that when I change the sliders quickly a few times the GUI freezes. I believe this is due to the on_changed() call I am using to make the sliders update. When you move the slider quickly I believe on_changed() is being called multiple times at a faster rate than the program can keep up with, thus causing the freeze.
Is there any way to add a delay to the on_changed() call so it only looks to see if the slider has been changed every given interval of time (ex. 100 ms)?
Below is example code I found which freezes in the way I described.
import tkinter as tk
from tkinter.ttk import Notebook
from tkinter import Canvas
from tkinter import messagebox as msg
import numpy as np
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.widgets import Slider, Button, RadioButtons
class LukeOutline(tk.Tk):
def __init__(self):
# Inherit from tk.Tk
super().__init__()
# Title and size of the window
self.title('Luke Outline')
self.geometry('600x400')
# Create the drop down menus
self.menu = tk.Menu(self,bg='lightgrey',fg='black')
self.file_menu = tk.Menu(self.menu,tearoff=0,bg='lightgrey',fg='black')
self.file_menu.add_command(label='Add Project',command=self.unfinished)
self.menu.add_cascade(label='File',menu=self.file_menu)
self.config(menu=self.menu)
# Create the tabs (Graph, File Explorer, etc.)
self.notebook = Notebook(self)
graph_tab = tk.Frame(self.notebook)
file_explorer_tab = tk.Frame(self.notebook)
# Sets the Graph Tab as a Canvas where figures, images, etc. can be added
self.graph_tab = tk.Canvas(graph_tab)
self.graph_tab.pack(side=tk.TOP, expand=1)
# Sets the file explorer tab as a text box (change later)
self.file_explorer_tab = tk.Text(file_explorer_tab,bg='white',fg='black')
self.file_explorer_tab.pack(side=tk.TOP, expand=1)
# Add the tabs to the GUI
self.notebook.add(graph_tab, text='Graph')
self.notebook.add(file_explorer_tab, text='Files')
self.notebook.pack(fill=tk.BOTH, expand=1)
# Add the graph to the graph tab
self.fig = Figure()
graph = FigureCanvasTkAgg(self.fig,self.graph_tab)
graph.get_tk_widget().pack(side='top',fill='both',expand=True)
EllipseSlider(self.fig)
#------------------------------------------------------
def quit(self):
'''
Quit the program
'''
self.destroy()
#------------------------------------------------------
def unfinished(self):
'''
Messagebox for unfinished items
'''
msg.showinfo('Unfinished','This feature has not been finished')
#------------------------------------------------------
def random_graph(self):
x = list(range(0,10))
y = [i**3 for i in x]
fig = Figure()
axes = fig.add_subplot(111)
axes.plot(x,y,label=r'$x^3$')
axes.legend()
return fig
#----------------------------------------------------------
class EllipseSlider():
#------------------------------------------------------
def __init__(self,fig):
self.fig = fig
# Initial values
self.u = 0. #x-position of the center
self.v = 0. #y-position of the center
self.a = 2. #radius on the x-axis
self.b = 1.5 #radius on the y-axis
# Points to plot against
self.t = np.linspace(0, 2*np.pi, 100)
# Set up figure with centered axes and grid
self.ax = self.fig.add_subplot(111)
self.ax.set_aspect(aspect='equal')
self.ax.spines['left'].set_position('center')
self.ax.spines['bottom'].set_position('center')
self.ax.spines['right'].set_color('none')
self.ax.spines['top'].set_color('none')
self.ax.xaxis.set_ticks_position('bottom')
self.ax.yaxis.set_ticks_position('left')
self.ax.set_xlim(-2,2)
self.ax.set_ylim(-2,2)
self.ax.grid(color='lightgray',linestyle='--')
# Initial plot
self.l, = self.ax.plot(self.u+self.a*np.cos(self.t),
self.v+self.b*np.sin(self.t),'k')
# Slider setup
self.axcolor = 'lightgoldenrodyellow'
self.axb = self.fig.add_axes([0.25, 0.1, 0.65, 0.03], facecolor=self.axcolor)
self.axa = self.fig.add_axes([0.25, 0.15, 0.65, 0.03], facecolor=self.axcolor)
self.sb = Slider(self.axb, 'Y Radius', 0.1, 2.0, valinit=self.b)
self.sa = Slider(self.axa, 'X Radius', 0.1, 2.0, valinit=self.a)
# Call update as slider is changed
self.sb.on_changed(self.update)
self.sa.on_changed(self.update)
# Reset if reset button is pushed
self.resetax = self.fig.add_axes([0.8,0.025,0.1,0.04])
self.button = Button(self.resetax, 'Reset', color=self.axcolor, hovercolor='0.975')
self.button.on_clicked(self.reset)
# Color button setup
self.rax = self.fig.add_axes([0.025, 0.5, 0.15, 0.15], facecolor=self.axcolor)
self.radio = RadioButtons(self.rax, ('red', 'blue', 'green'), active=0)
self.radio.on_clicked(self.colorfunc)
#------------------------------------------------------
def update(self, val):
'''
Updates the plot as sliders are moved
'''
self.a = self.sa.val
self.b = self.sb.val
self.l.set_xdata(self.u+self.a*np.cos(self.t))
self.l.set_ydata(self.u+self.b*np.sin(self.t))
#------------------------------------------------------
def reset(self, event):
'''
Resets everything if reset button clicked
'''
self.sb.reset()
self.sa.reset()
#------------------------------------------------------
def colorfunc(self, label):
'''
Changes color of the plot when button clicked
'''
self.l.set_color(label)
self.fig.canvas.draw_idle()
#----------------------------------------------------------
if __name__ == '__main__':
luke_gui = LukeOutline()
luke_gui.mainloop()
es = EllipseSlider()
(UPDATE):
I have implemented the correction you showed and the code looks to work as intended except the vertical sliders I am using now do not update their position until I move the slider a second time. Please see the section from my code below:
# Call update if slider is changed
self.sa.on_changed(self.schedule_update)
self.sb.on_changed(self.schedule_update)
def update(self, value):
print(value)
self.vert_a.set_xdata(value)
self.vert_b.set_xdata(self.sb.val)
root.update()
def schedule_update(self, new_value):
if self.after_id:
root.after_cancel(self.after_id)
self.after_id = root.after(1000, self.update, new_value)
root = tk.Tk()
mainapp = MainApp(root)
root.mainloop()
Tkinter has a method named after for delaying the execution of a command. Your slider can use this to delay the effect of the slider for a second or two. If the slider moves, it can cancel the previously scheduled command and submit a new one.
Here's an example using plain tkinter for simplicity. Run the code and then move the slider quickly or slowly. You'll see that the label will only update after you haven't moved the slider for a full second.
import tkinter as tk
after_id = None
def schedule_update(new_value):
global after_id
if after_id:
root.after_cancel(after_id)
after_id = root.after(1000, update_label, new_value)
def update_label(new_value):
label.configure(text=f"New value: {new_value}")
root = tk.Tk()
label = tk.Label(root, text="", width=20)
scale = tk.Scale(
root, from_=1, to=100,
orient="horizontal", command=schedule_update
)
label.pack(side="top", fill="x", padx=20, pady=(10,0))
scale.pack(side="bottom", fill="x", padx=20, pady=10)
root.mainloop()
I'm trying to plot data from serial port in a tkinter gui. The plot should be plotted and updated only when a specific pack comes from serial.
I can parse the new incoming data and update the GUI (text area). But when I call the "plot()" function from the "update_gui" thread, the program quit and I get the
"Process finished with exit code -1073741819 (0xC0000005)"
message.
Instead, if I call "plot()" from somewhere else (command button, or simply before mainloop()), the plot is generated and shown.
Here is the relevant part of the code:
import threading
import tkinter as tk
import tkinter.scrolledtext as st
import rx_seriale as rx_ser
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
import queue
running = True
serial_data = ''
filter_data = ''
update_period = 5
serial_object = None
gui = tk.Tk()
gui.title("UART Interface")
my_queue = queue.Queue()
t1 = ''
def plot(valori):
global frame_plot1
try:
# the figure that will contain the plot
fig = Figure(figsize=(4.5, 4), dpi=100)
# adding the subplot
plot1 = fig.add_subplot(111)
#dummy values
y = [i ** 2 for i in range(101)]
# plotting the graph
plot1.plot(y)
# creating the Tkinter canvas
# containing the Matplotlib figure
canvas = FigureCanvasTkAgg(fig, master=frame_plot1)
canvas.draw()
# placing the canvas on the Tkinter window
canvas.get_tk_widget().pack()
# creating the Matplotlib toolbar
#toolbar = NavigationToolbar2Tk(canvas, frame_plot1)
#toolbar.update()
# placing the toolbar on the Tkinter window
#canvas.get_tk_widget().pack()
except Exception as e:
print('Errore:' + str(e))
def update_gui():
global filter_data
global update_period
global my_queue
global type_test, test_status,flag
while (1):
data = my_queue.get(block=True)
text.insert('end', test_status[data[0]] + " - " + type_test[data[1]])
text.insert('end', '\n')
text.see('end')
if (data[1] == 6):
plot(1)
if __name__ == "__main__":
'''
...
all the stuff for design TK windows
...
'''
# threads
t2 = threading.Thread(target=update_gui)
t2.daemon = True
t2.start()
# mainloop
gui.geometry('1000x500')
gui.mainloop()
What goes wrong? Thank you.
I am writing a small program, with the intention to update matplotlib graphs periodically throughout. For this I intend to use clear() and redraw the graph. The clear function does work when called from within the method that creates the graph, but it does not work, when called from a button, eventhough the graph is given as a Parameter.
Below is runnable code in it's most basic form to illustrate the problem.
In this case, clicking the "Update" button does nothing. How would I fix that button to clear the graph?
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tkinter as tk
import numpy as np
class MainWindow(tk.Frame):
def __init__(self, master = None):
tk.Frame.__init__(self, master)
self.add_graph()
def add_graph(self):
fig_sig = plt.figure(figsize=(4,2))
graph = fig_sig.add_subplot(111)
y_values = [0,1,2,3,4,5]
x_values = [1,2,3,4,5,6]
graph.plot(x_values, y_values)
canvas = FigureCanvasTkAgg(fig_sig, master=root)
canvas_widget=canvas.get_tk_widget()
canvas_widget.grid(row = 1, column = 0, columnspan = 3)
canvas.draw()
self.add_widgets(root, graph)
#graph.clear() # Calling graph.clear() here does clear the graph
def add_widgets(self, parent, graph):
update_btn = tk.Button(parent, text = "Update", command = lambda: self.update_graph(graph))
update_btn.grid(row = 8, column = 3)
def update_graph(self, graph):
graph.clear() # calling graph.clear() here does nothing
root = tk.Tk()
oberflaeche = MainWindow(master = root)
oberflaeche.mainloop()
you need to "update" canvas in that case.
define your canvas as: self.canvas = FigureCanvasTkAgg(fig_sig, master=root)
and "update" it:
def update_graph(self, graph):
graph.clear() # calling graph.clear() here does nothing
self.canvas.draw()
This is my first question, so please let me know if I make any faux pas.
I'd like to have a check-box that creates and destroys a plot. If that plot is closed using the window's "x", then I'd like the check-box to be unchecked.
I've got it all working except the unchecking upon figure closing.
It looks like my close function is getting called, but it crashes when I try to manipulate the check-box.
from tkFont import *
from Tkinter import *
import matplotlib.pyplot as plt
import numpy as np
class Application(Frame):
def velocity_cb(self):
if self.velocity_var.get()==1:
print "selected Velocity checkbox"
x = np.arange(100.)
self.figureHandles[1] = plt.figure("Velocity")
# set the close event to run my function
self.figureHandles[1].canvas.mpl_connect('close_event', self.handle_close)
plt.plot(x,x)
plt.show()
else:
print "deselected"
plt.close(self.figureHandles[1])
def handle_close(self,event):
#print self.ground_var.get()
print "X'd velocity window"
#print self.figureHandles[1]
#plt.close(self.figureHandles[1])
self.velocity_var.set(0)
def createWidgets(self):
self.figureHandles = [0]*13
self.velocity_var = IntVar()
self.velocity = Checkbutton(self, text="Velocity", variable=self.velocity_var, command=self.velocity_cb)
self.velocity.grid(row=6,column=0,sticky='W')
self.velocity.var = self.velocity_var
def __init__(self,master=None):
Frame.__init__(self,master)
self.pack()
self.createWidgets()
if __name__=='__main__':
root = Tk()
app = Application(master = root)
app.mainloop()