python-可视化提取表格-通用

表格内容如下图所示

绘图后的数据如下图所示:

代码

python 复制代码
import tkinter as tk
from tkinter import filedialog, ttk, messagebox
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

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

'''
20260911 hao
简化版:提取指定行列,第一行表头直接作为图例
表格样例:第1行表头A B C D,第2行起数据,X轴取自指定列
'''

class SimpleTablePlotGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("简单表格绘图|表头作为图例")
        self.root.geometry("1000x720")

        self.file_path = tk.StringVar()
        self.sheet_list = []
        self.df_raw = None

        # ========= 行列配置:Excel原生行号从1开始 =========
        self.cfg_header_row = tk.StringVar(value="1")       # 表头(图例)行
        self.cfg_data_start_row = tk.StringVar(value="2")    # 数据起始行
        self.cfg_col_x = tk.StringVar(value="1")            # X轴所在列

        self.full_header = []

        # -------- 文件选择区 --------
        frame_file = ttk.LabelFrame(root, text="1.选择Excel文件 & Sheet")
        frame_file.pack(fill="x", padx=10, pady=4)
        ttk.Label(frame_file, text="文件路径:").grid(row=0, column=0, padx=5, pady=6)
        ttk.Entry(frame_file, textvariable=self.file_path, width=70).grid(row=0, column=1)
        ttk.Button(frame_file, text="浏览", command=self.select_file).grid(row=0, column=2, padx=5)

        ttk.Label(frame_file, text="Sheet名称:").grid(row=1, column=0, padx=5, pady=6)
        self.sheet_combo = ttk.Combobox(frame_file, state="readonly", width=66)
        self.sheet_combo.grid(row=1, column=1)

        # -------- 行列配置区 --------
        frame_cfg = ttk.LabelFrame(root, text="2.行列配置【Excel原生行号,从1开始】")
        frame_cfg.pack(fill="x", padx=10, pady=4)

        ttk.Label(frame_cfg, text="表头(图例)行号:").grid(row=0, column=0, padx=4, pady=4, sticky="e")
        ttk.Entry(frame_cfg, textvariable=self.cfg_header_row, width=8).grid(row=0, column=1, padx=4)

        ttk.Label(frame_cfg, text="数据起始行号:").grid(row=0, column=2, padx=4, pady=4, sticky="e")
        ttk.Entry(frame_cfg, textvariable=self.cfg_data_start_row, width=8).grid(row=0, column=3, padx=4)

        ttk.Label(frame_cfg, text="X轴数据列号:").grid(row=0, column=4, padx=4, pady=4, sticky="e")
        ttk.Entry(frame_cfg, textvariable=self.cfg_col_x, width=8).grid(row=0, column=5, padx=4)

        # -------- 勾选绘图数据列 --------
        frame_col = ttk.LabelFrame(root, text="3.勾选要绘制的数据列(表头自动作为图例名称)")
        frame_col.pack(fill="both", expand=True, padx=10, pady=4)
        self.col_check_frame = ttk.Frame(frame_col)
        self.col_check_frame.pack(fill="both", expand=True, padx=4, pady=3)
        self.col_vars = {}

        # -------- 按钮 --------
        frame_btn = ttk.Frame(root)
        frame_btn.pack(pady=6)
        ttk.Button(frame_btn, text="加载Sheet数据", command=self.load_sheet_data).grid(row=0, column=0, padx=12)
        ttk.Button(frame_btn, text="执行绘图", command=self.do_plot).grid(row=0, column=1, padx=12)

        # -------- 日志输出 --------
        self.info_text = tk.Text(root, height=10, wrap="word")
        self.info_text.pack(fill="x", padx=10, pady=3)
        self.log("就绪:选择Excel,设置行列,勾选数据列绘图,表格第一行直接作为图例")

    def log(self, msg):
        self.info_text.insert(tk.END, msg + "\n")
        self.info_text.see(tk.END)
        self.root.update_idletasks()

    @staticmethod
    def normalize_str(val):
        if pd.isna(val):
            return ""
        return str(val).strip()

    def _parse_int(self, text):
        try:
            return int(text.strip())
        except ValueError:
            return None

    def select_file(self):
        fp = filedialog.askopenfilename(filetypes=[("Excel xlsx", "*.xlsx")])
        if not fp:
            return
        self.file_path.set(fp)
        excel_file = pd.ExcelFile(fp)
        self.sheet_list = excel_file.sheet_names
        self.sheet_combo["values"] = self.sheet_list
        if self.sheet_list:
            self.sheet_combo.current(0)
        self.log(f"已选择文件:{fp}")

    def load_sheet_data(self):
        fp = self.file_path.get()
        sheet_name = self.sheet_combo.get()
        if not fp or not sheet_name:
            messagebox.showwarning("警告", "请先选择文件和sheet")
            return

        h_row = self._parse_int(self.cfg_header_row.get())
        data_start_excel = self._parse_int(self.cfg_data_start_row.get())
        x_col = self._parse_int(self.cfg_col_x.get())

        if None in [h_row, data_start_excel, x_col]:
            messagebox.showerror("输入错误", "行列号必须填写整数(Excel原生行/列号)")
            return

        try:
            self.df_raw = pd.read_excel(fp, sheet_name=sheet_name, header=None)
            # 读取表头行
            header_pd_idx = h_row - 1
            self.full_header = []
            for c in range(len(self.df_raw.columns)):
                self.full_header.append(self.normalize_str(self.df_raw.iloc[header_pd_idx, c]))

            self.log(f"✅加载成功|表头行={h_row}, 数据起始行={data_start_excel}")
            self.refresh_column_checkbox()
        except Exception as e:
            self.log(f"❌读取异常:{str(e)}")
            messagebox.showerror("错误", str(e))

    def refresh_column_checkbox(self):
        for widget in self.col_check_frame.winfo_children():
            widget.destroy()
        self.col_vars.clear()
        ui_row = 0
        for col_idx, col_name in enumerate(self.full_header):
            disp = f"列{col_idx+1}|{col_name}"
            var = tk.BooleanVar()
            cb = ttk.Checkbutton(self.col_check_frame, text=disp, variable=var)
            cb.grid(row=ui_row//4, column=ui_row%4, sticky="w", padx=4, pady=2)
            self.col_vars[col_idx] = var
            ui_row += 1

    @staticmethod
    def is_invalid(val):
        if pd.isna(val):
            return True
        s = str(val).strip()
        return s in ["#N/A", "#DIV/0!", "nan", ""]

    def do_plot(self):
        if self.df_raw is None:
            messagebox.showwarning("警告", "请先加载Sheet数据")
            return

        data_start_pd = self._parse_int(self.cfg_data_start_row.get()) - 1
        x_col_idx = self._parse_int(self.cfg_col_x.get()) - 1
        selected_cols = [c for c, v in self.col_vars.items() if v.get()]

        if len(selected_cols) == 0:
            messagebox.showwarning("警告", "请至少勾选一列绘图数据")
            return

        marker_list = ["o", "s", "^", "D", "v", "*", "p", "X"]
        color_list = plt.cm.tab10(np.linspace(0,1,10))

        fig, ax = plt.subplots(figsize=(13, 7))

        for plot_idx, c in enumerate(selected_cols):
            label_text = self.full_header[c]
            x_data = []
            y_data = []
            for ridx in range(data_start_pd, len(self.df_raw)):
                x_cell = self.df_raw.iloc[ridx, x_col_idx]
                y_cell = self.df_raw.iloc[ridx, c]
                if self.is_invalid(x_cell) or self.is_invalid(y_cell):
                    continue
                try:
                    xv = float(x_cell)
                    yv = float(y_cell)
                    x_data.append(xv)
                    y_data.append(yv)
                except ValueError:
                    continue
            if len(x_data) == 0:
                self.log(f"⚠️列{c+1}({label_text})无有效数值,跳过")
                continue
            mk = marker_list[plot_idx % len(marker_list)]
            clr = color_list[plot_idx % len(color_list)]
            ax.plot(x_data, y_data, marker=mk, linestyle="-", label=label_text, color=clr)

        ax.set_xlabel(f"X轴 - 列{x_col_idx+1}", fontsize=14)
        ax.set_ylabel("Y值", fontsize=14)
        ax.set_title("表格数据绘图(表头作为图例)", fontsize=15)
        ax.grid(alpha=0.35)
        ax.legend(fontsize=11)
        plt.tight_layout()
        fig.show()
        self.log(f"✅绘图完成,共绘制 {len(selected_cols)} 条曲线")


if __name__ == "__main__":
    root = tk.Tk()
    app = SimpleTablePlotGUI(root)
    root.mainloop()
相关推荐
databook1 小时前
使用 SciPy 库进行时间序列分析
python·数据分析·scipy
java资料站1 小时前
十一、评估测试
开发语言·人工智能·python
szial1 小时前
网络爬虫与 CDP 实战(二):浏览器能访问,Python 却被拒绝?拆开登录态与 CSRF
爬虫·python·csrf
左手の明天2 小时前
SLIP 协议封装与解析详解:原理、C 代码与实战案例
linux·c语言·开发语言·c++
海上小飞龙2 小时前
【KMP算法-下篇】同一道题:Java 库函数 2600 微秒,手写 KMP 16 微秒
java·开发语言·算法
青少儿编程课堂2 小时前
后缀自动机解析:本质不同子串与最长重复片段统计
c++·python·算法·bfs·信息学竞赛
朝朝辞暮i2 小时前
C++ 第 12 课:局部变量、作用域、变量生命周期
开发语言·c++·算法
狗凯之家源码网2 小时前
淘客口令落地页生成器 PHP 版深度评测
开发语言·php
画绛集美术2 小时前
本地化部署LLM用于美术史问答:一间美术教室的实践记录
开发语言·php
怕浪猫3 小时前
LLM 面试必问的 8 个问题,答不上来直接淘汰
人工智能·python·面试