簡體   English   中英

如何使用鼠標單擊事件將 matplotlib plot 嵌入到 tkinter window 中,並能夠更新繪圖的內容

[英]How to embed a matplotlib plot into a tkinter window, and be able to update the content of the plots, using mouse click events

我正在嘗試生成執行以下操作的 tkinter GUI:

  • 包含一個 matplotlib 圖
  • 可以響應圖中的按鈕按下事件,並創建連接由所述事件的坐標定義的點的 Line2D 對象。 線路的建設應該立即進行; 一旦按下按鈕,這個新點就會附加到現有的線上,圖形也會相應地更新。
  • 該代碼還應該能夠繪制/繪制不止一條線

上面的第 1 點和第 3 點已經完成,但是我正在為第 2 點而苦苦掙扎。鼠標事件后 plot 永遠不會更新,但是事件的坐標存儲到 Line2D object 中。您可以運行此代碼並親自查看。

我嘗試使用 figure.canvas.draw() 重新繪制我在軸 object(在我的例子中為 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 object 的內部數據。(如果您有興趣,請參閱實現。在draw()方法中,通過self._transformed_path _get_transformed_path()方法使用 self._transformed_path 並且它派生自self._path ,它是在recache()方法中創建的)一般情況下,修改object返回的getter方法what來修改object並不是一個好主意,因為返回值通常是一個副本。

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)
        ...

作為旁注,您不需要將event.button轉換為int ,最好使用self.ch.draw()而不是self.fig.canvas.draw()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM