首页 > 编程问答 >同时加载 2 个 Tkinter 窗口。一个有动画的

同时加载 2 个 Tkinter 窗口。一个有动画的

时间:2024-07-20 21:52:59浏览次数:8  
标签:python matplotlib tkinter tkinter-canvas matplotlib-animation

以下脚本独立运行以运行场景:

首先打印结果,然后以动画结束绘图。

import numpy as np
import matplotlib.pyplot as plt
import tkinter as tk
from tkinter import ttk
from matplotlib.animation import FuncAnimation

def run_model():
    # Input parameters (example values)
    frequency_per_year = 365
    exposure_duration = 240  # minutes
    product_amount = 5  # g
    weight_fraction_substance = 0.6  # fraction
    room_volume = 58  # m³
    ventilation_rate = 0.5  # per hour
    inhalation_rate = 10  # m³/hr
    body_weight_kg = 65  # kg

    # Advanced parameters
    vapour_pressure = 2  # Pa
    application_temperature = 18  # °C
    molecular_weight = 100  # g/mol

    # Conversion functions
    def convert_to_seconds(duration):
        return duration * 60  # minutes to seconds

    def convert_to_kg(amount):
        return amount / 1000  # g to kg

    def convert_to_per_second(rate):
        return rate / 3600  # per hour to per second

    def convert_inhalation_rate(rate):
        return rate  # m³/hr

    def convert_pressure(value):
        return value  # Pa

    def convert_temperature(value):
        return value + 273.15  # °C to K

    def convert_molecular_weight(value):
        return value  # g/mol

    # Convert units
    exposure_duration_seconds = convert_to_seconds(exposure_duration)
    product_amount_kg = convert_to_kg(product_amount)
    ventilation_rate_per_second = convert_to_per_second(ventilation_rate)
    inhalation_rate_m3_hr = convert_inhalation_rate(inhalation_rate)
    temperature_k = convert_temperature(application_temperature)
    vapour_pressure_pa = convert_pressure(vapour_pressure)
    molecular_weight_kg_mol = convert_molecular_weight(molecular_weight)

    # Universal gas constant
    R = 8.314  # J/mol/K

    # Time points (in seconds)
    time_points = np.linspace(0, exposure_duration_seconds, 500)

    # Calculate C_air over time
    C_air_over_time = (product_amount_kg * weight_fraction_substance / room_volume) * np.exp(-ventilation_rate_per_second * time_points)

    # Calculate C_sat
    C_sat = (molecular_weight_kg_mol * vapour_pressure_pa) / (R * temperature_k)

    # Convert C_air_over_time to mg/m^3 for plotting
    C_air_over_time_mg_m3 = C_air_over_time * 1e6

    # Calculate the total inhaled dose
    total_inhaled_volume_m3 = inhalation_rate_m3_hr * (exposure_duration_seconds / 3600)  # m^3
    total_inhaled_dose_kg = np.trapz(C_air_over_time, time_points) * inhalation_rate_m3_hr / 3600  # kg
    total_inhaled_dose_mg = total_inhaled_dose_kg * 1e6  # mg

    # Calculate the external event dose
    external_event_dose_mg_kg_bw = total_inhaled_dose_mg / body_weight_kg  # mg/kg bw

    # Calculate mean event concentration
    mean_event_concentration = np.mean(C_air_over_time_mg_m3)

    # Calculate peak concentration (TWA 15 min)
    time_interval_15min = 15 * 60  # 15 minutes in seconds
    if exposure_duration_seconds >= time_interval_15min:
        time_points_15min = time_points[time_points <= time_interval_15min]
        C_air_15min = C_air_over_time[time_points <= time_interval_15min]
        TWA_15_min = np.mean(C_air_15min) * 1e6  # Convert to mg/m³
    else:
        TWA_15_min = mean_event_concentration

    # Calculate mean concentration on day of exposure
    mean_concentration_day_of_exposure = mean_event_concentration * (exposure_duration_seconds / 86400)

    # Calculate year average concentration
    year_average_concentration = mean_concentration_day_of_exposure * frequency_per_year / 365

    # Calculate cumulative dose over time and convert to mg/kg body weight
    time_step = time_points[1] - time_points[0]
    cumulative_dose_kg = np.cumsum(C_air_over_time) * time_step * inhalation_rate_m3_hr / 3600  # kg
    cumulative_dose_mg = cumulative_dose_kg * 1e6  # mg
    cumulative_dose_mg_kg_bw = cumulative_dose_mg / body_weight_kg  # mg/kg bw

    # Final values for annotations
    final_air_concentration = C_air_over_time_mg_m3[-1]
    final_external_event_dose = cumulative_dose_mg_kg_bw[-1]

    # Prepare results text
    result_text = (
        f"Mean event concentration: {mean_event_concentration:.1e} mg/m³\n"
        f"Peak concentration (TWA 15 min): {TWA_15_min:.1e} mg/m³\n"
        f"Mean concentration on day of exposure: {mean_concentration_day_of_exposure:.1e} mg/m³\n"
        f"Year average concentration: {year_average_concentration:.1e} mg/m³\n"
        f"External event dose: {external_event_dose_mg_kg_bw:.1f} mg/kg bw\n"
        f"External dose on day of exposure: {external_event_dose_mg_kg_bw:.1f} mg/kg bw"
    )

    # Display results in a Tkinter window
    result_window = tk.Tk()
    result_window.title("Model Results")
    ttk.Label(result_window, text=result_text, justify=tk.LEFT).grid(column=0, row=0, padx=10, pady=10)
    ttk.Button(result_window, text="Close", command=result_window.destroy).grid(column=0, row=1, pady=(0, 10))
    result_window.mainloop()

    # Plotting with animation
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))

    # Initialize lines
    line1, = ax1.plot([], [], lw=2)
    line2, = ax2.plot([], [], lw=2)

    # Plot air concentration over time
    def init_air_concentration():
        ax1.set_xlim(0, exposure_duration)
        ax1.set_ylim(0, np.max(C_air_over_time_mg_m3) * 1.1)
        ax1.set_title('Inhalation - Air concentration')
        ax1.set_xlabel('Time (min)')
        ax1.set_ylabel('Concentration (mg/m³)')
        ax1.grid(True)
        return line1,

    def update_air_concentration(frame):
        line1.set_data(time_points[:frame] / 60, C_air_over_time_mg_m3[:frame])
        return line1,

    # Plot external event dose over time
    def init_external_dose():
        ax2.set_xlim(0, exposure_duration)
        ax2.set_ylim(0, np.max(cumulative_dose_mg_kg_bw) * 1.1)
        ax2.set_title('Inhalation - External event dose over time')
        ax2.set_xlabel('Time (min)')
        ax2.set_ylabel('Dose (mg/kg bw)')
        ax2.grid(True)
        return line2,

    def update_external_dose(frame):
        line2.set_data(time_points[:frame] / 60, cumulative_dose_mg_kg_bw[:frame])
        return line2,

    ani1 = FuncAnimation(fig, update_air_concentration, frames=len(time_points), init_func=init_air_concentration, blit=True, interval=300/len(time_points), repeat=False)
    ani2 = FuncAnimation(fig, update_external_dose, frames=len(time_points), init_func=init_external_dose, blit=True, interval=300/len(time_points), repeat=False)

    plt.tight_layout()
    plt.show()

