交互式绘图卡在与第一个元素(Python、tkinter)交互时

问题描述 投票:0回答:1

我正在构建一个减少质谱仪数据的程序:它应该向用户提供分析中的原始数据,并允许他们交互式地单击数据以将其作为异常值删除。删除数据后,应自动重新计算数据、趋势线和统计数据。该程序还在数据下方提供了按钮,用于转到下一个或上一个分析以检查异常值。

这对于每个序列中的第一个分析非常有效,但是当我尝试从任何其他分析中删除数据时(例如,单击“下一步”后),绘图将恢复到第一个分析并从该数据集中删除数据,而不是比我正在做的那个要好。看来我一直在与数组中的第一个绘图元素交互,而不是与我正在查看的元素交互(除非我正在查看第一个)。

交互逻辑如下:

  1. main.py 调用函数 setup_interacive_plot() 然后设置主窗口循环。
from import_raw import get_raw_data
from select_sequences import filter_data
from interactive_plot import setup_interactive_plot, interactive_update
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.backends.backend_tkagg as tkagg
import tkinter as tk


# next button
def on_next(current_plot_index):
    current_plot_index = (current_plot_index + 1) % len(filtered_data)
    interactive_update(filtered_data[current_plot_index], figure, canvas, stats_frame)
    update_buttons()


# previous button
def on_previous(current_plot_index):
    current_plot_index = (current_plot_index - 1) % len(filtered_data)
    interactive_update(filtered_data[current_plot_index], figure, canvas, stats_frame)
    update_buttons()


# finish button (to do)
def on_finish():
    window.quit() # replace with call to data reduction script


# exit button
def on_exit():
    window.quit()


# remove previous button on first index
# replace next with finish on last index
def update_buttons():
    prev_button.pack_forget()
    next_button.pack_forget()
    finish_button.pack_forget()

    if current_plot_index > 0:
        prev_button.pack(side=tk.LEFT)
    
    if current_plot_index == len(filtered_data) - 1:
        finish_button.pack(side=tk.RIGHT)
    else:  
        next_button.pack(side=tk.RIGHT)



### main code below ###


matplotlib.use('TkAgg') # forces TkAgg backend to matplotlib (for MacOSX development)

# get and filter raw data
global filtered_data
all_data, sequence_data = get_raw_data() # get all raw data and group into sequences
filtered_data           = filter_data(all_data, sequence_data) # filter out only the selected sequences

# initiate GUI
window = tk.Tk()
window.title('HeMan - Alphachron Data Reduction')

# define and pack main frame
main_frame = tk.Frame(window)
main_frame.pack(fill=tk.BOTH, expand=True)

# define and pack frame for statistics panel on the left
stats_frame = tk.Frame(main_frame, borderwidth=2, relief=tk.SUNKEN)
stats_frame.pack(side=tk.LEFT, fill=tk.Y)

# define and pack frame for data plot and buttons on the right
right_frame = tk.Frame(main_frame)
right_frame.pack(side=tk.RIGHT, fill=tk.X, expand=True)

# define and pack frame for buttons
button_frame = tk.Frame(right_frame)
button_frame.pack(side=tk.BOTTOM, fill=tk.X) 

# define and pack frame for data 
data_frame = tk.Frame(right_frame)
data_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True)

# initialize and pack the data frame
global canvas
figure = plt.figure(figsize=(15,8))
canvas = tkagg.FigureCanvasTkAgg(figure, master=data_frame)
canvas.draw()
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)

# set current_plot_index
current_plot_index = 0

# initialize and pack buttons
global prev_button, exit_button, next_button, finish_button
button_options = {'width': 10, 'height': 2}
exit_button    = tk.Button(button_frame, text="Exit", command=lambda: on_exit(), **button_options)
prev_button    = tk.Button(button_frame, text="Previous", command=lambda: on_previous(current_plot_index), **button_options)
next_button    = tk.Button(button_frame, text="Next", command=lambda: on_next(current_plot_index), **button_options)
finish_button  = tk.Button(button_frame, text="Finish", command=lambda: on_finish(), **button_options)
prev_button.pack(side=tk.LEFT)
exit_button.pack(side=tk.LEFT)
next_button.pack(side=tk.RIGHT)
finish_button.pack(side=tk.RIGHT)

# update the panes
update_buttons()
setup_interactive_plot(filtered_data[current_plot_index], figure, canvas, stats_frame)

# main window loop
window.mainloop()
    Interactive_plot.py 中的
  1. setup_interactive_plot()
    运行
    plot_raw_data()
    来生成图形,对其进行格式化,将其放置在 tk 画布上,并写入关联的统计窗格。然后,它使用
    fig.canvas.mpl_connect()
    命令设置 on_click 交互性。
