如何将 matplotlib 绘图嵌入到 tkinter 窗口中,并能够使用鼠标单击事件更新绘图的内容



我正在尝试生成一个执行以下操作的tkinter GUI:

  • 包含一个 matplotlib 图形
  • 可以响应图中的按钮按下事件,并创建连接由所述事件坐标定义的点的 Line2D 对象。线路的建设应该是立即的;按下按钮后,此新点将追加到现有线条中,图形也会相应更新。
  • 代码还应该能够绘制/绘制多条线

上面的第 1 点和第 3 点已经完成,但我正在为第 2 点而苦苦挣扎。鼠标事件后,绘图永远不会更新,但事件的坐标将存储到 Line2D 对象中。您可以运行此代码并亲自查看。

我尝试使用 figure.canvas.draw() 重新绘制我在轴对象中添加/更新的线条(在我的例子中是 self.ax),但无济于事。

下面是我对此 GUI 的代码:

from tkinter import *
from tkinter import ttk
from matplotlib.lines import Line2D
from matplotlib import pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
class gui:
def __init__(self):

# Miscellaneous Settings
self.numline = 1
self.activeline = 1
self.lines = [Line2D([],[])]
# Parent widget - widget that all subsequent widgets are added to
self.frm = ttk.Frame(Tk(), padding=10)

# Making a grid for widgets to be inserted into
self.frm.grid()

# Initializing StringVar variables
self.activeLineString = StringVar()

# Adding Buttons
self.newline = ttk.Button(self.frm, text='New Line', command=self.addline)#, command='function i havent made yet')

# Adding a dropdown menu to select the line ID to edit and/or make active
self.lineselect = ttk.Combobox(self.frm, textvariable=self.activeline)#.grid(row=2,column=0)
self.lineselect['values'] = [self.numline]

# Affirmitive comment to tell user exactly what line is being edited
self.activeLineString.set('Active Line is: {:<2}'.format(self.lineselect.get()))
# Linking the above stringvar to a label widget
self.activemessage = Label(self.frm, textvariable=self.activeLineString)
# Grid layout for all items
self.newline.grid(row=0,column=0)
self.lineselect.grid(row=1,column=0)
self.activemessage.grid(row=2,column=0)
# Create basic mpl figure and axes objects
self.fig,self.ax = plt.subplots(1,1)
self.fig.dpi = 100

self.ch = FigureCanvasTkAgg(self.fig,self.frm)
self.ch.draw()
self.ch.get_tk_widget().grid(column=1,row=0,rowspan=15)
# Binding certain events to class methods
self.lineselect.bind("<<ComboboxSelected>>", self.comboselect)
self.fig.canvas.mpl_connect('button_press_event',self.onclick)
self.fig.canvas.mpl_connect('key_press_event',self.keypress)

# Main GUI Loop
self.frm.mainloop()
def onclick(self,event):
# edit the data in a 2D list, representing the control points for each path
aind = self.activeline - 1
if int(event.button) == 1: #LEFT (1), MIDDLE (2), RIGHT (3) #add point
print(event.button,event.xdata, event.ydata)
self.lines[aind].get_xdata().append(event.xdata)
self.lines[aind].get_ydata().append(event.ydata)
elif int(event.button) == 3: #delete point
print('delete cp near here')

# update the graphics of each path in the plot
# if new line (length of data at this point in code is 1) then ax.add_line
print(self.lines[aind].get_xdata())
if len(self.lines[aind].get_xdata()) == 1:
self.ax.add_line(self.lines[aind])
print('line added')
# else, just update the line that already exists on the self.fig.canvas
print('plot updating')
print('number of lines in axes: ', len(self.ax.lines))
self.fig.canvas.draw()
#self.lines[aind].figure.canvas.draw()

def keypress(self,event):
if event.key == 'a':
aind = self.activeline - 1
self.lines[aind].set_xdata(self.lines[aind].get_xdata().append(event.xdata))
self.lines[aind].set_ydata(self.lines[aind].get_ydata().append(event.ydata))
elif event.key == 'd':
print('removing control point')

self.lines[self.activeline-1].figure.canvas.draw()

def addline(self):
self.numline += 1
self.activeline = self.numline
self.lineselect['values'] += (self.numline,)
self.lineselect.set(self.lineselect['values'][-1])
self.activeLineString.set('Active Line is: {:<2}'.format(self.lineselect.get()))
# add empty list to self.data
def comboselect(self,event):
self.activeline = int(self.lineselect.get())
self.activeLineString.set('Active Line is: {:<2}'.format(self.lineselect.get()))
test = gui()

我承认,我不清楚画布、艺术家和 GUI 渲染后端是如何工作的,所以我觉得我超过这一点的努力不会有太大价值。我还应该指出,这是我的第一个tkinter项目,所以请原谅我可能不正确的术语。

如果有人能提供一些见解,说明我如何使这个嵌入 tkinter 的图形在按钮事件时自动更新其 Line2D 内容,或者可以找到我的问题的解决方案,我将不胜感激!

提前谢谢你。

这是因为您只更新了Line2D对象的内部数据。如果您有兴趣,请参阅实现。在draw()方法中,self._transformed_path是通过_get_transformed_path()方法使用的,它派生自self._path,这是在recache()方法创建的)一般来说,修改返回的对象 getter 方法来修改对象不是一个好主意,因为返回的值通常是副本。

onclick()中这样做.

...
line = self.lines[aind]
if event.button == 1:
xdata, ydata = line.get_data() 
xdata.append(event.xdata)
ydata.append(event.ydata)
line.set_data(xdata, ydata)
...

作为旁注,您无需对intevent.button,最好执行self.ch.draw()而不是self.fig.canvas.draw()