可视化界面

|----------------|---------------------------|---------------|----------------------------------|
| 所属库 | 函数 / 方法 | 功能作用 | 简易用法 |
| tkinter | tk.Tk() | 创建程序主窗口 | root = tk.Tk() |
| tkinter | ttk.Label() | 创建文本标签 | ttk.Label(窗口, text="文字") |
| tkinter | ttk.Entry() | 创建输入框 | ttk.Entry(窗口, textvariable=绑定变量) |
| tkinter | ttk.Button() | 创建功能按钮 | ttk.Button(窗口, command=触发函数) |
| tkinter | pack() | 界面控件布局摆放 | 控件.pack() |
| tkinter | filedialog.askdirectory() | 弹出选择文件夹窗口 | path = filedialog.askdirectory() |
| tkinter | messagebox.showinfo() | 弹出成功提示框 | messagebox.showinfo("标题","内容") |
| tkinter | messagebox.showwarning() | 弹出警告提示框 | 用于空值、路径错误提醒 |
| tkinter | messagebox.showerror() | 弹出错误提示框 | 用于读取数据失败提醒 |
| os | os.listdir() | 遍历文件夹所有文件 | os.listdir(文件夹路径) |
| os | os.path.join() | 拼接文件完整路径 | os.path.join(路径,文件名) |
| os | os.path.isdir() | 判断路径是否为文件夹 | 判断选中路径有效性 |
| pandas | pd.read_csv() | 读取 csv 数据文件 | pd.read_csv(路径,encoding="gbk") |
| pandas | df.iloc[] | 按行截取表格数据 | df.iloc[起始:结束] |
| pandas | df.columns | 获取 csv 表头所有列名 | 直接打印查看字段名 |
| pandas | df.reset_index() | 重置数据行索引 | 规整截取后的数据顺序 |
| matplotlib | plt.subplots() | 创建绘图画布与坐标轴 | fig,ax=plt.subplots() |
| matplotlib | ax.plot() | 绘制二维数据曲线 | ax.plot(x轴数据,y轴数据) |
| matplotlib | ax.set_title() | 设置图表标题 | 自定义曲线图名称 |
| matplotlib | ax.set_xlabel() | 设置 X 轴坐标轴名称 | 标注横坐标含义 |
| matplotlib | ax.set_ylabel() | 设置 Y 轴坐标轴名称 | 标注纵坐标含义 |
| matplotlib | ax.grid() | 开启图表网格线 | 提升数据可读性 |
| matplotlib | ax.legend() | 显示曲线图例标签 | 区分多条不同测试曲线 |
| matplotlib | fig.tight_layout() | 自动适配绘图布局 | 防止文字重叠遮挡 |
| matplotlib | FigureCanvasTkAgg() | 把绘图嵌入 tk 窗口 | 实现界面内嵌图表 |
| matplotlib | canvas.draw() | 刷新绘制图像 | 执行绘图后刷新显示 |
| Python 内置 | strip() | 清除字符串首尾空格 | 去除输入多余空格 |
| Python 内置 | split(";") | 按分号分割字符串 | 拆分多个文件名编号 |
| Python 内置 | join() | 列表拼接为字符串 | 批量组合生成文件名 |
| Python 内置 | int() | 字符串转为整数 | 转换起止行文本为数字 |
| Python 内置 | len() | 获取列表 / 数据长度 | 判断文件、数据数量 |

python 复制代码
import os
import pandas as pd
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

'''
MOS管输出+转移特性曲线可视化界面
20260515
'''

plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
plt.rcParams["figure.dpi"] = 100

