如何让 tkinter mainloop 等待 matplotlib 点击事件
How do I make tkinter mainloop wait for a matplotlib click event
我正在使用 Tkinter 和 matplotlib 构建一个带有嵌入式绘图的 GUI。我在我的 window 中嵌入了一个图形,现在我希望使用 matplotlib 的事件处理程序从图形中获取两组 x、y 坐标,然后使用这些坐标创建一条直线,该直线从中的数据中减去图。代码的简化版本如下所示:
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()
显然这个例子没有以任何方式绘制或使用线,坐标也不正确,因为它们没有转换为图形的坐标。我尝试用 plt.connect('button_press_event',on_click)
替换 window.bind("<Button-1>",wait_click)
但这不会等待点击,因此发生错误,因为程序试图访问 points
但它是空的。
我想使用matplotlib事件处理的功能,这样我就可以使用event.xdata
和event.inaxes
等方法来避免不必要的额外工作。
谢谢。
您应该使用 canvas.mpl_connect
来触发您的 events,然后检索 xdata
和 ydata
来绘制线条。请参阅下面的示例:
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()
因此,我设法通过调整@Henry Yik 的回答获得了我想要的功能。只需使用 canvas.mpl_disconnect(cid)
.
即可解决该问题
我基本上使用一个按钮来使用一个名为 choose_cords()
的函数,该函数将接收一个名为 draw
的对象,类型为 DrawLine
,其中包含 x 和 y 坐标以构建线条。然后该函数将发出命令 canvas.mpl_connect('button_press_event', draw.get_cords)
以开始监听图表上的点击。注册两次点击后,matplotlib 事件处理程序将从 draw
对象中断开连接。代码是这样的
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()
我在绘制线条后添加了一个 return,因为我每次都想要一个新的线条,而不是一个延续。
再次感谢 Henry 的回答,这引起了这个问题
我正在使用 Tkinter 和 matplotlib 构建一个带有嵌入式绘图的 GUI。我在我的 window 中嵌入了一个图形,现在我希望使用 matplotlib 的事件处理程序从图形中获取两组 x、y 坐标,然后使用这些坐标创建一条直线,该直线从中的数据中减去图。代码的简化版本如下所示:
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()
显然这个例子没有以任何方式绘制或使用线,坐标也不正确,因为它们没有转换为图形的坐标。我尝试用 plt.connect('button_press_event',on_click)
替换 window.bind("<Button-1>",wait_click)
但这不会等待点击,因此发生错误,因为程序试图访问 points
但它是空的。
我想使用matplotlib事件处理的功能,这样我就可以使用event.xdata
和event.inaxes
等方法来避免不必要的额外工作。
谢谢。
您应该使用 canvas.mpl_connect
来触发您的 events,然后检索 xdata
和 ydata
来绘制线条。请参阅下面的示例:
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()
因此,我设法通过调整@Henry Yik 的回答获得了我想要的功能。只需使用 canvas.mpl_disconnect(cid)
.
我基本上使用一个按钮来使用一个名为 choose_cords()
的函数,该函数将接收一个名为 draw
的对象,类型为 DrawLine
,其中包含 x 和 y 坐标以构建线条。然后该函数将发出命令 canvas.mpl_connect('button_press_event', draw.get_cords)
以开始监听图表上的点击。注册两次点击后,matplotlib 事件处理程序将从 draw
对象中断开连接。代码是这样的
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()
我在绘制线条后添加了一个 return,因为我每次都想要一个新的线条,而不是一个延续。
再次感谢 Henry 的回答,这引起了这个问题