Good morning,
What I am aiming for:
- I want to create a button that when clicked triggers a call back function (even if that function is still running). The call back function is a matplotlib animation
Module:
- For the GUI, I am using tkinter
- For the callback function, I am using a matplotlib animation
Attempt n1:
- I created a button with tkinter that triggers the animation
Problem for attempt n1:
- When I trigger the button, the function runs, but then I cannot push the button again before the function has finished its run. I want to be able to push the button while the function is still running and have the function stop and run again from start.
Code for attempt n1 is basically this one
Attempt n2:
- I then tried to use multi-threading, so that the animation and the GUI run on different threads, but when I do this, I get "Starting a Matplotlib GUI outside of the main thread will likely fail.". This is the code for this second attempt:
Here it is:
import tkinter as tk # Import tkinter
from tkinter import ttk # Import ttk
import threading
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from random import randint
def button():
thread = threading.Thread(target=func)
thread.start()
def func():
fig = plt.figure()
ax = fig.add_subplot(xlim=(0, 1), ylim=(0, 1))
point, = ax.plot([], [], '-')
CumulativeX, CumulativeY=[], []
def animate(i):
CumulativeX.append(randint(0, 10)/10), CumulativeY.append(randint(0, 10)/10)
point.set_data(CumulativeX,CumulativeY)
return point
ani = animation.FuncAnimation(fig, animate, interval=1000)
plt.show()
win = tk.Tk() # Create instance of the Tk class
aButton = ttk.Button(win, text="Click Me!", command=button)
aButton.grid(column=0, row=0) # Adding a Button
win.mainloop() # Start GUI
CodePudding user response:
Combining the example from jfaccioni's comment and your question code, we get something like the below (which works for me on Python 3.10.7)...
import tkinter
from random import randint
import matplotlib.animation as animation
import numpy as np
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
root = tkinter.Tk()
root.wm_title("Embedding in Tk")
fig = Figure(figsize=(5, 4), dpi=100)
t = np.arange(0, 3, 0.01)
ax = fig.add_subplot()
(line,) = ax.plot(t, 2 * np.sin(2 * np.pi * t))
ax.set_xlabel("time [s]")
ax.set_ylabel("f(t)")
CumulativeX, CumulativeY = [], []
canvas = FigureCanvasTkAgg(fig, master=root) # A tk.DrawingArea.
canvas.draw()
restart = [True]
def button():
restart[0] = True
def update(frame, CumulativeX, CumulativeY, restart, *fargs):
if restart[0] is True:
CumulativeY.clear()
CumulativeX.clear()
restart[0] = False
else:
CumulativeX.append(randint(0, 10) / 10)
CumulativeY.append(randint(0, 10) / 10)
line.set_data(CumulativeX, CumulativeY)
_ = animation.FuncAnimation(
fig, update, interval=1000, fargs=(CumulativeX, CumulativeY, restart)
)
# pack_toolbar=False will make it easier to use a layout manager later on.
toolbar = NavigationToolbar2Tk(canvas, root, pack_toolbar=False)
toolbar.update()
button_quit = tkinter.Button(master=root, text="Quit", command=root.destroy)
button_restart = tkinter.Button(master=root, text="Click Me!", command=button)
# 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)
button_restart.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()
Though it's quite verbose, it's not very complicated:
- we are updating the image once per second
- the
button()
function setsrestart[0]
equal toTrue
- the
update()
function with either add another random line segment (ifrestart[0]
isFalse
) or clear the figure and start again (ifrestart[0]
isTrue
)