class MOSDualChipToolUI:
    def __init__(self, root):
        self.root = root
        self.root.title("双芯片输出+转移特性对比工具")
        self.root.geometry("1700x900")

        # ========== 输出特性(左半区) ==========
        self.out_folder1 = tk.StringVar()
        self.out_chip1 = tk.StringVar(value="25B")
        self.out_dev1 = tk.StringVar(value="1-1")
        self.out_count1 = tk.StringVar(value="1;2;3")
        self.out_key1 = tk.StringVar(value="25B-1-1-1;25B-1-1-2;25B-1-1-3")

        self.out_folder2 = tk.StringVar()
        self.out_chip2 = tk.StringVar(value="26B")
        self.out_dev2 = tk.StringVar(value="1-1")
        self.out_count2 = tk.StringVar(value="1;2;3")
        self.out_key2 = tk.StringVar(value="26B-1-1-1;26B-1-1-2;26B-1-1-3")

        self.out_row_start = tk.StringVar(value="1")
        self.out_row_end = tk.StringVar(value="101")
        self.out_ycols = tk.StringVar(value="Gate=0.0V;Gate=0.5V;Gate=1.0V;Gate=1.5V")

        # ========== 转移特性(右半区) ==========
        self.tr_folder1 = tk.StringVar()
        self.tr_chip1 = tk.StringVar(value="25B")
        self.tr_dev1 = tk.StringVar(value="1-1")
        self.tr_count1 = tk.StringVar(value="1;2;3")
        self.tr_key1 = tk.StringVar(value="25B-1-1-1_F-Z_Combined;25B-1-1-2_F-Z_Combined;25B-1-1-3_F-Z_Combined")

        self.tr_folder2 = tk.StringVar()
        self.tr_chip2 = tk.StringVar(value="26B")
        self.tr_dev2 = tk.StringVar(value="1-1")
        self.tr_count2 = tk.StringVar(value="1;2;3")
        self.tr_key2 = tk.StringVar(value="26B-1-1-1_F-Z_Combined;26B-1-1-2_F-Z_Combined;26B-1-1-3_F-Z_Combined")

        self.tr_row_start = tk.StringVar(value="1")
        self.tr_row_end = tk.StringVar(value="101")
        self.tr_ycols = tk.StringVar(value="DrainV=0.1V-F;DrainV=1.05V-F;DrainV=2.0V-F;DrainV=0.1V-Z;DrainV=1.05V-Z;DrainV=2.0V-Z")

        self.tr_scale_type = tk.StringVar(value="linear")
        self.colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd']
        self.create_widgets()

    def create_widgets(self):
        main = ttk.Frame(self.root)
        main.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # ========== 左:输出特性 ==========
        out_frame = ttk.LabelFrame(main, text="📊 输出特性曲线", padding=8)
        out_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5)

        out_ctrl = ttk.Frame(out_frame)
        out_ctrl.pack(fill=tk.X, pady=5)

        ttk.Label(out_ctrl, text="芯片1文件夹").grid(row=0, column=0, sticky=tk.W)
        ttk.Entry(out_ctrl, textvariable=self.out_folder1, width=16).grid(row=0, column=1, padx=2)
        ttk.Button(out_ctrl, text="选择", command=lambda:self.sel_dir(self.out_folder1), width=5).grid(row=0, column=2)

        ttk.Label(out_ctrl, text="芯片编号").grid(row=1, column=0, sticky=tk.W)
        ttk.Entry(out_ctrl, textvariable=self.out_chip1, width=6).grid(row=1, column=1, padx=2)
        ttk.Label(out_ctrl, text="器件编号").grid(row=2, column=0, sticky=tk.W)
        ttk.Entry(out_ctrl, textvariable=self.out_dev1, width=6).grid(row=2, column=1, padx=2)
        ttk.Label(out_ctrl, text="测试次数").grid(row=3, column=0, sticky=tk.W)
        ttk.Entry(out_ctrl, textvariable=self.out_count1, width=6).grid(row=3, column=1, padx=2)
        ttk.Button(out_ctrl, text="生成编号1", command=self.gen_out1).grid(row=1, column=2, rowspan=3)

        ttk.Label(out_ctrl, text="芯片1编号").grid(row=4, column=0, sticky=tk.W, pady=(5,0))
        ttk.Entry(out_ctrl, textvariable=self.out_key1, width=18).grid(row=4, column=1, columnspan=2, padx=2)

        ttk.Label(out_ctrl, text="芯片2文件夹").grid(row=5, column=0, sticky=tk.W, pady=(5,0))
        ttk.Entry(out_ctrl, textvariable=self.out_folder2, width=16).grid(row=5, column=1, padx=2)
        ttk.Button(out_ctrl, text="选择", command=lambda:self.sel_dir(self.out_folder2), width=5).grid(row=5, column=2)

        ttk.Label(out_ctrl, text="芯片编号").grid(row=6, column=0, sticky=tk.W)
        ttk.Entry(out_ctrl, textvariable=self.out_chip2, width=6).grid(row=6, column=1, padx=2)
        ttk.Label(out_ctrl, text="器件编号").grid(row=7, column=0, sticky=tk.W)
        ttk.Entry(out_ctrl, textvariable=self.out_dev2, width=6).grid(row=7, column=1, padx=2)
        ttk.Label(out_ctrl, text="测试次数").grid(row=8, column=0, sticky=tk.W)
        ttk.Entry(out_ctrl, textvariable=self.out_count2, width=6).grid(row=8, column=1, padx=2)
        ttk.Button(out_ctrl, text="生成编号2", command=self.gen_out2).grid(row=6, column=2, rowspan=3)

        ttk.Label(out_ctrl, text="芯片2编号").grid(row=9, column=0, sticky=tk.W, pady=(5,0))
        ttk.Entry(out_ctrl, textvariable=self.out_key2, width=18).grid(row=9, column=1, columnspan=2, padx=2)

        ttk.Label(out_ctrl, text="起始行").grid(row=10, column=0, sticky=tk.W, pady=(5,0))
        ttk.Entry(out_ctrl, textvariable=self.out_row_start, width=6).grid(row=10, column=1, padx=2)
        ttk.Label(out_ctrl, text="结束行").grid(row=11, column=0, sticky=tk.W)
        ttk.Entry(out_ctrl, textvariable=self.out_row_end, width=6).grid(row=11, column=1, padx=2)

        ttk.Label(out_ctrl, text="Y轴列").grid(row=12, column=0, sticky=tk.W, pady=(5,0))
        ttk.Entry(out_ctrl, textvariable=self.out_ycols, width=18).grid(row=12, column=1, columnspan=2, padx=2)

        # ✅ 修复:查看表头 按钮
        ttk.Button(out_ctrl, text="查看表头", command=self.show_out_header).grid(row=13, column=0, pady=3)
        ttk.Button(out_ctrl, text="绘制曲线", command=self.draw_output, style="Accent.TButton").grid(row=13, column=1, columnspan=2, pady=3)

        self.fig_out, self.ax_out = plt.subplots(figsize=(5.5, 5.5))
        self.canvas_out = FigureCanvasTkAgg(self.fig_out, master=out_frame)
        self.canvas_out.get_tk_widget().pack(fill=tk.BOTH, expand=True, pady=5)

        # ========== 右:转移特性 ==========
        tr_frame = ttk.LabelFrame(main, text="📊 转移特性曲线", padding=8)
        tr_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5)

        tr_ctrl = ttk.Frame(tr_frame)
        tr_ctrl.pack(fill=tk.X, pady=5)

        ttk.Label(tr_ctrl, text="芯片1文件夹").grid(row=0, column=0, sticky=tk.W)
        ttk.Entry(tr_ctrl, textvariable=self.tr_folder1, width=16).grid(row=0, column=1, padx=2)
        ttk.Button(tr_ctrl, text="选择", command=lambda:self.sel_dir(self.tr_folder1), width=5).grid(row=0, column=2)

        ttk.Label(tr_ctrl, text="芯片编号").grid(row=1, column=0, sticky=tk.W)
        ttk.Entry(tr_ctrl, textvariable=self.tr_chip1, width=6).grid(row=1, column=1, padx=2)
        ttk.Label(tr_ctrl, text="器件编号").grid(row=2, column=0, sticky=tk.W)
        ttk.Entry(tr_ctrl, textvariable=self.tr_dev1, width=6).grid(row=2, column=1, padx=2)
        ttk.Label(tr_ctrl, text="测试次数").grid(row=3, column=0, sticky=tk.W)
        ttk.Entry(tr_ctrl, textvariable=self.tr_count1, width=6).grid(row=3, column=1, padx=2)
        ttk.Button(tr_ctrl, text="生成编号1", command=self.gen_tr1).grid(row=1, column=2, rowspan=3)

        ttk.Label(tr_ctrl, text="芯片1编号").grid(row=4, column=0, sticky=tk.W, pady=(5,0))
        ttk.Entry(tr_ctrl, textvariable=self.tr_key1, width=18).grid(row=4, column=1, columnspan=2, padx=2)

        ttk.Label(tr_ctrl, text="芯片2文件夹").grid(row=5, column=0, sticky=tk.W, pady=(5,0))
        ttk.Entry(tr_ctrl, textvariable=self.tr_folder2, width=16).grid(row=5, column=1, padx=2)
        ttk.Button(tr_ctrl, text="选择", command=lambda:self.sel_dir(self.tr_folder2), width=5).grid(row=5, column=2)

        ttk.Label(tr_ctrl, text="芯片编号").grid(row=6, column=0, sticky=tk.W)
        ttk.Entry(tr_ctrl, textvariable=self.tr_chip2, width=6).grid(row=6, column=1, padx=2)
        ttk.Label(tr_ctrl, text="器件编号").grid(row=7, column=0, sticky=tk.W)
        ttk.Entry(tr_ctrl, textvariable=self.tr_dev2, width=6).grid(row=7, column=1, padx=2)
        ttk.Label(tr_ctrl, text="测试次数").grid(row=8, column=0, sticky=tk.W)
        ttk.Entry(tr_ctrl, textvariable=self.tr_count2, width=6).grid(row=8, column=1, padx=2)
        ttk.Button(tr_ctrl, text="生成编号2", command=self.gen_tr2).grid(row=6, column=2, rowspan=3)

        ttk.Label(tr_ctrl, text="芯片2编号").grid(row=9, column=0, sticky=tk.W, pady=(5,0))
        ttk.Entry(tr_ctrl, textvariable=self.tr_key2, width=18).grid(row=9, column=1, columnspan=2, padx=2)

        ttk.Label(tr_ctrl, text="Y轴坐标:").grid(row=10, column=0, sticky=tk.W, pady=(5,0))
        scale_frame = ttk.Frame(tr_ctrl)
        scale_frame.grid(row=10, column=1, columnspan=2, sticky=tk.W)
        ttk.Radiobutton(scale_frame, text="线性坐标", variable=self.tr_scale_type, value="linear").pack(side=tk.LEFT, padx=2)
        ttk.Radiobutton(scale_frame, text="对数坐标", variable=self.tr_scale_type, value="log").pack(side=tk.LEFT, padx=2)

        ttk.Label(tr_ctrl, text="起始行").grid(row=11, column=0, sticky=tk.W, pady=(5,0))
        ttk.Entry(tr_ctrl, textvariable=self.tr_row_start, width=6).grid(row=11, column=1, padx=2)
        ttk.Label(tr_ctrl, text="结束行").grid(row=12, column=0, sticky=tk.W)
        ttk.Entry(tr_ctrl, textvariable=self.tr_row_end, width=6).grid(row=12, column=1, padx=2)

        ttk.Label(tr_ctrl, text="Y轴列").grid(row=13, column=0, sticky=tk.W, pady=(5,0))
        ttk.Entry(tr_ctrl, textvariable=self.tr_ycols, width=18).grid(row=13, column=1, columnspan=2, padx=2)

        # ✅ 修复:查看表头 按钮
        ttk.Button(tr_ctrl, text="查看表头", command=self.show_tr_header).grid(row=14, column=0, pady=3)
        ttk.Button(tr_ctrl, text="绘制曲线", command=self.draw_transfer, style="Accent.TButton").grid(row=14, column=1, columnspan=2, pady=3)

        self.fig_tr, self.ax_tr = plt.subplots(figsize=(5.5, 5.5))
        self.canvas_tr = FigureCanvasTkAgg(self.fig_tr, master=tr_frame)
        self.canvas_tr.get_tk_widget().pack(fill=tk.BOTH, expand=True, pady=5)

    def sel_dir(self, var):
        p = filedialog.askdirectory()
        if p:
            var.set(p)

    def gen_out1(self):
        c, d, ts = self.out_chip1.get().strip(), self.out_dev1.get().strip(), self.out_count1.get().strip()
        if not all([c, d, ts]):
            messagebox.showwarning("提示","请填写完整")
            return
        keys = [f"{c}-{d}-{t.strip()}" for t in ts.split(";") if t.strip()]
        self.out_key1.set(";".join(keys))

    def gen_out2(self):
        c, d, ts = self.out_chip2.get().strip(), self.out_dev2.get().strip(), self.out_count2.get().strip()
        if not all([c, d, ts]):
            messagebox.showwarning("提示","请填写完整")
            return
        keys = [f"{c}-{d}-{t.strip()}" for t in ts.split(";") if t.strip()]
        self.out_key2.set(";".join(keys))

    def gen_tr1(self):
        c, d, ts = self.tr_chip1.get().strip(), self.tr_dev1.get().strip(), self.tr_count1.get().strip()
        if not all([c, d, ts]):
            messagebox.showwarning("提示","请填写完整")
            return
        keys = [f"{c}-{d}-{t.strip()}_F-Z_Combined" for t in ts.split(";") if t.strip()]
        self.tr_key1.set(";".join(keys))

    def gen_tr2(self):
        c, d, ts = self.tr_chip2.get().strip(), self.tr_dev2.get().strip(), self.tr_count2.get().strip()
        if not all([c, d, ts]):
            messagebox.showwarning("提示","请填写完整")
            return
        keys = [f"{c}-{d}-{t.strip()}_F-Z_Combined" for t in ts.split(";") if t.strip()]
        self.tr_key2.set(";".join(keys))

    def get_files(self, folder, keys_str):
        if not folder or not os.path.isdir(folder):
            return []
        keys = [k.strip() for k in keys_str.split(";") if k.strip()]
        files = []
        for f in os.listdir(folder):
            if f.endswith(".csv"):
                for k in keys:
                    if k in f:
                        files.append(os.path.join(folder, f))
                        break
        return files

    def read_df(self, path, s, e):
        try:
            df = pd.read_csv(path, encoding="gbk")
            return df.iloc[int(s)-1:int(e)].reset_index(drop=True)
        except:
            return None

    def load(self, folder, key_str, s, e):
        files = self.get_files(folder, key_str)
        data = []
        for f in files:
            df = self.read_df(f, s, e)
            if df is not None:
                data.append(df)
        return data

    def plot_curves(self, ax, fig, canvas, d1, d2, y_cols, title, chip1_info, chip2_info, scale_type="linear"):
        ax.clear()
        cols = [c.strip() for c in y_cols.split(";") if c.strip()]
        ax.set_yscale(scale_type)

        chip1, dev1 = chip1_info
        chip2, dev2 = chip2_info

        for i, col in enumerate(cols):
            color = self.colors[i % len(self.colors)]
            # 芯片1(实线)
            for j, df in enumerate(d1):
                if col in df.columns:
                    label = f"{chip1}-{dev1} 第{j+1}次 | {col}"
                    ax.plot(df.iloc[:,0], df[col], color=color, linewidth=2.5, label=label)
            # 芯片2(虚线)
            for j, df in enumerate(d2):
                if col in df.columns:
                    label = f"{chip2}-{dev2} 第{j+1}次 | {col}"
                    ax.plot(df.iloc[:,0], df[col], color=color, linestyle="--", linewidth=2.5, label=label)

        ax.set_title(title, fontsize=12)
        ax.set_xlabel(d1[0].columns[0], fontsize=10)
        ax.set_ylabel("电流", fontsize=10)
        ax.grid(alpha=0.3)
        ax.legend(fontsize=8, loc="upper left", bbox_to_anchor=(1,1))
        fig.tight_layout()
        canvas.draw()

    # ✅ 修复:输出特性查看表头
    def show_out_header(self):
        folder = self.out_folder1.get()
        key_str = self.out_key1.get()
        fs = self.get_files(folder, key_str)
        if fs:
            df = pd.read_csv(fs[0], encoding="gbk", nrows=1)
            messagebox.showinfo("输出特性表头", "\n".join(df.columns))
        else:
            messagebox.showwarning("提示","未找到匹配文件")

    # ✅ 修复:转移特性查看表头
    def show_tr_header(self):
        folder = self.tr_folder1.get()
        key_str = self.tr_key1.get()
        fs = self.get_files(folder, key_str)
        if fs:
            df = pd.read_csv(fs[0], encoding="gbk", nrows=1)
            messagebox.showinfo("转移特性表头", "\n".join(df.columns))
        else:
            messagebox.showwarning("提示","未找到匹配文件")

    def draw_output(self):
        d1 = self.load(self.out_folder1.get(), self.out_key1.get(), self.out_row_start.get(), self.out_row_end.get())
        d2 = self.load(self.out_folder2.get(), self.out_key2.get(), self.out_row_start.get(), self.out_row_end.get())
        if not d1:
            messagebox.showerror("错误","芯片1无有效数据")
            return
        chip1_info = (self.out_chip1.get(), self.out_dev1.get())
        chip2_info = (self.out_chip2.get(), self.out_dev2.get())
        self.plot_curves(self.ax_out, self.fig_out, self.canvas_out, d1, d2, self.out_ycols.get(),
                        "输出特性曲线", chip1_info, chip2_info)
        messagebox.showinfo("完成","输出特性绘制完成!")

    def draw_transfer(self):
        d1 = self.load(self.tr_folder1.get(), self.tr_key1.get(), self.tr_row_start.get(), self.tr_row_end.get())
        d2 = self.load(self.tr_folder2.get(), self.tr_key2.get(), self.tr_row_start.get(), self.tr_row_end.get())
        scale = self.tr_scale_type.get()
        if not d1:
            messagebox.showerror("错误","芯片1无有效数据")
            return
        chip1_info = (self.tr_chip1.get(), self.tr_dev1.get())
        chip2_info = (self.tr_chip2.get(), self.tr_dev2.get())
        self.plot_curves(self.ax_tr, self.fig_tr, self.canvas_tr, d1, d2, self.tr_ycols.get(),
                        "转移特性曲线", chip1_info, chip2_info, scale_type=scale)
        messagebox.showinfo("完成",f"转移特性绘制完成!\n当前坐标:{'对数坐标' if scale=='log' else '线性坐标'}")

