我可以在嵌入式 FigureCanvasTkAgg 上绘制 TKinter 对象吗?
Can I draw TKinter objects on top of an embedded FigureCanvasTkAgg?
简而言之:
- 我正在创建一个快速的 TKinter API,我首先生成一个 tk.Canvas
- 我正在嵌入一个 FigureCanvasTkAgg canvas 上面有 master = tk.Canvas
- 有了这个我就可以通过 Matplotlib 显示图像
- 现在我想在 FigureCanvasTkAgg 的顶部绘制 TKinter 对象 canvas(例如矩形或按钮)
这可能吗?或者是否有任何特别的建议(即只使用一个 canvas 或另一个)?
这里是一些快速代码:
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
class MyApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.canvas = tk.Canvas(self, width=500, height=500, cursor="cross")
self.canvas.pack(side="top", fill="both", expand=True)
def draw_image_and_button(self):
self.figure_obj = Figure()
a = self.figure_obj.add_axes([0, 0, 1, 1])
imgplot = a.imshow(some_preloaded_data_array, cmap='gray')
# create tkagg canvas
self.canvas_agg = FigureCanvasTkAgg(self.figure_obj, master=self.canvas)
self.canvas_agg.draw()
self.canvas_agg.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
# attempt to draw rectangle
self.rectangle = self.canvas.create_rectangle(0, 0, 100, 100, fill='red')
if __name__ == "__main__":
app = MyApp()
app.draw_image()
app.mainloop()
我的意思是我看到矩形是在图像之前绘制的。也许是我对 FigureCanvasTkAgg 如何附加到 tk.canvas
缺乏了解
谢谢!
好的,这是我最近开发的一个应用程序,其中包含 matplotlib 小部件和鼠标事件。你也可以有 tkinter 小部件,但我没有找到将它们放在 matplolib canvas 之上的方法。我个人更喜欢 matplotlib 小部件而不是 tkinter 小部件,所以我认为它还不错。
您唯一需要做的准备工作是修改 matplotlib 源代码,因为您需要将 canvas 传递给小部件 class,而默认情况下小部件采用数字 canvas 嵌入 tk 时将不起作用(按钮将无响应)。修改其实很简单,还是按顺序来吧。
- 在 matplotlib 文件夹中打开 'widgets.py'(取决于你安装它的位置,在我的例子中,我在 "C:\Program Files\Python37\Lib\site-packages\matplotlib" 中)。
- 转到
class AxesWidget(Widget)
(第90行左右)并使用以下代码修改__init__
方法:
def __init__(self, ax, canvas=None):
self.ax = ax
if canvas is None:
self.canvas = ax.figure.canvas
else:
self.canvas = canvas
self.cids = []
如您所见,与原始代码相比,我添加了关键字参数 canvas=None
。通过这种方式保留了原始功能,但您现在可以将 canvas 传递给小部件。
- 要在 tk 中嵌入的 matplolib canvas 上有一个响应按钮,您现在创建一个小部件并传递使用
FigureCanvasTkAgg
创建的 matplolib canvas。例如对于 Button
你会写
from matplotlib.widgets import Button
ok_button = Button(ax_ok_button, 'Ok', canvas=canvas) # canvas created with FigureCanvasTkAgg
好的,现在我们拥有了在 tk 中嵌入的 matplolib canvas 上拥有 matplolib 小部件所需的所有功能,此外,您还可以拥有鼠标和按键事件,我猜这涵盖了您期望的 95%图形用户界面。请注意,如果您不想修改原始源代码,当然可以创建自己的 class 复制 AxesWidget
class。
您可以在此处找到所有可用的 matplolib 小部件 https://matplotlib.org/3.1.1/api/widgets_api.html
这是您的应用程序的修改版本,我们将所有内容整合在一起:
将 tkinter 导入为 tk
来自 matplotlib.backends.backend_tkagg 导入 FigureCanvasTkAgg、NavigationToolbar2Tk
来自 matplotlib.figure 导入图
来自 matplotlib.widgets 导入按钮
将 numpy 导入为 np
class MyApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.canvas = tk.Canvas(self, width=500, height=500, cursor="cross")
self.canvas.pack(side="top", fill="both", expand=True)
def draw_image_and_button(self):
self.figure_obj = Figure()
self.ax = self.figure_obj.add_subplot()
self.figure_obj.subplots_adjust(bottom=0.25)
some_preloaded_data_array = np.zeros((600,600))
imgplot = self.ax.imshow(some_preloaded_data_array, cmap='gray')
# create tkagg canvas
self.canvas_agg = FigureCanvasTkAgg(self.figure_obj, master=self.canvas)
self.canvas_agg.draw()
self.canvas_agg.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
# add matplolib toolbar
toolbar = NavigationToolbar2Tk(self.canvas_agg, self.canvas)
toolbar.update()
self.canvas_agg._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
# add matplolib widgets
self.ax_ok_B = self.figure_obj.add_subplot(position=[0.2, 0.2, 0.1, 0.03]) # axes position doesn't really matter here because we have the resize event that adjusts widget position
self.ok_B = Button(self.ax_ok_B, 'Ok', canvas=self.canvas_agg)
# add tkinter widgets (outside of the matplolib canvas)
button = tk.Button(master=self, text="Quit", command=self._quit)
button.pack(side=tk.BOTTOM)
# Connect to Events
self.ok_B.on_clicked(self.ok)
self.canvas_agg.mpl_connect('button_press_event', self.press)
self.canvas_agg.mpl_connect('button_release_event', self.release)
self.canvas_agg.mpl_connect('resize_event', self.resize)
self.canvas_agg.mpl_connect("key_press_event", self.on_key_press)
self.protocol("WM_DELETE_WINDOW", self.abort_exec)
def abort_exec(self):
print('Closing with \'x\' is disabled. Please use quit button')
def _quit(self):
print('Bye bye')
self.quit()
self.destroy()
def ok(self, event):
print('Bye bye')
self.quit()
self.destroy()
def press(self, event):
button = event.button
print('You pressed button {}'.format(button))
if event.inaxes == self.ax and event.button == 3:
self.xp = int(event.xdata)
self.yp = int(event.ydata)
self.cid = (self.canvas_agg).mpl_connect('motion_notify_event',
self.draw_line)
self.pltLine = Line2D([self.xp, self.xp], [self.yp, self.yp])
def draw_line(self, event):
if event.inaxes == self.ax and event.button == 3:
self.yd = int(event.ydata)
self.xd = int(event.xdata)
self.pltLine.set_visible(False)
self.pltLine = Line2D([self.xp, self.xd], [self.yp, self.yd], color='r')
self.ax.add_line(self.pltLine)
(self.canvas_agg).draw_idle()
def release(self, event):
button = event.button
(self.canvas_agg).mpl_disconnect(self.cid)
print('You released button {}'.format(button))
def on_key_press(self, event):
print("you pressed {}".format(event.key))
# Resize event is needed if you want your widget to move together with the plot when you resize the window
def resize(self, event):
ax_ok_left, ax_ok_bottom, ax_ok_right, ax_ok_top = self.ax.get_position().get_points().flatten()
B_h = 0.08 # button width
B_w = 0.2 # button height
B_sp = 0.08 # space between plot and button
self.ax_ok_B.set_position([ax_ok_right-B_w, ax_ok_bottom-B_h-B_sp, B_w, B_h])
print('Window was resized')
if __name__ == "__main__":
app = MyApp()
app.draw_image_and_button()
app.mainloop()
好的,让我们看看这个应用程序的功能:
- 按下键盘上的一个键 → 打印按下的键
- 按下鼠标按钮 → 打印按下的按钮(1 = 左,2 = 滚轮,3 = 右)
- 释放鼠标按钮 → 打印释放的按钮
- 在绘图上的任意点上按右键并在按住鼠标按钮的同时画一条线
- 按确定或退出关闭应用程序
- 按'x'关闭window被禁用。
- 调整 window → 绘图和小部件相应缩放
我还冒昧地添加了 classic matplotlib 工具栏,用于缩放等其他功能。
请注意,图像图是使用 add_suplot()
方法添加的,该方法添加了调整大小的功能。这样,当您调整 window 的大小时,绘图会相应地缩放。
我实现的大部分内容您也可以在 matplotlib 的官方教程中找到它们,了解如何嵌入 tk (https://matplotlib.org/3.1.3/gallery/user_interfaces/embedding_in_tk_sgskip.html)。
如果这回答了您的问题,请告诉我。我想分享它,因为几天前我实际上开发了一些非常相似的东西。
简而言之:
- 我正在创建一个快速的 TKinter API,我首先生成一个 tk.Canvas
- 我正在嵌入一个 FigureCanvasTkAgg canvas 上面有 master = tk.Canvas
- 有了这个我就可以通过 Matplotlib 显示图像
- 现在我想在 FigureCanvasTkAgg 的顶部绘制 TKinter 对象 canvas(例如矩形或按钮)
这可能吗?或者是否有任何特别的建议(即只使用一个 canvas 或另一个)?
这里是一些快速代码:
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
class MyApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.canvas = tk.Canvas(self, width=500, height=500, cursor="cross")
self.canvas.pack(side="top", fill="both", expand=True)
def draw_image_and_button(self):
self.figure_obj = Figure()
a = self.figure_obj.add_axes([0, 0, 1, 1])
imgplot = a.imshow(some_preloaded_data_array, cmap='gray')
# create tkagg canvas
self.canvas_agg = FigureCanvasTkAgg(self.figure_obj, master=self.canvas)
self.canvas_agg.draw()
self.canvas_agg.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
# attempt to draw rectangle
self.rectangle = self.canvas.create_rectangle(0, 0, 100, 100, fill='red')
if __name__ == "__main__":
app = MyApp()
app.draw_image()
app.mainloop()
我的意思是我看到矩形是在图像之前绘制的。也许是我对 FigureCanvasTkAgg 如何附加到 tk.canvas
缺乏了解谢谢!
好的,这是我最近开发的一个应用程序,其中包含 matplotlib 小部件和鼠标事件。你也可以有 tkinter 小部件,但我没有找到将它们放在 matplolib canvas 之上的方法。我个人更喜欢 matplotlib 小部件而不是 tkinter 小部件,所以我认为它还不错。
您唯一需要做的准备工作是修改 matplotlib 源代码,因为您需要将 canvas 传递给小部件 class,而默认情况下小部件采用数字 canvas 嵌入 tk 时将不起作用(按钮将无响应)。修改其实很简单,还是按顺序来吧。
- 在 matplotlib 文件夹中打开 'widgets.py'(取决于你安装它的位置,在我的例子中,我在 "C:\Program Files\Python37\Lib\site-packages\matplotlib" 中)。
- 转到
class AxesWidget(Widget)
(第90行左右)并使用以下代码修改__init__
方法:
def __init__(self, ax, canvas=None):
self.ax = ax
if canvas is None:
self.canvas = ax.figure.canvas
else:
self.canvas = canvas
self.cids = []
如您所见,与原始代码相比,我添加了关键字参数 canvas=None
。通过这种方式保留了原始功能,但您现在可以将 canvas 传递给小部件。
- 要在 tk 中嵌入的 matplolib canvas 上有一个响应按钮,您现在创建一个小部件并传递使用
FigureCanvasTkAgg
创建的 matplolib canvas。例如对于Button
你会写
from matplotlib.widgets import Button
ok_button = Button(ax_ok_button, 'Ok', canvas=canvas) # canvas created with FigureCanvasTkAgg
好的,现在我们拥有了在 tk 中嵌入的 matplolib canvas 上拥有 matplolib 小部件所需的所有功能,此外,您还可以拥有鼠标和按键事件,我猜这涵盖了您期望的 95%图形用户界面。请注意,如果您不想修改原始源代码,当然可以创建自己的 class 复制 AxesWidget
class。
您可以在此处找到所有可用的 matplolib 小部件 https://matplotlib.org/3.1.1/api/widgets_api.html
这是您的应用程序的修改版本,我们将所有内容整合在一起:
将 tkinter 导入为 tk 来自 matplotlib.backends.backend_tkagg 导入 FigureCanvasTkAgg、NavigationToolbar2Tk 来自 matplotlib.figure 导入图 来自 matplotlib.widgets 导入按钮 将 numpy 导入为 np
class MyApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.canvas = tk.Canvas(self, width=500, height=500, cursor="cross")
self.canvas.pack(side="top", fill="both", expand=True)
def draw_image_and_button(self):
self.figure_obj = Figure()
self.ax = self.figure_obj.add_subplot()
self.figure_obj.subplots_adjust(bottom=0.25)
some_preloaded_data_array = np.zeros((600,600))
imgplot = self.ax.imshow(some_preloaded_data_array, cmap='gray')
# create tkagg canvas
self.canvas_agg = FigureCanvasTkAgg(self.figure_obj, master=self.canvas)
self.canvas_agg.draw()
self.canvas_agg.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
# add matplolib toolbar
toolbar = NavigationToolbar2Tk(self.canvas_agg, self.canvas)
toolbar.update()
self.canvas_agg._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
# add matplolib widgets
self.ax_ok_B = self.figure_obj.add_subplot(position=[0.2, 0.2, 0.1, 0.03]) # axes position doesn't really matter here because we have the resize event that adjusts widget position
self.ok_B = Button(self.ax_ok_B, 'Ok', canvas=self.canvas_agg)
# add tkinter widgets (outside of the matplolib canvas)
button = tk.Button(master=self, text="Quit", command=self._quit)
button.pack(side=tk.BOTTOM)
# Connect to Events
self.ok_B.on_clicked(self.ok)
self.canvas_agg.mpl_connect('button_press_event', self.press)
self.canvas_agg.mpl_connect('button_release_event', self.release)
self.canvas_agg.mpl_connect('resize_event', self.resize)
self.canvas_agg.mpl_connect("key_press_event", self.on_key_press)
self.protocol("WM_DELETE_WINDOW", self.abort_exec)
def abort_exec(self):
print('Closing with \'x\' is disabled. Please use quit button')
def _quit(self):
print('Bye bye')
self.quit()
self.destroy()
def ok(self, event):
print('Bye bye')
self.quit()
self.destroy()
def press(self, event):
button = event.button
print('You pressed button {}'.format(button))
if event.inaxes == self.ax and event.button == 3:
self.xp = int(event.xdata)
self.yp = int(event.ydata)
self.cid = (self.canvas_agg).mpl_connect('motion_notify_event',
self.draw_line)
self.pltLine = Line2D([self.xp, self.xp], [self.yp, self.yp])
def draw_line(self, event):
if event.inaxes == self.ax and event.button == 3:
self.yd = int(event.ydata)
self.xd = int(event.xdata)
self.pltLine.set_visible(False)
self.pltLine = Line2D([self.xp, self.xd], [self.yp, self.yd], color='r')
self.ax.add_line(self.pltLine)
(self.canvas_agg).draw_idle()
def release(self, event):
button = event.button
(self.canvas_agg).mpl_disconnect(self.cid)
print('You released button {}'.format(button))
def on_key_press(self, event):
print("you pressed {}".format(event.key))
# Resize event is needed if you want your widget to move together with the plot when you resize the window
def resize(self, event):
ax_ok_left, ax_ok_bottom, ax_ok_right, ax_ok_top = self.ax.get_position().get_points().flatten()
B_h = 0.08 # button width
B_w = 0.2 # button height
B_sp = 0.08 # space between plot and button
self.ax_ok_B.set_position([ax_ok_right-B_w, ax_ok_bottom-B_h-B_sp, B_w, B_h])
print('Window was resized')
if __name__ == "__main__":
app = MyApp()
app.draw_image_and_button()
app.mainloop()
好的,让我们看看这个应用程序的功能:
- 按下键盘上的一个键 → 打印按下的键
- 按下鼠标按钮 → 打印按下的按钮(1 = 左,2 = 滚轮,3 = 右)
- 释放鼠标按钮 → 打印释放的按钮
- 在绘图上的任意点上按右键并在按住鼠标按钮的同时画一条线
- 按确定或退出关闭应用程序
- 按'x'关闭window被禁用。
- 调整 window → 绘图和小部件相应缩放
我还冒昧地添加了 classic matplotlib 工具栏,用于缩放等其他功能。
请注意,图像图是使用 add_suplot()
方法添加的,该方法添加了调整大小的功能。这样,当您调整 window 的大小时,绘图会相应地缩放。
我实现的大部分内容您也可以在 matplotlib 的官方教程中找到它们,了解如何嵌入 tk (https://matplotlib.org/3.1.3/gallery/user_interfaces/embedding_in_tk_sgskip.html)。
如果这回答了您的问题,请告诉我。我想分享它,因为几天前我实际上开发了一些非常相似的东西。