Как заставить tkinter mainl oop ждать события щелчка matplotlib - PullRequest
1 голос
/ 28 апреля 2020

Я строю GUI со встроенным графиком, используя Tkinter и matplotlib. Я встроил фигуру в свое окно и теперь пытаюсь использовать обработчик событий 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()

Очевидно, что этот пример не рисует и не использует линию каким-либо образом, а также не являются правильными координатами, поскольку они не преобразуются в координаты графика. , Я попытался заменить window.bind("<Button-1>",wait_click) на plt.connect('button_press_event',on_click), но это не ожидает щелчка, и поэтому возникает ошибка, так как программа пытается получить доступ к points, но она пуста.

Я хотел бы использовать функциональность обработки событий matplotlib, так что я могу использовать такие методы, как event.xdata и event.inaxes, чтобы избежать ненужной дополнительной работы.

Спасибо.

Ответы [ 2 ]

1 голос
/ 28 апреля 2020

Вы должны использовать canvas.mpl_connect для запуска ваших событий , а затем получить 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()
0 голосов
/ 28 апреля 2020

Итак, мне удалось получить желаемую функциональность, настроив ответ @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()

Я добавил возврат после построения линии, потому что я хочу новую строку каждый раз, а не продолжение.

Еще раз спасибо, Генри, за ответ, какой ответ этот

...