You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python Tkinter交互式绘图异常:始终绑定首个分析数据集

质谱仪数据处理程序交互异常问题

我开发了一款质谱仪数据处理程序,核心功能如下:

  • 展示分析原始数据
  • 支持用户点击交互移除异常值,移除后自动重新计算数据、趋势线及统计值
  • 提供按钮切换至上一个/下一个分析数据,进行异常值检查

异常现象:序列中的首个分析数据可完美运行,但切换至其他分析数据(如点击“Next”按钮后),尝试移除异常值时,绘图会回退到首个分析数据,且修改的是首个数据集的数据,而非当前查看的数据集——交互逻辑似乎始终与数组中的首个绘图元素绑定(仅查看首个数据时正常)。


交互逻辑说明

1. main.py初始化流程

主程序调用setup_interactive_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()

2. interactive_plot.py:初始化交互绘图

setup_interactive_plot()负责生成图形、写入统计面板,并绑定点击交互事件:

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

3. 点击交互逻辑

on_click()函数处理点击事件,切换数据点的激活状态,并触发更新:

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

4. 交互更新函数

interactive_update()负责清除旧图形、绘制新图形并更新统计面板:

# 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

5. 切换数据逻辑

点击“Next”或“Previous”按钮时,同样调用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()

怀疑方向

怀疑是全局变量使用不当等低级错误导致,但目前无法定位具体问题。


内容的提问来源于stack exchange,提问作者ohshitgorillas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 08:52:33