if __name__ == "__main__":
    run_model()

但是,我希望它们同时出现。或者,如果不是,则该一个紧接着另一个,而不必先关闭结果窗口。

对此的各种尝试会导致此问题:

*** 由于未捕获的异常“NSInvalidArgumentException”而终止应用程序,原因:“ -[NSApplication macOSVersion]:无法识别的选择器发送到实例 0x13616f730' *** 首先抛出调用堆栈:

任何帮助表示赞赏。


代码的问题在于,你试图从主线程以外的线程更新 Tkinter GUI。 matplotlib.animation.FuncAnimation 在后台线程中运行动画,当动画更新试图与主线程中的 Tkinter 窗口交互时,就会发生冲突。

以下是如何修改代码以使其正常工作的步骤:

  1. 将绘图逻辑与 GUI 逻辑分开: 将计算结果和更新绘图的代码移动到单独的函数中。
  2. 使用 matplotlib.figure.Figure 嵌入 Matplotlib 图表: 不用 plt.show() ,而是创建一个 Figure Canvas 并将其嵌入到 Tkinter 窗口中。
  3. 使用 after 方法进行动画: 使用 Tkinter 的 after 方法更新动画,而不是 FuncAnimation ,该方法允许你在不阻塞主循环的情况下安排函数在特定时间后运行。

