
|----------------|---------------------------|---------------|----------------------------------|
| 所属库 | 函数 / 方法 | 功能作用 | 简易用法 |
| 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()