Left Mouse Button(DFS)

本文介绍了一种解决经典扫雷游戏的算法。通过深度优先搜索(DFS)策略来最小化左键点击次数,确保游戏胜利的同时减少鼠标损耗。文章详细解释了如何遍历地图并标记已知安全区域,最终计算出完成游戏所需的最少操作。

Description

  Mine sweeper is a very popular small game in Windows operating system. The object of the game is to find mines, and mark them out. You mark them by clicking your right mouse button. Then you will place a little flag where you think the mine is. You click your left mouse button to claim a square as not being a mine. If this square is really a mine, it explodes, and you lose. Otherwise, there are two cases. In the first case, a little colored numbers, ranging from 1 to 8, will display on the corresponding square. The number tells you how many mines are adjacent to the square. For example, if you left-clicked a square, and a little 8 appeared, then you would know that this square is surrounded by 8 mines, all 8 of its adjacent squares are mines. In the second case, when you left-click a square whose all adjacent squares are not mines, then all its adjacent squares (8 of its adjacent squares) are mine-free. If some of these adjacent squares also come to the second case, then such deduce can go on. In fact, the computer will help you to finish such deduce process and left-click all mine-free squares in the process. The object of the game is to uncover all of the non-mine squares, without exploding any actual mines. Tom is very interesting in this game. Unfortunately his right mouse button is broken, so he could only use his left mouse button. In order to avoid damage his mouse, he would like to use the minimum number of left clicks to finish mine sweeper. Given the current situation of the mine sweeper, your task is to calculate the minimum number of left clicks.

                                                                                

Input

  The first line of the input contains an integer T (T <= 12), indicating the number of cases. Each case begins with a line containing an integer n (5 <= n <= 9), the size of the mine sweeper is n×n. Each of the following n lines contains n characters M ij(1 <= i,j <= n), M ij denotes the status of the square in row i and column j, where ‘@’ denotes mine, ‘0-8’ denotes the number of mines adjacent to the square, specially ‘0’ denotes there are no mines adjacent to the square. We guarantee that the situation of the mine sweeper is valid. 

Output

  For each test case, print a line containing the test case number (beginning with 1) and the minimum left mouse button clicks to finish the game.

Sample Input

1
9
001@11@10
001111110
001111110
001@22@10
0012@2110
221222011
@@11@112@
2211111@2
000000111

Sample Output

Case 1: 24

解析

 根据扫雷的规则,点在0的位置话,会将一片连在一起的0全部显示出来外加0的周围一圈。而非零的位置(不包括雷)点一次只能显示一个位置。问说最少要多少步可以点完所有没有雷的位置。

 遍历图,碰到'0'的时候用dfs搜索将一片整体的全标记起来。然后最后再计算没有标记且不是雷的位置的个数,加上前面调用dfs的次数就是答案了。

代码

#include<stdio.h>
#include<string.h>
#define MAX 10
int dir[8][2]={{0,1},{0,-1},{1,0},{-1,0},{1,-1},{1,1},{-1,-1},{-1,1}};
int vis[MAX][MAX],n;
char maze[MAX][MAX];
void DFS(int x,int y)
{
    vis[x][y]=1;
    if(maze[x][y]!='0') return ;
    int i,tempx,tempy;
    for(i=0;i<8;i++)
    {
        tempx=x+dir[i][0];
        tempy=y+dir[i][1];
        if(tempx<0||tempx>n||tempy<0||tempy>n)
            continue;
        if(!vis[tempx][tempy])
            DFS(tempx,tempy);
    }
    return ;
}
int main()
{
    int T,kase=1;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        for(int i=0;i<n;i++)
        {
            scanf("%s",maze[i]);
        }
        memset(vis,0,sizeof(vis));
        int ans=0,i,j;
       for(i=0;i<n;i++)
       {
         for(j=0;j<n;j++)
         {
            if(!vis[i][j]&&maze[i][j]=='0')
            {
                DFS(i,j);
                ans+=1;
            }
         }
       }
       for(i=0;i<n;i++)
       {
          for(j=0;j<n;j++)
          {
            if(!vis[i][j]&&maze[i][j]!='@')
                ans+=1;
          }
       }
      printf("Case %d: %d\n",kase,ans);
      kase+=1;
    }
    return 0;
}

 