以下是用这些更改修改后的代码:

import numpy as np
import matplotlib.pyplot as plt
import tkinter as tk
from tkinter import ttk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

def run_model():
    # ...(你的计算代码,与之前相同)...

    # --- GUI 代码 ---
    root = tk.Tk()
    root.title("Inhalation Model")

    # 结果框架
    result_frame = ttk.LabelFrame(root, text="Model Results")
    result_frame.grid(row=0, column=0, padx=10, pady=10, sticky="nsew")

    result_text = (
        f"Mean event concentration: {mean_event_concentration:.1e} mg/m³\n"
        f"Peak concentration (TWA 15 min): {TWA_15_min:.1e} mg/m³\n"
        f"Mean concentration on day of exposure: {mean_concentration_day_of_exposure:.1e} mg/m³\n"
        f"Year average concentration: {year_average_concentration:.1e} mg/m³\n"
        f"External event dose: {external_event_dose_mg_kg_bw:.1f} mg/kg bw\n"
        f"External dose on day of exposure: {external_event_dose_mg_kg_bw:.1f} mg/kg bw"
    )
    ttk.Label(result_frame, text=result_text, justify=tk.LEFT).grid(column=0, row=0, padx=10, pady=10)

    # 绘图框架
    plot_frame = ttk.LabelFrame(root, text="Concentration and Dose over Time")
    plot_frame.grid(row=1, column=0, padx=10, pady=10, sticky="nsew")

    # 创建 Matplotlib 图表
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))
    canvas = FigureCanvasTkAgg(fig, master=plot_frame)
    canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)

    # 初始化线
    line1, = ax1.plot([], [], lw=2)
    line2, = ax2.plot([], [], lw=2)

    # 绘图配置
    ax1.set_xlim(0, exposure_duration)
    ax1.set_ylim(0, np.max(C_air_over_time_mg_m3) * 1.1)
    ax1.set_title('Inhalation - Air Concentration')
    ax1.set_xlabel('Time (min)')
    ax1.set_ylabel('Concentration (mg/m³)')
    ax1.grid(True)

    ax2.set_xlim(0, exposure_duration)
    ax2.set_ylim(0, np.max(cumulative_dose_mg_kg_bw) * 1.1)
    ax2.set_title('Inhalation - External Event Dose')
    ax2.set_xlabel('Time (min)')
    ax2.set_ylabel('Dose (mg/kg bw)')
    ax2.grid(True)

    # 动画函数
    def animate(frame):
        line1.set_data(time_points[:frame] / 60, C_air_over_time_mg_m3[:frame])
        line2.set_data(time_points[:frame] / 60, cumulative_dose_mg_kg_bw[:frame])
        canvas.draw()
        if frame < len(time_points):
            root.after(300 // len(time_points), animate, frame + 1) 

    # 启动动画
    animate(0)

    root.mainloop()

if __name__ == "__main__":
    run_model()

在这个修改后的代码中:

  • 我们创建了一个单独的 animate 函数来处理绘图更新。
  • 我们使用 canvas.draw() 更新嵌入式 Matplotlib 图表。
  • 我们使用 root.after() 来安排 animate 函数在每个时间步长后调用自身,从而创建动画。

此方法可确保所有 GUI 更新都在主线程上进行,从而避免了冲突并按预期运行代码。

标签:python,matplotlib,tkinter,tkinter-canvas,matplotlib-animation
From: 78772431

相关文章

  • python反序列化
    之前hgame中遇到python反序列化,这次正好借分享会来尽可能详细学习一下python反序列化基础知识什么是序列化?反序列化?在很多时候为了方便对象传输,我们往往会把一些内容转化成更方便存储、传输的形式。我们把“对象->字符串”的翻译过程称为“序列化”;相应地,把“字符串->对......
  • 我在 python 项目中不断收到“无法识别图像文件中的数据”错误
    我正在尝试向我的TK窗口添加一个图标,但我不断收到一条错误消息:Traceback(mostrecentcalllast):File"C:\Users\roger\source\repos\PythonApplication\PythonApplication.py",line7,in<module>windowIcon=tk.PhotoImage(file="C:/Users/roger/Downloa......
  • Python学习笔记41:游戏篇之外星人入侵(二)
    前言在上一篇文章,我们已经创建好了项目目录,在今天,我们主要编写入口模块的功能。mainmain.py模块是我们游戏程序的入口,所有我们需要在模块中编写游戏主启动以及主页面相关的代码。当前我们的main模块是这样的,这是我们创建项目时默认生成一些代码,接下来我们就要进行我们......
  • Python学习笔记39:进阶篇(二十八)pygame的使用之按键映射及按键失效问题解决
    前言基础模块的知识通过这么长时间的学习已经有所了解,更加深入的话需要通过完成各种项目,在这个过程中逐渐学习,成长。我们的下一步目标是完成pythoncrashcourse中的外星人入侵项目,这是一个2D游戏项目。在这之前,我们先简单学习一下pygame模块。私信我发送消息python资料,......
  • Python学习笔记40:游戏篇之外星人入侵(一)
    前言入门知识已经学完,常用标准库也了解了,pygame入门知识也学了,那么开始尝试小游戏的开发。当然这个小游戏属于比较简单的小游戏,复杂的游戏需要长时间的编写累计开发经验,同时也需要一定的时间才能编写出来。现在的话还是嫩了点。从基础的简单的开始,学习实践,慢慢的成长才......
  • Python学习笔记37:进阶篇(二十六)pygame的使用之输入处理
    前言基础模块的知识通过这么长时间的学习已经有所了解,更加深入的话需要通过完成各种项目,在这个过程中逐渐学习,成长。我们的下一步目标是完成pythoncrashcourse中的外星人入侵项目,这是一个2D游戏项目。在这之前,我们先简单学习一下pygame模块。私信我发送消息python资料,......
  • Python学习笔记38:进阶篇(二十七)pygame的使用之时间与帧数控制
    前言基础模块的知识通过这么长时间的学习已经有所了解,更加深入的话需要通过完成各种项目,在这个过程中逐渐学习,成长。我们的下一步目标是完成pythoncrashcourse中的外星人入侵项目,这是一个2D游戏项目。在这之前,我们先简单学习一下pygame模块。私信我发送消息python资料,......
  • 单击 tkinter 按钮时是否可以删除“动画”?
    我在tkinter中定义了一个按钮,其背景颜色与屏幕相同,因此它只是屏幕上的文本。我遇到的问题是,当单击按钮时,背景会在单击时变为白色,这与深色背景形成鲜明对比。我正在尝试禁用此功能。我尝试设置activebackground='SystemButtonFace',但由于某种原因这不起作用。我不太确......
  • 音频文件降噪及python示例
    操作系统:Windows10_x64Python版本:3.9.2noisereduce版本:3.0.2从事音频相关工作,大概率会碰到降噪问题,今天整理下之前学习音频文件降噪的笔记,并提供Audacity和python示例。我将从以下几个方面展开:noisereduce库介绍使用Audacity进行降噪使用fft滤波降噪使用noisereduce进......
  • Python; Django 添加字符到路径名导致操作系统错误 22
    我一直在尝试让django渲染我创建的模板。起初它说模板不存在,但是一旦我修复了错误,它现在就会向路径添加字符,并且因此找不到模板。路径应该是:C:\\Users\\ABC\\Desktop\\science_crowd\\Lightweight_Django\\placeholder\\home.html但是错误说:它找不到:C:\\Us......