if __name__ == "__main__":
    root = tk.Tk()
    style = ttk.Style()
    if "clam" in style.theme_names():
        style.theme_use("clam")
    app = MOSDualChipToolUI(root)
    root.mainloop()
相关推荐
weixin_444012931 小时前
CSS如何快速实现网站换肤功能_利用CSS变量重置全局颜色方案
jvm·数据库·python
kgduu1 小时前
python中的魔法方法
开发语言·python
m0_596749091 小时前
Vue.js计算属性computed依赖追踪与副作用函数effect关联机制
jvm·数据库·python
fox_lht1 小时前
12.3.使用生命周期使引用一直有用
开发语言·后端·rust
神明9311 小时前
Golang testing怎么写单元测试_Golang单元测试教程【经典】
jvm·数据库·python
开发者联盟league1 小时前
在cursor中配置c/c++开发环境
c语言·开发语言·c++
初圣魔门首席弟子1 小时前
bug 2026.05.15(以前能运行的java springboot项目突然间不能运行后台数据了)
java·开发语言·bug
求知也求真佳1 小时前
S19|MCP 与插件:多 Agent 平台 —— 外部能力总线,让外部工具安全接入
开发语言·agent
keineahnung23451 小时前
為什麼要有 eval_is_non_overlapping_and_dense?PyTorch 包裝層與調用端解析
人工智能·pytorch·python·深度学习