import os import re import sys import tkinter as tk from tkinter import ttk, Frame, Scrollbar, Canvas from tkinter import scrolledtext, messagebox import pandas as pd import numpy as np from glob import glob import seaborn as sns from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk import matplotlib.ticker as ticker import matplotlib.pyplot as plt from matplotlib.font_manager import FontProperties from matplotlib.figure import Figure plt.switch_backend("TkAgg") plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"] plt.rcParams["axes.unicode_minus"] = False def find_batch_folders(path, batch_number): target_dir = "4WCSVLog" batch_folders = [] for root, dirs, files in os.walk(path): path_components = os.path.normpath(root).split(os.sep) if target_dir in path_components and batch_number in dirs: full_path = os.path.join(root, batch_number) batch_folders.append(full_path) return batch_folders def get_csv_files_path1(folder): all_files = glob(os.path.join(folder, "*-SVP1B*.csv")) return [f for f in all_files if "HEAD" not in f] def get_csv_files_path2(folder): return glob(os.path.join(folder, "*-SVP1B*.csv")) def parse_svp1b(filename): match = re.search(r"svp1b([0-9A-Z])([0-9])([0-9])", filename, re.IGNORECASE) if match: piece, line, file_num = match.groups() piece_num = 10 + ord(piece.upper()) - ord("A") if piece.isalpha() else int(piece) return piece_num, int(line), int(file_num) return None def read_csv_file(file, start_row=0, start_col=0): df = pd.read_csv(file, header=None, skiprows=start_row) return df.iloc[:, start_col:] if start_col > 0 else df def merge_files(files, path_type): piece_dict = {} for file in files: svp_info = parse_svp1b(os.path.basename(file).lower()) if not svp_info: continue piece, line, file_num = svp_info key = (piece, line) df = read_csv_file(file, start_row=(2 if path_type == 1 else 4), start_col=(0 if path_type == 1 else 18)) if df.empty: continue if key not in piece_dict: piece_dict[key] = [] piece_dict[key].append((file_num, df)) sorted_keys = sorted(piece_dict.keys(), key=lambda x: (x[0], x[1])) global_col_counter = 1 final_dfs = [] for key in sorted_keys: file_dfs = sorted(piece_dict[key], key=lambda x: x[0]) base_df = None for file_num, df in file_dfs: if base_df is None: new_columns = [f"net{global_col_counter + i}" for i in range(len(df.columns))] global_col_counter += len(df.columns) df.columns = new_columns base_df = df else: new_columns = [f"net{global_col_counter + i}" for i in range(len(df.columns))] global_col_counter += len(df.columns) df.columns = new_columns base_df = pd.concat([base_df, df], axis=1) if base_df is not None: final_dfs.append(base_df) return pd.concat(final_dfs, axis=0, ignore_index=True) if final_dfs else pd.DataFrame() def collect_batch_data(batch_list): batch_data_dict = {} path1 = r"\\10.127.1.248\c1tst\7750" path2 = r"\\10.127.1.248\c1tst\7755" for idx, batch_number in enumerate(batch_list, 1): print(f"=== 处理第 {idx}/{len(batch_list)} 个批次:{batch_number} ===") batch_data = pd.DataFrame() folders1 = find_batch_folders(path1, batch_number) if folders1: for folder1 in folders1: files = get_csv_files_path1(folder1) if files: current_data = merge_files(files, path_type=1) batch_data = pd.concat([batch_data, current_data], axis=0, ignore_index=True) print(f"从路径1找到 {len(files)} 个数据文件") else: folders2 = find_batch_folders(path2, batch_number) if folders2: for folder2 in folders2: files = get_csv_files_path2(folder2) if files: current_data = merge_files(files, path_type=2) batch_data = pd.concat([batch_data, current_data], axis=0, ignore_index=True) print(f"从路径2找到 {len(files)} 个数据文件") else: print(f"批次 {batch_number}:未找到对应文件夹,跳过\n") continue if not batch_data.empty: batch_data_dict[batch_number] = batch_data print(f"批次 {batch_number} 数据合并完成,共 {len(batch_data)} 行\n") else: print(f"批次 {batch_number}:无有效数据,跳过\n") return batch_data_dict class RedirectText: def __init__(self, text_widget): self.text_widget = text_widget def write(self, string): self.text_widget.insert(tk.END, string) self.text_widget.see(tk.END) def flush(self): pass class NetPlotViewer: """高级NET图表查看器,支持分页和无限滚动""" def __init__(self, root, batch_data_dict, batch_list, net_list): self.root = root self.batch_data_dict = batch_data_dict self.batch_list = batch_list self.net_list = net_list # 预先计算并缓存所有NET的数据 self.net_data_dict = self._precompute_net_data() # 分页配置 self.per_page = 10 self.current_page = 0 self.total_pages = (len(net_list) + self.per_page - 1) // self.per_page # 无限滚动配置 self.loaded_count = 0 self.load_step = 10 self.loading = False self.view_mode = "pagination" # 或 "infinite_scroll" # 创建主窗口 self.create_main_window() def _precompute_net_data(self): """预先计算并缓存所有NET的数据""" net_data_dict = {} for net in self.net_list: net_data = pd.DataFrame() for batch in self.batch_list: if batch in self.batch_data_dict and net in self.batch_data_dict[batch].columns: net_data[f"批次 {batch}"] = self.batch_data_dict[batch][net] net_data_dict[net] = net_data return net_data_dict def create_main_window(self): """创建主窗口和控件""" self.plot_window = tk.Toplevel(self.root) self.plot_window.title("批次数据分布箱线图") self.plot_window.geometry("1200x800") # 创建顶部控制面板 self.create_control_panel() # 创建内容区域 self.create_content_area() # 初始显示 self.update_display() def create_control_panel(self): """创建顶部控制面板""" control_frame = tk.Frame(self.plot_window, bd=1, relief=tk.RIDGE, padx=10, pady=10) control_frame.pack(fill=tk.X, padx=10, pady=(10, 5)) # 视图模式选择 tk.Label(control_frame, text="视图模式:", font=("SimHei", 10)).pack(side=tk.LEFT, padx=5) self.view_mode_var = tk.StringVar(value="分页视图" if self.view_mode == "pagination" else "无限滚动") view_mode_menu = ttk.Combobox( control_frame, textvariable=self.view_mode_var, values=["分页视图", "无限滚动"], width=12, state="readonly" ) view_mode_menu.pack(side=tk.LEFT, padx=5) view_mode_menu.bind("<<ComboboxSelected>>", self.change_view_mode) # 分页控制 self.pagination_frame = tk.Frame(control_frame) self.pagination_frame.pack(side=tk.LEFT, padx=20) # 分页控件 self.prev_btn = tk.Button( self.pagination_frame, text="上一页", command=lambda: self.change_page(-1), state=tk.DISABLED ) self.prev_btn.pack(side=tk.LEFT, padx=5) self.page_label = tk.Label(self.pagination_frame, text="1/1", font=("SimHei", 10)) self.page_label.pack(side=tk.LEFT, padx=5) self.next_btn = tk.Button( self.pagination_frame, text="下一页", command=lambda: self.change_page(1), state=tk.NORMAL if self.total_pages > 1 else tk.DISABLED ) self.next_btn.pack(side=tk.LEFT, padx=5) # 跳转控件 tk.Label(self.pagination_frame, text="跳转到:", font=("SimHei", 9)).pack(side=tk.LEFT, padx=(20, 0)) self.page_entry = tk.Entry(self.pagination_frame, width=4, font=("SimHei", 9)) self.page_entry.pack(side=tk.LEFT, padx=2) self.page_entry.insert(0, "1") self.goto_btn = tk.Button( self.pagination_frame, text="跳转", command=self.goto_page, font=("SimHei", 9) ) self.goto_btn.pack(side=tk.LEFT, padx=2) # 每页数量选择 tk.Label(self.pagination_frame, text="每页数量:", font=("SimHei", 9)).pack(side=tk.LEFT, padx=(20, 0)) self.per_page_var = tk.StringVar(value=str(self.per_page)) self.per_page_dropdown = ttk.Combobox( self.pagination_frame, textvariable=self.per_page_var, values=["5", "10", "20", "50"], width=4, state="readonly" ) self.per_page_dropdown.pack(side=tk.LEFT, padx=2) self.per_page_dropdown.bind("<<ComboboxSelected>>", self.change_per_page) # 关闭按钮 close_btn = tk.Button( control_frame, text="关闭窗口", command=self.plot_window.destroy, bg="#f44336", fg="white", font=("SimHei", 10), ) close_btn.pack(side=tk.RIGHT, padx=10) # 状态标签 self.status_label = tk.Label(control_frame, text="", font=("SimHei", 9), fg="#666") self.status_label.pack(side=tk.RIGHT, padx=10) def create_content_area(self): """创建内容显示区域""" # 创建滚动框架 self.scroll_frame = tk.Frame(self.plot_window) self.scroll_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10)) # 添加垂直滚动条 self.vscrollbar = tk.Scrollbar(self.scroll_frame, orient=tk.VERTICAL) self.vscrollbar.pack(side=tk.RIGHT, fill=tk.Y) # 创建画布用于滚动 self.canvas = tk.Canvas(self.scroll_frame, yscrollcommand=self.vscrollbar.set) self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) self.vscrollbar.config(command=self.canvas.yview) # 创建内容框架 self.content_frame = tk.Frame(self.canvas) self.canvas_frame = self.canvas.create_window((0, 0), window=self.content_frame, anchor="nw") # 绑定配置事件 self.content_frame.bind("<Configure>", self.on_frame_configure) # 绑定鼠标滚轮事件 self.canvas.bind_all("<MouseWheel>", self.on_mousewheel) def on_frame_configure(self, event=None): """更新滚动区域""" self.canvas.configure(scrollregion=self.canvas.bbox("all")) # 检查是否需要加载更多(无限滚动模式) if self.view_mode == "infinite_scroll": self.check_load_more() def on_mousewheel(self, event): """处理鼠标滚轮事件""" self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units") # 检查是否需要加载更多(无限滚动模式) if self.view_mode == "infinite_scroll": self.check_load_more() def change_view_mode(self, event=None): """切换视图模式""" new_mode = "pagination" if self.view_mode_var.get() == "分页视图" else "infinite_scroll" if new_mode != self.view_mode: self.view_mode = new_mode self.update_display() # 更新分页控件的可见性 if self.view_mode == "pagination": self.pagination_frame.pack(side=tk.LEFT, padx=20) else: self.pagination_frame.pack_forget() def change_per_page(self, event=None): """更改每页显示数量""" try: new_per_page = int(self.per_page_var.get()) if new_per_page != self.per_page: self.per_page = new_per_page self.total_pages = (len(self.net_list) + self.per_page - 1) // self.per_page self.current_page = 0 self.update_display() except ValueError: pass def change_page(self, delta): """翻页""" new_page = max(0, min(self.total_pages - 1, self.current_page + delta)) if new_page != self.current_page: self.current_page = new_page self.update_display() def goto_page(self): """跳转到指定页码""" try: page_num = int(self.page_entry.get()) - 1 if 0 <= page_num < self.total_pages: self.current_page = page_num self.update_display() else: messagebox.showerror("错误", f"页码必须在 1 到 {self.total_pages} 之间") except ValueError: messagebox.showerror("错误", "请输入有效的页码数字") def check_load_more(self): """检查是否需要加载更多(无限滚动模式)""" if self.view_mode != "infinite_scroll" or self.loading: return # 获取滚动位置 scroll_position = self.canvas.yview() # 如果滚动到底部附近(90%位置),加载更多 if scroll_position[1] > 0.9 and self.loaded_count < len(self.net_list): self.load_more() def load_more(self): """加载更多图表(无限滚动模式)""" if self.loading or self.loaded_count >= len(self.net_list): return self.loading = True # 显示加载状态 self.status_label.config(text=f"正在加载数据... ({self.loaded_count}/{len(self.net_list)})") self.plot_window.update() start_idx = self.loaded_count end_idx = min(self.loaded_count + self.load_step, len(self.net_list)) current_nets = self.net_list[start_idx:end_idx] # 创建加载动画 loading_label = tk.Label( self.content_frame, text="加载中...", font=("SimHei", 10, "italic"), fg="#666" ) loading_label.grid(row=self.content_frame.grid_size()[1], column=0, sticky="ew", pady=10) # 短暂延迟,让用户看到加载提示 self.plot_window.after(50, lambda: self._create_charts(current_nets, loading_label)) def _create_charts(self, nets, loading_label): """创建图表并移除加载提示""" # 移除加载提示 loading_label.destroy() # 创建图表 for net in nets: self.create_net_chart(net) self.loaded_count += len(nets) self.loading = False # 更新状态 self.status_label.config(text=f"已加载 {self.loaded_count} 个NET,共 {len(self.net_list)} 个") # 检查是否全部加载完成 if self.loaded_count >= len(self.net_list): self.status_label.config(text=f"全部加载完成,共 {len(self.net_list)} 个NET", fg="green") def create_net_chart(self, net): """为单个NET创建图表""" net_data = self.net_data_dict.get(net, pd.DataFrame()) if net_data.empty: return # 获取当前内容框架中的行数 row_idx = self.content_frame.grid_size()[1] # 创建图表框架 frame = tk.Frame(self.content_frame, bd=2, relief=tk.GROOVE, padx=10, pady=10) frame.grid(row=row_idx, column=0, sticky="nsew", padx=5, pady=5) # 添加NET标签 tk.Label(frame, text=f"NET: {net}", font=("SimHei", 12, "bold")).pack(anchor=tk.W) # 创建图表 fig = Figure(figsize=(10, 4), dpi=100) ax = fig.add_subplot(111) # 准备数据 long_data = net_data.melt(var_name="Lot", value_name="测量值") long_data["批次序号"] = long_data["Lot"].str.extract(r"(\d+)").astype(int) long_data = long_data.sort_values("批次序号") # 绘制箱线图 sns.boxplot( x="Lot", y="测量值", data=long_data, ax=ax, boxprops=dict(facecolor="lightblue", alpha=0.7), medianprops=dict(color="red", linewidth=2), ) # 设置图表样式 ax.set_title(f"{net} 各批次数据分布", fontsize=12) ax.set_xlabel("Lot", fontsize=10) ax.set_ylabel("测量值", fontsize=10) ax.tick_params(axis="x", labelsize=8, rotation=30) ax.grid(axis="y", linestyle="--", alpha=0.7) fig.tight_layout() # 标注统计值 all_values = long_data["测量值"].dropna() if not all_values.empty: data_range = all_values.max() - all_values.min() for i, col in enumerate(net_data.columns): col_values = net_data[col].dropna() if col_values.empty: continue q1 = col_values.quantile(0.25) median = col_values.median() q3 = col_values.quantile(0.75) text_offset = data_range * 0.03 ax.text(i, q1 - text_offset, f"Q1: {q1:.2f}", ha="center", fontsize=8) ax.text( i, median, f"Med: {median:.2f}", ha="center", fontsize=8, color="red", ) ax.text(i, q3 + text_offset, f"Q3: {q3:.2f}", ha="center", fontsize=8) # 嵌入图表到Tkinter canvas_plot = FigureCanvasTkAgg(fig, master=frame) canvas_plot.draw() canvas_plot.get_tk_widget().pack(fill=tk.X) # 添加工具栏 toolbar = NavigationToolbar2Tk(canvas_plot, frame) toolbar.update() canvas_plot.get_tk_widget().pack(fill=tk.X) def update_display(self): """根据当前视图模式更新显示""" # 清除当前内容 for widget in self.content_frame.winfo_children(): widget.destroy() # 重置无限滚动计数器 self.loaded_count = 0 if self.view_mode == "pagination": # 分页模式 page_idx = self.current_page start_idx = page_idx * self.per_page end_idx = min((page_idx + 1) * self.per_page, len(self.net_list)) current_nets = self.net_list[start_idx:end_idx] # 更新标题和页码标签 self.plot_window.title(f"批次数据分布箱线图 ({page_idx + 1}/{self.total_pages})") self.page_label.config(text=f"{page_idx + 1}/{self.total_pages}") # 更新按钮状态 self.prev_btn.config(state=tk.NORMAL if page_idx > 0 else tk.DISABLED) self.next_btn.config(state=tk.NORMAL if page_idx < self.total_pages - 1 else tk.DISABLED) # 创建当前页的图表 for net in current_nets: self.create_net_chart(net) # 更新状态 self.status_label.config(text=f"显示 {start_idx+1}-{end_idx} 个NET,共 {len(self.net_list)} 个") else: # 无限滚动模式 self.plot_window.title("批次数据分布箱线图 (无限滚动模式)") self.status_label.config(text=f"已加载 0 个NET,共 {len(self.net_list)} 个") # 初始加载第一组图表 self.load_more() def process_batches(): input_text = batch_entry.get().strip() if not input_text: messagebox.showwarning("输入警告", "请输入批次号后再处理!") return batch_list = re.split(r"[,\s]+", input_text) batch_list = [b for b in batch_list if b] if not batch_list: messagebox.showwarning("输入警告", "未识别到有效批次号!") return # 解析NET输入 net_text = net_entry.get().strip() net_list = [] if net_text: raw_nets = re.split(r"[,\s]+", net_text) raw_nets = [n.strip() for n in raw_nets if n.strip()] for net in raw_nets: if net.startswith("net") and net[3:].isdigit(): net_list.append(net) elif net.isdigit(): net_list.append(f"net{net}") else: print(f"警告:无效NET格式 '{net}',已忽略") net_list = list(dict.fromkeys(net_list)) net_list.sort(key=lambda x: int(x[3:])) print(f"已解析NET列表(排序后):{', '.join(net_list)}") else: # 默认显示前20个NET net_list = [f"net{i}" for i in range(1, 21)] print("未指定NET编号,将显示前20个NET") process_btn.config(state=tk.DISABLED) log_text.delete(1.0, tk.END) print(f"开始处理批次:{', '.join(batch_list)}\n") # 收集所有批次的数据 batch_data_dict = collect_batch_data(batch_list) # 如果没有找到任何批次数据 if not batch_data_dict: messagebox.showerror("错误", "未找到任何批次数据!") process_btn.config(state=tk.NORMAL) return # 如果用户没有指定NET,使用第一个批次的所有NET列 if not net_list: first_batch = next(iter(batch_data_dict.values())) net_list = [col for col in first_batch.columns if col.startswith("net")] net_list.sort(key=lambda x: int(x[3:])) print(f"自动获取NET列表: {', '.join(net_list[:20])} (显示前20个)") # 创建NET图表查看器 NetPlotViewer(root, batch_data_dict, batch_list, net_list) process_btn.config(state=tk.NORMAL) print("所有批次处理完成!") def create_gui(): global root, batch_entry, log_text, process_btn, net_entry root = tk.Tk() root.title("電測阻值箱線圖") root.geometry("800x650") root.resizable(True, True) # 批次号输入区域 input_frame = tk.Frame(root, padx=10, pady=10) input_frame.pack(fill=tk.X) tk.Label( input_frame, text="批次号输入(多个批次用逗号/空格分隔):", font=("SimHei", 10) ).pack(side=tk.LEFT) batch_entry = tk.Entry(input_frame, width=50, font=("SimHei", 10)) batch_entry.pack(side=tk.LEFT, padx=5) batch_entry.insert(0, "25AL90070800,25AL90070400") # NET编号输入 net_frame = tk.Frame(root, padx=10, pady=5) net_frame.pack(fill=tk.X) tk.Label( net_frame, text="NET编号输入(多个用逗号/空格分隔,留空显示全部):", font=("SimHei", 10), ).pack(side=tk.LEFT) net_entry = tk.Entry(net_frame, width=50, font=("SimHei", 10)) net_entry.pack(side=tk.LEFT, padx=5) net_entry.insert(0, "1,2,3,4,5") # 測試先默認 # 按钮 btn_frame = tk.Frame(root, padx=10, pady=5) btn_frame.pack(fill=tk.X) process_btn = tk.Button( btn_frame, text="开始处理", command=process_batches, bg="#4CAF50", fg="white", font=("SimHei", 10), ) process_btn.pack(side=tk.LEFT, padx=5) clear_btn = tk.Button( btn_frame, text="清空日志", command=lambda: log_text.delete(1.0, tk.END), bg="#f44336", fg="white", font=("SimHei", 10), ) clear_btn.pack(side=tk.LEFT, padx=5) # 日志 log_frame = tk.Frame(root, padx=10, pady=5) log_frame.pack(fill=tk.BOTH, expand=True) tk.Label(log_frame, text="处理日志:", font=("SimHei", 10)).pack(anchor=tk.W) log_text = scrolledtext.ScrolledText(log_frame, wrap=tk.WORD, font=("SimHei", 9)) log_text.pack(fill=tk.BOTH, expand=True) sys.stdout = RedirectText(log_text) root.mainloop() if __name__ == "__main__": create_gui() 我現在想要先把數據收集完成後,在進行輸入net,畫圖,這樣如果我要更換查看net,就不用再重新執行程式重新收集。只要這個界面不關閉,收集一次後,可以多次輸入net進行查看
11-28
import os import re import sys import tkinter as tk from tkinter import ttk, Frame, Scrollbar, Canvas from tkinter import scrolledtext, messagebox import pandas as pd import numpy as np from glob import glob import seaborn as sns from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk import matplotlib.pyplot as plt from matplotlib.figure import Figure import matplotlib as mpl import matplotlib.font_manager as fm from datetime import datetime def configure_chinese_font(): # 尝试寻找可用中文字体 chinese_fonts = [] chinese_fonts = [ "SimHei", "Microsoft YaHei", "SimSun", "KaiTi", "FangSong", "Arial Unicode MS", ] # 检查系统中有哪些字体可用 available_fonts = [f.name for f in fm.fontManager.ttflist] selected_font = None # 寻找第一个可用的中文字体 for font in chinese_fonts: if font in available_fonts: selected_font = font break # 如果找不到任何中文字体,使用默认字体并警告 if selected_font is None: print(f"警告: 系统中未找到以下中文: {', '.join(chinese_fonts)}") print("将使用系统默认字体,中文可能无法正确显示") selected_font = fm.FontProperties().get_name() # 设置matplotlib使用选定的字体 plt.rcParams["font.family"] = selected_font plt.rcParams["axes.unicode_minus"] = False print(f"已选择字体: {selected_font} 用于中文显示") def find_batch_folders(path, batch_number): target_dir = "4WCSVLog" batch_folders = [] for root, dirs, files in os.walk(path): path_components = os.path.normpath(root).split(os.sep) if target_dir in path_components and batch_number in dirs: full_path = os.path.join(root, batch_number) batch_folders.append(full_path) return batch_folders def get_csv_files_path1(folder): all_files = glob(os.path.join(folder, "*-SVP1B*.csv")) return [f for f in all_files if "HEAD" not in f] def get_csv_files_path2(folder): return glob(os.path.join(folder, "*-SVP1B*.csv")) def parse_svp1b(filename): match = re.search(r"svp1b([0-9A-Z])([0-9])([0-9])", filename, re.IGNORECASE) if match: piece, line, file_num = match.groups() piece_num = ( 10 + ord(piece.upper()) - ord("A") if piece.isalpha() else int(piece) ) return piece_num, int(line), int(file_num) return None def read_csv_file(file, start_row=0, start_col=0): df = pd.read_csv(file, header=None, skiprows=start_row) return df.iloc[:, start_col:] if start_col > 0 else df def merge_files(files, path_type): piece_dict = {} for file in files: svp_info = parse_svp1b(os.path.basename(file).lower()) if not svp_info: continue piece, line, file_num = svp_info key = (piece, line) df = read_csv_file( file, start_row=(2 if path_type == 1 else 4), start_col=(0 if path_type == 1 else 18), ) if df.empty: continue if key not in piece_dict: piece_dict[key] = [] piece_dict[key].append((file_num, df)) sorted_keys = sorted(piece_dict.keys(), key=lambda x: (x[0], x[1])) global_col_counter = 1 final_dfs = [] for key in sorted_keys: file_dfs = sorted(piece_dict[key], key=lambda x: x[0]) base_df = None for file_num, df in file_dfs: if base_df is None: global_col_counter = 1 new_columns = [] for _ in df.columns: new_columns.append(f"net{global_col_counter}") global_col_counter += 1 df.columns = new_columns base_df = df else: new_columns = [] for _ in df.columns: new_columns.append(f"net{global_col_counter}") global_col_counter += 1 df.columns = new_columns base_df = pd.concat([base_df, df], axis=1) # 横向拼接 if base_df is not None: final_dfs.append(base_df) return ( pd.concat(final_dfs, axis=0, ignore_index=True) if final_dfs else pd.DataFrame() ) def collect_batch_data(batch_list): batch_data_dict = {} path1 = r"\\10.127.1.248\c1tst\7750" path2 = r"\\10.127.1.248\c1tst\7755" for idx, batch_number in enumerate(batch_list, 1): print(f"=== 处理第 {idx}/{len(batch_list)} 个批次:{batch_number} ===") batch_data = pd.DataFrame() folders1 = find_batch_folders(path1, batch_number) if folders1: for folder1 in folders1: files = get_csv_files_path1(folder1) if files: current_data = merge_files(files, path_type=1) batch_data = pd.concat( [batch_data, current_data], axis=0, ignore_index=True ) print(f"从{folder1}找到 {len(files)} 个数据文件") else: folders2 = find_batch_folders(path2, batch_number) if folders2: for folder2 in folders2: files = get_csv_files_path2(folder2) if files: current_data = merge_files(files, path_type=2) batch_data = pd.concat( [batch_data, current_data], axis=0, ignore_index=True ) print(f"从{folder2}找到 {len(files)} 个数据文件") else: print(f"批次 {batch_number}:未找到对应文件夹,跳过\n") continue if not batch_data.empty: batch_data_dict[batch_number] = batch_data print(f"批次 {batch_number} 数据合并完成,共 {len(batch_data)} 行\n") else: print(f"批次 {batch_number}:无有效数据,跳过\n") return batch_data_dict class RedirectText: def __init__(self, text_widget): self.text_widget = text_widget def write(self, string): self.text_widget.insert(tk.END, string) self.text_widget.see(tk.END) def flush(self): pass class NetPlotViewer: """高级NET图表查看器,支持分页和无限滚动""" def __init__(self, root, batch_data_dict, batch_list, net_list): self.root = root self.batch_data_dict = batch_data_dict self.batch_list = batch_list self.net_list = net_list # 预先计算并缓存所有NET的数据 self.net_data_dict = self._precompute_net_data() # 记录无效的NET self.invalid_nets = [net for net in net_list if net not in self.net_data_dict] # 打印无效NET警告 if self.invalid_nets: print(f"警告: 以下NET在批次数据中不存在: {', '.join(self.invalid_nets)}") # 只保留有效的NET self.valid_nets = [net for net in net_list if net in self.net_data_dict] # 分页配置 self.per_page = 10 self.current_page = 0 self.total_pages = max( 1, (len(self.valid_nets) + self.per_page - 1) // self.per_page ) # 无限滚动配置 self.loaded_count = 0 self.load_step = 10 self.loading = False self.view_mode = "pagination" # 或 "infinite_scroll" # 创建主窗口 self.create_main_window() # 居中显示窗口 self.center_window() def center_window(self): """使窗口在屏幕上居中显示""" self.plot_window.update_idletasks() # 确保窗口尺寸已更新 width = self.plot_window.winfo_width() height = self.plot_window.winfo_height() screen_width = self.plot_window.winfo_screenwidth() screen_height = self.plot_window.winfo_screenheight() x = (screen_width - width) // 2 y = (screen_height - height) // 2 # 设置窗口位置居中 self.plot_window.geometry(f"+{x}+{y}") def _precompute_net_data(self): """预先计算并缓存所有NET的数据""" net_data_dict = {} for net in self.net_list: net_data = pd.DataFrame() for batch in self.batch_list: if ( batch in self.batch_data_dict and net in self.batch_data_dict[batch].columns ): net_data[f"{batch}"] = self.batch_data_dict[batch][net] # 只添加有数据的NET if not net_data.empty: net_data_dict[net] = net_data return net_data_dict def create_main_window(self): """创建主窗口和控件""" self.plot_window = tk.Toplevel(self.root) self.plot_window.title("批次数据分布箱线图") self.plot_window.geometry("1200x800") # 添加无效NET警告 if self.invalid_nets: warning_frame = tk.Frame( self.plot_window, bg="#ffebee", bd=1, relief=tk.SOLID ) warning_frame.pack(fill=tk.X, padx=10, pady=5) warning_label = tk.Label( warning_frame, text=f"警告: 以下NET在批次数据中不存在: {', '.join(self.invalid_nets)}", fg="#d32f2f", bg="#ffebee", font=("SimHei", 10, "bold"), ) warning_label.pack(padx=10, pady=5) # 添加有效NET数量信息 # info_frame = tk.Frame(self.plot_window, bg="#e3f2fd", bd=1, relief=tk.SOLID) # info_frame.pack(fill=tk.X, padx=10, pady=5) # info_label = tk.Label( # info_frame, # text=f"显示 {len(self.valid_nets)} 个有效的NET中的图表", # fg="#1565c0", # bg="#e3f2fd", # font=("SimHei", 10), # ) # info_label.pack(padx=10, pady=5) # 创建顶部控制面板 self.create_control_panel() # 创建内容区域 self.create_content_area() # 初始显示 self.update_display() self.plot_window.update_idletasks() self.center_window() def create_control_panel(self): """创建顶部控制面板""" control_frame = tk.Frame( self.plot_window, bd=1, relief=tk.RIDGE, padx=10, pady=10 ) control_frame.pack(fill=tk.X, padx=10, pady=(10, 5)) # 视图模式选择 tk.Label(control_frame, text="视图模式:", font=("SimHei", 10)).pack( side=tk.LEFT, padx=5 ) self.view_mode_var = tk.StringVar( value="分页视图" if self.view_mode == "pagination" else "无限滚动" ) view_mode_menu = ttk.Combobox( control_frame, textvariable=self.view_mode_var, values=["分页视图", "无限滚动"], width=12, state="readonly", ) view_mode_menu.pack(side=tk.LEFT, padx=5) view_mode_menu.bind("<<ComboboxSelected>>", self.change_view_mode) # 分页控制 self.pagination_frame = tk.Frame(control_frame) self.pagination_frame.pack(side=tk.LEFT, padx=20) # 分页控件 self.prev_btn = tk.Button( self.pagination_frame, text="上一页", command=lambda: self.change_page(-1), state=tk.DISABLED, ) self.prev_btn.pack(side=tk.LEFT, padx=5) self.page_label = tk.Label( self.pagination_frame, text="1/1", font=("SimHei", 10) ) self.page_label.pack(side=tk.LEFT, padx=5) self.next_btn = tk.Button( self.pagination_frame, text="下一页", command=lambda: self.change_page(1), state=tk.NORMAL if self.total_pages > 1 else tk.DISABLED, ) self.next_btn.pack(side=tk.LEFT, padx=5) # 跳转控件 tk.Label(self.pagination_frame, text="跳转到:", font=("SimHei", 9)).pack( side=tk.LEFT, padx=(20, 0) ) self.page_entry = tk.Entry(self.pagination_frame, width=4, font=("SimHei", 9)) self.page_entry.pack(side=tk.LEFT, padx=2) self.page_entry.insert(0, "1") self.goto_btn = tk.Button( self.pagination_frame, text="跳转", command=self.goto_page, font=("SimHei", 9), ) self.goto_btn.pack(side=tk.LEFT, padx=2) # 每页数量选择 tk.Label(self.pagination_frame, text="每页数量:", font=("SimHei", 9)).pack( side=tk.LEFT, padx=(20, 0) ) self.per_page_var = tk.StringVar(value=str(self.per_page)) self.per_page_dropdown = ttk.Combobox( self.pagination_frame, textvariable=self.per_page_var, values=["1", "5", "10", "20", "50"], width=4, state="readonly", ) self.per_page_dropdown.pack(side=tk.LEFT, padx=2) self.per_page_dropdown.bind("<<ComboboxSelected>>", self.change_per_page) # 关闭按钮 close_btn = tk.Button( control_frame, text="关闭窗口", command=self.plot_window.destroy, bg="#f44336", fg="white", font=("SimHei", 10), ) close_btn.pack(side=tk.RIGHT, padx=10) # 状态标签 self.status_label = tk.Label( control_frame, text="", font=("SimHei", 9), fg="#666" ) self.status_label.pack(side=tk.RIGHT, padx=10) def create_content_area(self): """创建内容显示区域""" # 创建滚动框架 self.scroll_frame = tk.Frame(self.plot_window) self.scroll_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10)) # 添加水平和垂直滚动条 self.hscrollbar = tk.Scrollbar(self.scroll_frame, orient=tk.HORIZONTAL) self.vscrollbar = tk.Scrollbar(self.scroll_frame, orient=tk.VERTICAL) # 创建画布用于滚动 self.canvas = tk.Canvas( self.scroll_frame, yscrollcommand=self.vscrollbar.set, xscrollcommand=self.hscrollbar.set, ) # 配置滚动条 self.vscrollbar.config(command=self.canvas.yview) self.hscrollbar.config(command=self.canvas.xview) # 布局 self.hscrollbar.pack(side=tk.BOTTOM, fill=tk.X) self.vscrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) # 创建内容框架 self.content_frame = tk.Frame(self.canvas) self.canvas_frame = self.canvas.create_window( (0, 0), window=self.content_frame, anchor="nw" ) # 绑定配置事件 self.content_frame.bind("<Configure>", self.on_frame_configure) self.canvas.bind("<Configure>", self.on_canvas_configure) # 绑定鼠标滚轮事件 self.canvas.bind_all("<MouseWheel>", self.on_mousewheel) def on_frame_configure(self, event=None): """更新滚动区域""" self.canvas.configure(scrollregion=self.canvas.bbox("all")) # 检查是否需要加载更多(无限滚动模式) if self.view_mode == "infinite_scroll": self.check_load_more() # 动态调整列宽 self.content_frame.update_idletasks() self.canvas.itemconfig( self.canvas_frame, width=self.content_frame.winfo_width() ) def on_canvas_configure(self, event): """调整内容框架宽度以适应画布""" self.canvas.itemconfig(self.canvas_frame, width=event.width) def on_mousewheel(self, event): """处理鼠标滚轮事件""" self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units") # 检查是否需要加载更多(无限滚动模式) if self.view_mode == "infinite_scroll": self.check_load_more() def change_view_mode(self, event=None): """切换视图模式""" new_mode = ( "pagination" if self.view_mode_var.get() == "分页视图" else "infinite_scroll" ) if new_mode != self.view_mode: self.view_mode = new_mode self.update_display() # 更新分页控件的可见性 if self.view_mode == "pagination": self.pagination_frame.pack(side=tk.LEFT, padx=20) else: self.pagination_frame.pack_forget() def change_per_page(self, event=None): """更改每页显示数量""" try: new_per_page = int(self.per_page_var.get()) if new_per_page != self.per_page: self.per_page = new_per_page self.total_pages = max( 1, (len(self.valid_nets) + self.per_page - 1) // self.per_page ) self.current_page = 0 self.update_display() except ValueError: pass def change_page(self, delta): """翻页""" new_page = max(0, min(self.total_pages - 1, self.current_page + delta)) if new_page != self.current_page: self.current_page = new_page self.update_display() def goto_page(self): """跳转到指定页码""" try: page_num = int(self.page_entry.get()) - 1 if 0 <= page_num < self.total_pages: self.current_page = page_num self.update_display() else: messagebox.showerror("错误", f"页码必须在 1 到 {self.total_pages} 之间") except ValueError: messagebox.showerror("错误", "请输入有效的页码数字") def check_load_more(self): """检查是否需要加载更多(无限滚动模式)""" if self.view_mode != "infinite_scroll" or self.loading: return # 获取滚动位置 scroll_position = self.canvas.yview() # 如果滚动到底部附近(90%位置),加载更多 if scroll_position[1] > 0.9 and self.loaded_count < len(self.valid_nets): self.load_more() def load_more(self): """加载更多图表(无限滚动模式)""" if self.loading or self.loaded_count >= len(self.valid_nets): return self.loading = True # 显示加载状态 self.status_label.config( text=f"正在加载数据... ({self.loaded_count}/{len(self.valid_nets)})" ) self.plot_window.update() start_idx = self.loaded_count end_idx = min(self.loaded_count + self.load_step, len(self.valid_nets)) current_nets = self.valid_nets[start_idx:end_idx] # 创建加载动画 loading_label = tk.Label( self.content_frame, text="加载中...", font=("SimHei", 10, "italic"), fg="#666", ) loading_label.grid( row=self.content_frame.grid_size()[1], column=0, sticky="ew", pady=10 ) # 短暂延迟,让用户看到加载提示 self.plot_window.after( 50, lambda: self._create_charts(current_nets, loading_label) ) def _create_charts(self, nets, loading_label): """创建图表并移除加载提示""" # 移除加载提示 loading_label.destroy() # 创建图表 for net in nets: self.create_net_chart(net) self.loaded_count += len(nets) self.loading = False # 更新状态 self.status_label.config( text=f"已加载 {self.loaded_count} 个NET,共 {len(self.valid_nets)} 个" ) # 检查是否全部加载完成 if self.loaded_count >= len(self.valid_nets): self.status_label.config( text=f"全部加载完成,共 {len(self.valid_nets)} 个NET", fg="green" ) def create_net_chart(self, net): """为单个NET创建图表""" net_data = self.net_data_dict.get(net, pd.DataFrame()) if net_data.empty: return # 获取当前内容框架中的行数 row_idx = self.content_frame.grid_size()[1] # 创建图表框架 frame = tk.Frame(self.content_frame, bd=2, relief=tk.GROOVE, padx=10, pady=10) frame.grid(row=row_idx, column=0, sticky="nsew", padx=5, pady=5) # # 添加NET标签 # net_label = tk.Label(frame, text=f" {net}", font=("SimHei", 12, "bold")) # net_label.pack(anchor=tk.W) # # 添加批次信息标签 # batch_info = ", ".join(net_data.columns) # batch_label = tk.Label( # frame, text=f"{batch_info}", font=("SimHei", 9), fg="#666" # ) # batch_label.pack(anchor=tk.W) # 创建图表 fig = Figure(figsize=(10, 4), dpi=100) ax = fig.add_subplot(111) # 准备数据 long_data = net_data.melt(var_name="批號", value_name="测量值") long_data["批次序号"] = long_data["批號"].str.extract(r"(\d+)").astype(int) long_data = long_data.sort_values("批次序号") # 绘制箱线图 sns.boxplot( x="批號", y="测量值", data=long_data, ax=ax, boxprops=dict(facecolor="lightblue", alpha=0.7), medianprops=dict(color="red", linewidth=2), ) # 设置图表样式 ax.set_title(f"{net} ", fontsize=12) ax.set_xlabel("", fontsize=10) ax.set_ylabel("NET值", fontsize=10) ax.tick_params(axis="x", labelsize=8, rotation=30) ax.grid(axis="y", linestyle="--", alpha=0.7) fig.tight_layout() # 标注统计值 all_values = long_data["测量值"].dropna() if not all_values.empty: data_range = all_values.max() - all_values.min() for i, col in enumerate(net_data.columns): col_values = net_data[col].dropna() if col_values.empty: continue q1 = col_values.quantile(0.25) median = col_values.median() q3 = col_values.quantile(0.75) text_offset = data_range * 0.03 ax.text(i, q1 - text_offset, f"Q1: {q1:.2f}", ha="center", fontsize=8) ax.text( i, median, f"Med: {median:.2f}", ha="center", fontsize=8, color="red", ) ax.text(i, q3 + text_offset, f"Q3: {q3:.2f}", ha="center", fontsize=8) # 嵌入图表到Tkinter canvas_plot = FigureCanvasTkAgg(fig, master=frame) canvas_plot.draw() canvas_plot.get_tk_widget().pack(fill=tk.X) # 添加工具栏 toolbar = NavigationToolbar2Tk(canvas_plot, frame) toolbar.update() canvas_plot.get_tk_widget().pack(fill=tk.X) def update_display(self): """根据当前视图模式更新显示""" # 清除当前内容 for widget in self.content_frame.winfo_children(): widget.destroy() # 重置无限滚动计数器 self.loaded_count = 0 if self.view_mode == "pagination": # 分页模式 page_idx = self.current_page start_idx = page_idx * self.per_page end_idx = min((page_idx + 1) * self.per_page, len(self.valid_nets)) current_nets = self.valid_nets[start_idx:end_idx] # 更新标题和页码标签 self.plot_window.title( f"批次数据分布箱线图 ({page_idx + 1}/{self.total_pages})" ) self.page_label.config(text=f"{page_idx + 1}/{self.total_pages}") # 更新按钮状态 self.prev_btn.config(state=tk.NORMAL if page_idx > 0 else tk.DISABLED) self.next_btn.config( state=tk.NORMAL if page_idx < self.total_pages - 1 else tk.DISABLED ) # 创建当前页的图表 for net in current_nets: self.create_net_chart(net) # 更新状态 self.status_label.config( text=f"显示 {start_idx+1}-{end_idx} 个NET,共 {len(self.valid_nets)} 个" ) # 居中窗口 self.center_window() else: # 无限滚动模式 self.plot_window.title("批次数据分布箱线图 (无限滚动模式)") self.status_label.config( text=f"已加载 0 个NET,共 {len(self.valid_nets)} 个" ) # 初始加载第一组图表 self.load_more() # 居中窗口 self.center_window() class BatchDataManager: """批次数据管理器,负责数据收集和缓存""" def __init__(self): self.batch_data_dict = {} self.collected_batches = set() self.current_batch_list = [] def collect_data(self, batch_list): """收集批次数据并缓存""" # 检查哪些批次尚未收集 new_batches = [b for b in batch_list if b not in self.collected_batches] if new_batches: # 只收集新增批次的数据 new_data = collect_batch_data(new_batches) self.batch_data_dict.update(new_data) self.collected_batches.update(new_batches) print(f"已收集 {len(new_batches)} 个新批次的数据") # 更新当前批次列表 self.current_batch_list = batch_list # 打印所有NET信息 self.print_net_info() return self.batch_data_dict def get_net_names(self): """获取当前批次的所有NET名称""" net_names = set() for batch in self.current_batch_list: if batch in self.batch_data_dict: net_names.update(self.batch_data_dict[batch].columns) # 转换为列表并按数字排序 net_list = sorted( net_names, key=lambda x: int(x[3:]) if x.startswith("net") and x[3:].isdigit() else 0, ) return net_list def print_net_info(self): """打印所有批次中的NET信息""" print("\n===== 批次中的NET信息 =====") total_nets = set() # 用于统计所有批次中的唯一NET for batch in self.current_batch_list: if batch in self.batch_data_dict: df = self.batch_data_dict[batch] net_columns = [col for col in df.columns if col.startswith("net")] # 添加NET到总集合 total_nets.update(net_columns) print( f"批次 {batch} 包含 {len(net_columns)} 个NET: {', '.join(net_columns[:10])}{'...' if len(net_columns) > 10 else ''}" ) else: print(f"批次 {batch} 无可用数据") # 打印总NET数量 print(f"批次总共包含 {len(total_nets)} 个不同的NET") print("=========================\n") def get_available_nets(self, batch_list): """获取特定批次中可用的NET""" available_nets = set() for batch in batch_list: if batch in self.batch_data_dict: available_nets.update(self.batch_data_dict[batch].columns) return [net for net in available_nets if net.startswith("net")] def process_batches(): """处理批次数据""" input_text = batch_entry.get().strip() if not input_text: messagebox.showwarning("输入警告", "请输入批次号后再处理!") return batch_list = re.split(r"[,\s]+", input_text) batch_list = [b for b in batch_list if b] if not batch_list: messagebox.showwarning("输入警告", "未识别到有效批次号!") return process_btn.config(state=tk.DISABLED) log_text.delete(1.0, tk.END) print(f"开始处理批次:{', '.join(batch_list)} - {datetime.now()}\n") # 收集批次数据 data_manager.collect_data(batch_list) # 获取所有NET名称 all_net_names = data_manager.get_net_names() net_entry.delete(0, tk.END) if all_net_names: net_entry.insert(0, ", ".join(all_net_names[:5])) # 默认显示前5个NET print(f"批次中包含的NET总数: {len(all_net_names)}") else: print("警告: 批次中未找到任何NET数据") # 启用显示图表按钮 show_plot_btn.config(state=tk.NORMAL) process_btn.config(state=tk.NORMAL) print(f"批次数据收集完成!现在可以输入NET列表并点击'显示图表'按钮 {datetime.now()}") def show_plots(): """显示NET图表""" # 解析NET输入 net_text = net_entry.get().strip() net_list = [] if net_text: raw_nets = re.split(r"[,\s]+", net_text) raw_nets = [n.strip() for n in raw_nets if n.strip()] for net in raw_nets: if net.startswith("net") and net[3:].isdigit(): net_list.append(net) elif net.isdigit(): net_list.append(f"net{net}") else: print(f"警告:无效NET格式 '{net}',已忽略") net_list = list(dict.fromkeys(net_list)) net_list.sort(key=lambda x: int(x[3:])) print(f"已解析NET列表(排序后):{', '.join(net_list)}") else: # 默认显示前20个NET net_list = [f"net{i}" for i in range(1, 21)] print("未指定NET编号,将显示前20个NET") if not net_list: messagebox.showwarning("输入警告", "未识别到有效的NET编号!") return # 检查NET是否在批次中 available_nets = data_manager.get_available_nets(data_manager.current_batch_list) invalid_nets = [net for net in net_list if net not in available_nets] if invalid_nets: print(f"警告: 以下NET在批次数据中不存在: {', '.join(invalid_nets)}") # 只保留有效的NET valid_nets = [net for net in net_list if net in available_nets] if not valid_nets: messagebox.showwarning("输入警告", "没有有效的NET可显示!") return print(f"将显示 {len(valid_nets)} 个有效的NET") # 创建NET图表查看器 NetPlotViewer( root, data_manager.batch_data_dict, data_manager.current_batch_list, valid_nets ) def create_gui(): global root, batch_entry, log_text, process_btn, net_entry, show_plot_btn, data_manager # 初始化数据管理器 data_manager = BatchDataManager() root = tk.Tk() root.title("電測阻值箱線圖") root.geometry("800x650") root.resizable(True, True) # 批次号输入区域 input_frame = tk.Frame(root, padx=10, pady=10) input_frame.pack(fill=tk.X) tk.Label( input_frame, text="批次号输入(多个批次用逗号/空格分隔):", font=("SimHei", 10) ).pack(side=tk.LEFT) batch_entry = tk.Entry(input_frame, width=50, font=("SimHei", 10)) batch_entry.pack(side=tk.LEFT, padx=5) batch_entry.insert(0, "25AL90070800,25AL90070400") # NET编号输入 net_frame = tk.Frame(root, padx=10, pady=5) net_frame.pack(fill=tk.X) tk.Label( net_frame, text="NET编号输入(多个用逗号/空格分隔):", font=("SimHei", 10), ).pack(side=tk.LEFT) net_entry = tk.Entry(net_frame, width=50, font=("SimHei", 10)) net_entry.pack(side=tk.LEFT, padx=5) net_entry.insert(0, "1,2,3,4,5") # 按钮 btn_frame = tk.Frame(root, padx=10, pady=5) btn_frame.pack(fill=tk.X) process_btn = tk.Button( btn_frame, text="收集批次数据", command=process_batches, bg="#4CAF50", fg="white", font=("SimHei", 10), ) process_btn.pack(side=tk.LEFT, padx=5) show_plot_btn = tk.Button( btn_frame, text="显示图表", command=show_plots, bg="#2196F3", fg="white", font=("SimHei", 10), state=tk.DISABLED, # 初始禁用 ) show_plot_btn.pack(side=tk.LEFT, padx=5) clear_btn = tk.Button( btn_frame, text="清空日志", command=lambda: log_text.delete(1.0, tk.END), bg="#f44336", fg="white", font=("SimHei", 10), ) clear_btn.pack(side=tk.LEFT, padx=5) # 日志 log_frame = tk.Frame(root, padx=10, pady=5) log_frame.pack(fill=tk.BOTH, expand=True) tk.Label(log_frame, text="处理日志:", font=("SimHei", 10)).pack(anchor=tk.W) log_text = scrolledtext.ScrolledText(log_frame, wrap=tk.WORD, font=("SimHei", 9)) log_text.pack(fill=tk.BOTH, expand=True) sys.stdout = RedirectText(log_text) root.mainloop() if __name__ == "__main__": configure_chinese_font() create_gui() 我想要將net圖標居中,但是現在程式寫了居中,可以沒有起作用。將這個功能重新改寫一下。此外,我現在想要將每一個net的圖都可以水平拉動。
最新发布
11-29
import os import re import sys import tkinter as tk from tkinter import ttk, Frame, Scrollbar, Canvas from tkinter import scrolledtext, messagebox import pandas as pd import numpy as np from glob import glob import seaborn as sns from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk import matplotlib.ticker as ticker import matplotlib.pyplot as plt from matplotlib.font_manager import FontProperties from matplotlib.figure import Figure plt.switch_backend("TkAgg") plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"] plt.rcParams["axes.unicode_minus"] = False def find_batch_folders(path, batch_number): target_dir = "4WCSVLog" # 需筛选的上级目录名 batch_folders = [] for root, dirs, files in os.walk(path): path_components = os.path.normpath(root).split(os.sep) if target_dir in path_components and batch_number in dirs: full_path = os.path.join(root, batch_number) batch_folders.append(full_path) return batch_folders def get_csv_files_path1(folder): all_files = glob(os.path.join(folder, "*-SVP1B*.csv")) files = [f for f in all_files if "HEAD" not in f] return files def get_csv_files_path2(folder): all_files = glob(os.path.join(folder, "*-SVP1B*.csv")) files = all_files return files def parse_svp1b(filename): match = re.search(r"svp1b([0-9A-Z])([0-9])([0-9])", filename, re.IGNORECASE) if match: piece, line, file_num = match.groups() if piece.isalpha(): piece_num = 10 + ord(piece.upper()) - ord("A") else: piece_num = int(piece) return piece_num, int(line), int(file_num) return None def read_csv_file(file, start_row=0, start_col=0): df = pd.read_csv(file, header=None, skiprows=start_row) return df.iloc[:, start_col:] if start_col > 0 else df def merge_files(files, path_type): piece_dict = {} for file in files: svp_info = parse_svp1b(os.path.basename(file).lower()) if not svp_info: continue piece, line, file_num = svp_info if path_type == 1: df = read_csv_file(file, start_row=2) else: df = read_csv_file(file, start_row=4, start_col=18) if df.empty: continue key = (piece, line) if key not in piece_dict: piece_dict[key] = [] piece_dict[key].append((file_num, df)) sorted_keys = sorted(piece_dict.keys(), key=lambda x: (x[0], x[1])) global_col_counter = 1 final_dfs = [] for key in sorted_keys: file_dfs = piece_dict[key] file_dfs.sort(key=lambda x: x[0]) base_df = None for file_num, df in file_dfs: if base_df is None: global_col_counter = 1 new_columns = [] for _ in df.columns: new_columns.append(f"net{global_col_counter}") global_col_counter += 1 df.columns = new_columns base_df = df else: new_columns = [] for _ in df.columns: new_columns.append(f"net{global_col_counter}") global_col_counter += 1 df.columns = new_columns base_df = pd.concat([base_df, df], axis=1) # 横向拼接 if base_df is not None: final_dfs.append(base_df) return ( pd.concat(final_dfs, axis=0, ignore_index=True) if final_dfs else pd.DataFrame() ) def collect_batch_data(batch_list): batch_data_dict = {} path1 = r"\\10.127.1.248\c1tst\7750" path2 = r"\\10.127.1.248\c1tst\7755" for idx, batch_number in enumerate(batch_list, 1): print(f"=== 处理第 {idx}/{len(batch_list)} 个批次:{batch_number} ===") batch_data = pd.DataFrame() folders1 = find_batch_folders(path1, batch_number) if folders1: for folder1 in folders1: files = get_csv_files_path1(folder1) current_data = ( merge_files(files, path_type=1) if files else pd.DataFrame() ) batch_data = pd.concat( [batch_data, current_data], axis=0, ignore_index=True ) print(f"从路径1找到 {len(files)} 个数据文件") else: folders2 = find_batch_folders(path2, batch_number) if folders2: for folder2 in folders2: files = get_csv_files_path2(folder2) current_data = ( merge_files(files, path_type=2) if files else pd.DataFrame() ) batch_data = pd.concat( [batch_data, current_data], axis=0, ignore_index=True ) print(f"从路径2找到 {len(files)} 个数据文件") else: print(f"批次 {batch_number}:未找到对应文件夹,跳过\n") continue if not batch_data.empty: batch_data_dict[batch_number] = batch_data print(f"批次 {batch_number} 数据合并完成,共 {len(batch_data)} 行\n") else: print(f"批次 {batch_number}:无有效数据,跳过\n") return batch_data_dict class RedirectText: def __init__(self, text_widget): self.text_widget = text_widget def write(self, string): self.text_widget.insert(tk.END, string) self.text_widget.see(tk.END) def flush(self): pass def plot_all_nets(batch_data_dict, batch_list, net_list): """在一个窗口中显示所有NET的图表,支持滚动""" if not batch_data_dict: print("无有效批次数据,无法绘图") return # 创建主图表窗口 plot_window = tk.Toplevel(root) plot_window.title(f"批次数据分布箱线图") plot_window.geometry("1200x800") # 创建滚动框架 scroll_frame = Frame(plot_window) scroll_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 添加垂直滚动条 vscrollbar = Scrollbar(scroll_frame, orient=tk.VERTICAL) vscrollbar.pack(side=tk.RIGHT, fill=tk.Y) # 创建画布用于滚动 canvas = Canvas(scroll_frame, yscrollcommand=vscrollbar.set) canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) vscrollbar.config(command=canvas.yview) # 创建内容框架(放在画布上) content_frame = Frame(canvas) canvas_frame = canvas.create_window((0, 0), window=content_frame, anchor="nw") # 绑定配置事件以更新滚动区域 def on_frame_configure(event): canvas.configure(scrollregion=canvas.bbox("all")) content_frame.bind("<Configure>", on_frame_configure) # 绑定鼠标滚轮事件 def on_mousewheel(event): canvas.yview_scroll(int(-1 * (event.delta / 120)), "units") canvas.bind_all("<MouseWheel>", on_mousewheel) # 创建所有NET的图表 row_count = 0 for net in net_list: # 收集当前NET在所有批次的数值 net_data = pd.DataFrame() for batch in batch_list: if batch in batch_data_dict and net in batch_data_dict[batch].columns: net_data[f" {batch}"] = batch_data_dict[batch][net] if net_data.empty: continue # 创建图表框架 frame = Frame(content_frame, bd=2, relief=tk.GROOVE, padx=10, pady=10) frame.grid(row=row_count, column=0, sticky="nsew", padx=5, pady=5) row_count += 1 # 添加NET标签 tk.Label(frame, text=f"NET: {net}", font=("SimHei", 12, "bold")).pack( anchor=tk.W ) # 创建图表 fig = Figure(figsize=(10, 4), dpi=100) ax = fig.add_subplot(111) # 准备数据 long_data = net_data.melt(var_name="Lot", value_name="测量值") long_data["批次序号"] = long_data["Lot"].str.extract(r"(\d+)").astype(int) long_data = long_data.sort_values("批次序号") # 绘制箱线图 sns.boxplot( x="Lot", y="测量值", data=long_data, ax=ax, boxprops=dict(facecolor="lightblue", alpha=0.7), medianprops=dict(color="red", linewidth=2), ) # 设置图表样式 ax.set_title(f"{net} ", fontsize=12) ax.set_xlabel("", fontsize=10) ax.set_ylabel("Value", fontsize=10) ax.tick_params(axis="x", labelsize=8, rotation=30) ax.grid(axis="y", linestyle="--", alpha=0.7) fig.tight_layout() # 标注统计值 all_values = long_data["测量值"].dropna() if not all_values.empty: data_range = all_values.max() - all_values.min() for i, col in enumerate(net_data.columns): col_values = net_data[col].dropna() if col_values.empty: continue q1 = col_values.quantile(0.25) median = col_values.median() q3 = col_values.quantile(0.75) text_offset = data_range * 0.03 ax.text(i, q1 - text_offset, f"Q1: {q1:.2f}", ha="center", fontsize=8) ax.text( i, median, f"Med: {median:.2f}", ha="center", fontsize=8, color="red", ) ax.text(i, q3 + text_offset, f"Q3: {q3:.2f}", ha="center", fontsize=8) # 嵌入图表到Tkinter canvas_plot = FigureCanvasTkAgg(fig, master=frame) canvas_plot.draw() canvas_plot.get_tk_widget().pack(fill=tk.X) # 添加工具栏(提供缩放等功能) toolbar = NavigationToolbar2Tk(canvas_plot, frame) toolbar.update() canvas_plot.get_tk_widget().pack(fill=tk.X) # 添加分隔线 ttk.Separator(content_frame, orient=tk.HORIZONTAL).grid( row=row_count, column=0, sticky="ew", pady=10 ) row_count += 1 # 如果没有有效图表 if row_count == 0: tk.Label(content_frame, text="没有找到有效的NET数据", font=("SimHei", 12)).grid( row=0, column=0 ) print("没有找到有效的NET数据") # 添加关闭按钮 close_btn = tk.Button( plot_window, text="关闭窗口", command=plot_window.destroy, bg="#f44336", fg="white", font=("SimHei", 10), ) close_btn.pack(pady=10) def process_batches(): input_text = batch_entry.get().strip() if not input_text: messagebox.showwarning("输入警告", "请输入批次号后再处理!") return batch_list = re.split(r"[,\s]+", input_text) batch_list = [b for b in batch_list if b] if not batch_list: messagebox.showwarning("输入警告", "未识别到有效批次号!") return # 解析NET输入 net_text = net_entry.get().strip() net_list = [] if net_text: raw_nets = re.split(r"[,\s]+", net_text) raw_nets = [n.strip() for n in raw_nets if n.strip()] for net in raw_nets: if net.startswith("net") and net[3:].isdigit(): net_list.append(net) elif net.isdigit(): net_list.append(f"net{net}") else: print(f"警告:无效NET格式 '{net}',已忽略") net_list = list(dict.fromkeys(net_list)) net_list.sort( key=lambda x: int(x[3:]) ) # x是"netX"格式,x[3:]提取数字字符串并转为int print(f"已解析NET列表(排序后):{', '.join(net_list)}") else: net_list = None process_btn.config(state=tk.DISABLED) log_text.delete(1.0, tk.END) print(f"开始处理批次:{', '.join(batch_list)}\n") # 收集所有批次的数据 batch_data_dict = collect_batch_data(batch_list) # 收集所有NET的数据 net_data_dict = {} if net_list: for net in net_list: part_data = pd.DataFrame() for batch_number in batch_list: if batch_number in batch_data_dict: if net in batch_data_dict[batch_number].columns: part_data[batch_number] = batch_data_dict[batch_number][net] print(f"从批次 {batch_number} 中提取 {net} 数据") else: print(f"警告:NET {net} 在批次 {batch_number} 中不存在,已忽略") else: print(f"警告:批次 {batch_number} 的数据未找到,已忽略") if not part_data.empty: net_data_dict[net] = part_data else: print("未指定NET编号,无法绘图") # 在一个窗口中显示所有NET if net_data_dict: plot_all_nets(batch_data_dict, batch_list, net_list) else: print("所有NET均无有效数据,无法绘图") process_btn.config(state=tk.NORMAL) print("所有批次处理完成!") def create_gui(): global root, batch_entry, log_text, process_btn, net_entry root = tk.Tk() root.title("電測阻值箱線圖") root.geometry("800x650") root.resizable(True, True) # 批次号输入区域 input_frame = tk.Frame(root, padx=10, pady=10) input_frame.pack(fill=tk.X) tk.Label( input_frame, text="批次号输入(多个批次用逗号/空格分隔):", font=("SimHei", 10) ).pack(side=tk.LEFT) batch_entry = tk.Entry(input_frame, width=50, font=("SimHei", 10)) batch_entry.pack(side=tk.LEFT, padx=5) batch_entry.insert(0, "25AL90070800,25AL90070400") # NET编号输入 net_frame = tk.Frame(root, padx=10, pady=5) net_frame.pack(fill=tk.X) tk.Label( net_frame, text="NET编号输入(多个用逗号/空格分隔,留空显示全部):", font=("SimHei", 10), ).pack(side=tk.LEFT) net_entry = tk.Entry(net_frame, width=50, font=("SimHei", 10)) net_entry.pack(side=tk.LEFT, padx=5) net_entry.insert(0, "1,2,3,4,5") # 測試先默認 # 按钮 btn_frame = tk.Frame(root, padx=10, pady=5) btn_frame.pack(fill=tk.X) process_btn = tk.Button( btn_frame, text="开始处理", command=process_batches, bg="#4CAF50", fg="white", font=("SimHei", 10), ) process_btn.pack(side=tk.LEFT, padx=5) clear_btn = tk.Button( btn_frame, text="清空日志", command=lambda: log_text.delete(1.0, tk.END), bg="#f44336", fg="white", font=("SimHei", 10), ) clear_btn.pack(side=tk.LEFT, padx=5) # 日志 log_frame = tk.Frame(root, padx=10, pady=5) log_frame.pack(fill=tk.BOTH, expand=True) tk.Label(log_frame, text="处理日志:", font=("SimHei", 10)).pack(anchor=tk.W) log_text = scrolledtext.ScrolledText(log_frame, wrap=tk.WORD, font=("SimHei", 9)) log_text.pack(fill=tk.BOTH, expand=True) sys.stdout = RedirectText(log_text) root.mainloop() if __name__ == "__main__": create_gui() 我現有的程式如上,你給我提供一個完整的代碼
11-28
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值