def setup_interactive_plot(data_entry, fig, canvas, stats_frame):
    plot_raw_data(data_entry, fig, canvas) # draw raw data to data frame
    write_stats_frame(data_entry, stats_frame) # write stats to stats frame

    # interactivity
    fig.canvas.mpl_connect('button_press_event', lambda event: on_click(event, data_entry, fig, canvas, stats_frame))


# draw the raw data plots on the data frame
def plot_raw_data(data_entry, fig, canvas):
    update_plot_and_trendlines(data_entry, fig) # generate the figure
    plt.tight_layout()  # Adjust spacing to prevent labels overlapping
    plt.suptitle(f"He {data_entry.helium_number}: {data_entry.analysis_label}")
    plt.subplots_adjust(top=0.92)
    fig.canvas.draw()
    canvas.draw()

  1. on_click()
    检查点击是否落在绘图窗口内;如果是这样,它会找到最接近点击的数据,并通过将相应的条目
    data_entry.data_status[mass]
    从 1 设置为 0 来“停用”它,反之亦然。
# what to do when the user clicks on the plot
def on_click(event, data_entry, figure, canvas, stats_frame):
    click_coords = (event.xdata, event.ydata) # click coordinates

    # check whether the click landed inside a plot and if so, get the plot and closest_index
    try:
        mass          = event.inaxes.get_title() # get axis title of click plot
        closest_index = find_closest_index(data_entry.raw_data, click_coords, mass) # closest index to click
    except AttributeError:
        return # click outside of bounds, do nothing

    # toggle the clicked datum between 1 (active) and 0 (inactive)
    data_entry.data_status[mass].iloc[closest_index] = (data_entry.data_status[mass].iloc[closest_index] + 1) % 2
    if mass in ['3 amu', '4 amu']: # if any datum is excluded from 3 amu or 4 amu, also exclude it from the 4/3 Ratio
            data_entry.data_status['4/3 Ratio'].iloc[closest_index] = (data_entry.data_status['4/3 Ratio'].iloc[closest_index] + 1) % 2

    interactive_update(data_entry, figure, canvas, stats_frame)
  1. on_click()
    以调用
    interactive_update()
    结束,这将清除图形,使用
    plot_raw_data()
    绘制一个新图形,并使用新的统计数据更新统计数据框架。
# run all interactive update features in one function
def interactive_update(data_entry, fig, canvas, stats_frame):
    fig.clf()
    plot_raw_data(data_entry, fig, canvas) # draw raw data on data frame canvas
    write_stats_frame(data_entry, stats_frame) # write stats to stats frame
    当我单击 main.py 中定义的下一个或上一个按钮时,也会调用
  1. interactive_update():
# next button
def on_next(current_plot_index):
    current_plot_index = (current_plot_index + 1) % len(filtered_data)
    interactive_update(filtered_data[current_plot_index], figure, canvas, stats_frame)
    update_buttons()


# previous button
def on_previous(current_plot_index):
    current_plot_index = (current_plot_index - 1) % len(filtered_data)
    interactive_update(filtered_data[current_plot_index], figure, canvas, stats_frame)
    update_buttons()

我怀疑我做了一些愚蠢的事情,比如错误地使用了全局变量或其他东西,但我很困惑。

以下是撰写本文时的完整脚本(为了便于阅读,我从上面删除了一些不相关的功能和语句):

  1. main.py
  2. 导入_raw.py
  3. interactive_plot.py
  4. plot_data.py

还有脚本 write_stats.py、he_stats.py 和 select_sequences.py。我相信这些是无关紧要的,但如果有人有兴趣运行脚本并自己尝试的话,可以将它们与原始数据一起提供。

python matplotlib tkinter
1个回答
0
投票

我正在为第一个图创建一个单击处理程序,并且没有删除它或为每个新图生成一个新的处理程序。这是更正后的代码:

current_click_handler = None

# attach the click handler to the new figure
def attach_click_handler(analysis, figure, canvas, stats_frame):
    global current_click_handler

    # disconnect the old handler if it exists
    if current_click_handler is not None:
        figure.canvas.mpl_disconnect(current_click_handler)

    click_handler = figure.canvas.mpl_connect('button_press_event', lambda event: on_click(event, analysis, figure, canvas, stats_frame))
    current_click_handler = click_handler

# run all interactive update features in one function
def interactive_update(analysis, figure, canvas, stats_frame):
    figure.clf()
    plot_raw_data(analysis, figure, canvas) # draw raw data on data frame canvas
    write_stats_frame(analysis, stats_frame) # write stats to stats frame

    attach_click_handler(analysis, figure, canvas, stats_frame)


def setup_interactive_plot(analysis, figure, canvas, stats_frame):
    plot_raw_data(analysis, figure, canvas) # draw raw data to data frame
    write_stats_frame(analysis, stats_frame) # write stats to stats frame

    attach_click_handler(analysis, figure, canvas, stats_frame)
© www.soinside.com 2019 - 2024. All rights reserved.