【Python将excel数据绘制成图表并打包成exe】

目录

实现步骤

效果展示

核心代码

1.读取excel,绘制图表

2.编写GUI界面,调用绘制图表方法

打包exe

1.在Python项目目录下运行cmd

2.pyi-makespec -w Test_Data_Statistics.py生成spec文件

3.修改生成的spec文件

4.在python项目目录下运行CMD,输入命令:pyinstaller GUI_TDS.spec

注意事项

参考资料


实现步骤

        1.pandas从excel中读取数据

        2.matplotlab将读取到的数据绘制成图表(柱状图和饼图)

        3.ttkbootstrap编写GUI交互界面

        4.pyinstaller将程序打包为可执行的exe文件

效果展示

 

 

 

核心代码

1.读取excel,绘制图表

excel表格内容如下图:

绘制饼图和柱状图:graph_handle.py

import os
import random
import pandas
from matplotlib import pyplot
from common.path_handle import config_data_path
from common.yaml_handle import Yaml


def create_pie(col, title):
    """
    绘制饼图
    :param col: excel文件中的指定列名
    :param title: 饼图标题
    :return:
    """
    path = Yaml(config_data_path).read()['data_path']  # 读取配置的data_path路径
    file_name = os.path.basename(path).split('.xlsx')[0]  # 截取data_path的文件名
    df = pandas.read_excel(path)  # pandas读取excel
    data = df[col]  # 提取指定列的数据
    x_data = (data.drop_duplicates().dropna()).values  # 去重并去掉空值
    y_data = data.value_counts(sort=False).values  # 筛选计数,不排序

    pyplot.rcParams['font.sans-serif'] = ['STSong']  # 设置字体为宋体--设置后可正常显示中文

    # figure = pyplot.figure(num=1, figsize=(7, 4), dpi=80, facecolor=None, edgecolor=None, frameon=True)
    figure = pyplot.figure()  # 创建图像
    # pyplot.pie(y_data, labels=x_data, autopct='%.1f%%',rotatelabels=True)  # 饼图--添加百分比标签,设置文字标签径向
    pyplot.pie(y_data, labels=x_data, autopct=lambda pct: pct_fun(pct, y_data))  # 饼图--添加百分比标签和数量
    pyplot.title(f"【{file_name}】{title}", fontsize=16)  # 设置标题名称和字体大小
    return figure


def pct_fun(pct, num):
    """饼图百分比格式化"""
    n = round(pct * sum(num) / 100)  # 数量
    return f'{pct:.1f}%\n{n}'


def create_bar(col, x_title, y_title, title):
    """
    绘制柱状图
    :param col: excel文件中的指定列名
    :param x_title: x轴标题
    :param y_title: y轴标题
    :param title: 柱状图标题
    :return:
    """
    path = Yaml(config_data_path).read()['data_path']  # 读取配置的data_path路径
    file_name = os.path.basename(path).split('.xlsx')[0]  # 截取data_path的文件名
    df = pandas.read_excel(path)  # pandas读取excel
    data = df[col]  # 提取指定列的数据
    x_data = (data.drop_duplicates().dropna()).values  # 去重并去掉空值
    y_data = data.value_counts(sort=False).values  # 筛选计数,不排序

    # pyplot.rcParams['font.family'] = ['sans-serif']
    pyplot.rcParams['font.sans-serif'] = ['STSong']  # 设置字体为宋体--设置后可正常显示中文
    pyplot.rcParams['axes.unicode_minus'] = False  # 设置正常显示负号

    # pyplot.figure(figsize=(8, 6))  # 设置图像大小
    figure = pyplot.figure()  # 创建图像
    pyplot.bar(x_data, y_data, edgecolor='black', color=random_color(len(y_data)))  # 柱状图
    scale = pyplot.subplot(111)  # 设置刻度字体大小--三位整数
    scale.set_xlabel(..., fontsize=14)  # 设置x轴标题字体大小
    scale.set_ylabel(..., fontsize=14)  # 设置y轴标题字体大小
    pyplot.xticks(fontsize=10)  # 设置x坐标标签字体大小
    pyplot.yticks(fontsize=10)  # 设置y坐标标签字体大小
    # pyplot.rcParams.update({'font.size': 15})  # 设置图例字体大小
    # pyplot.legend(loc='upper right')  # 定义图例所处位置,这里表示右上
    # pyplot.grid(True)  # 是否要显示网格线
    pyplot.xlabel(x_title)  # 设置x轴标题名称
    pyplot.ylabel(y_title)  # 设置y轴标题名称
    pyplot.title(f"【{file_name}】{title}", fontsize=16)  # 设置标题名称和字体大小
    for index, val in enumerate(y_data):  # 设置y轴数据
        pyplot.text(x_data[index], val, val, ha='center', va='bottom')
    return figure


def random_color(num):
    """柱状图随机颜色"""
    color_list = ['r', 'g', 'b', 'y', 'c', 'm']
    if num <= len(color_list):
        color = random.sample(color_list, num)
    else:
        gap = num - len(color_list)
        color1 = random.sample(color_list, len(color_list))
        color2 = random.sample(color_list, gap)
        color = color1 + color2
    return color

2.编写GUI界面,调用绘制图表方法

GUI交互界面:Test_Data_Statistics.py

import re
from tkinter.filedialog import askopenfilename
from matplotlib import pyplot
import ttkbootstrap as ttk
from ttkbootstrap.constants import *
from ttkbootstrap.dialogs import Messagebox
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from common.graph_handle import create_pie, create_bar
from common.path_handle import icon_path, config_data_path
from common.yaml_handle import Yaml


class MainPage:
    def __init__(self, master):
        self.root = master
        self.path = ttk.StringVar()
        self.create_page()

    def create_page(self):
        # ------------------------------------页面元素------------------------------------#
        # 主控件
        self.mf = ttk.Frame(self.root)
        self.mf.pack()

        # 标题标签
        fontstyle = ttk.font.Font(size=16)
        ttk.Label(self.mf, text='bug分析图展示', font=fontstyle, bootstyle='primary').pack()

        # 分割线
        ttk.Separator(self.mf, bootstyle='info').pack(fill='x')

        # 文件路径标签控件
        lf1 = ttk.Labelframe(self.mf, text="文件路径:", bootstyle='primary')
        lf1.pack(padx=2, pady=5)
        # 文件路径输入框
        ttk.Entry(lf1, textvariable=self.path, width=50).pack(padx=2, pady=5)
        path = Yaml(config_data_path).read()['data_path']
        self.path.set(path)
        # 文件路径选择按钮
        ttk.Button(lf1, text="选择", width=4, bootstyle='outline', command=self.select_path).pack(padx=2, pady=5)

        # 跳转按钮
        ttk.Button(self.mf, text="模块分布图", bootstyle='outline', command=self.to_page1).pack(side=LEFT, padx=2, pady=5)
        ttk.Button(self.mf, text="优先级分布图", bootstyle='outline', command=self.to_page2).pack(side=LEFT, padx=78, pady=5)
        ttk.Button(self.mf, text="状态分布图", bootstyle='outline', command=self.to_page3).pack(side=LEFT, padx=2, pady=5)
        # ------------------------------------页面元素------------------------------------#

    def select_path(self):
        # 文件选择对话框
        path_excel = askopenfilename()
        # 判断是否选择了文件
        if path_excel:  # 选择了文件则写入输入框和yaml配置
            self.path.set(path_excel)
            Yaml(config_data_path).clear()
            Yaml(config_data_path).write({'data_path': path_excel})
            if not is_xlsx(path_excel):  # 若文件不是xlsx则提示并重新选择文件
                Messagebox.show_error(title="哦豁,出错了!", message=f"请选择xlsx文件!")
                self.select_path()
        else:  # 若关闭对话框未选择文件并且文件不是xlsx则提示并重新选择文件
            path = Yaml(config_data_path).read()['data_path']
            if not is_xlsx(path):
                Messagebox.show_error(title="哦豁,出错了!", message=f"请选择xlsx文件!")
                self.select_path()

    def to_page1(self):
        self.mf.destroy()  # 销毁主控件
        figure = create_pie('模块', 'bug模块分布图')  # 绘制饼图
        FigurePage(root, figure)  # 展示图形页面

    def to_page2(self):
        self.mf.destroy()
        figure = create_bar('优先级', '优先级', '数量', 'bug优先级分布图')
        FigurePage(root, figure)

    def to_page3(self):
        self.mf.destroy()
        figure = create_bar('状态', '状态', '数量', 'bug状态分布图')
        FigurePage(root, figure)


class FigurePage:
    def __init__(self, master, figure):
        self.root, self.figure = master, figure
        self.create_page()

    def create_page(self):
        """将绘制的图像展示到tkinter窗口"""
        self.canvas = FigureCanvasTkAgg(self.figure, self.root)
        self.canvas.draw()
        self.widget = self.canvas.get_tk_widget()
        self.widget.pack()
        # 将matplotlib导航工具栏显示到窗口上
        self.toolbar = NavigationToolbar2Tk(self.canvas, self.root)
        self.toolbar.update()

        # 返回按钮
        self.back_button = ttk.Button(self.root, text='返回', command=self.back, bootstyle='primary-outline')
        self.back_button.pack(pady=2)

    def back(self):
        # 隐藏图像页面元素
        self.widget.pack_forget()
        self.toolbar.pack_forget()
        self.back_button.pack_forget()
        # 关闭图像--解决再次生成时图像重叠和大小变化问题
        pyplot.close(self.figure)
        # 显示主页面
        MainPage(root)


def _quit():
    """退出时对应的事件"""
    root.quit()  # 结束主循环
    root.destroy()  # 销毁窗口


def is_xlsx(path):
    """判断文件名称是否以xlsx结尾"""
    reg_value = '.*\.xlsx$'
    xlsx_reg = re.match(reg_value, path)
    return xlsx_reg


def win_adjust(form, x, y):
    """
    根据屏幕尺寸设置窗口出现在屏幕的位置
    :param x: x坐标左移
    :param y: y坐标上移
    :param form: 窗口
    :return:
    """
    # 获取屏幕宽度
    win_width = form.winfo_screenwidth()
    # 获取屏幕高度
    win_height = form.winfo_screenheight()

    # 偏移量
    width_adjust = win_width / 2 - x
    height_adjust = win_height / 2 - y

    form.geometry("+%d+%d" % (width_adjust, height_adjust))


if __name__ == "__main__":
    root = ttk.Window(
        title="TDS",  # 窗口标题
        themename="superhero",  # 主题
        minsize=(0, 0),  # 窗口最小宽高
        maxsize=(1920, 1080),  # 窗口最大宽高
        alpha=1.0  # 窗口的透明度(0.0--完全透明)
    )
    # root.place_window_center()  # 设置窗口居中
    win_adjust(root, 300, 500)  # 根据屏幕尺寸设置窗口出现在屏幕的位置
    root.resizable(width=False, height=False)  # 将窗口大小设置为不可变
    # root.wm_attributes('-topmost', 1)  # 使窗口位置其它窗口之上
    root.iconbitmap(icon_path)  # 更改GUI的图标

    main_page = MainPage(root)  # 默认展示主页面
    root.protocol("WM_DELETE_WINDOW", _quit)  # 关闭窗口时调用_quit,结束主循环并销毁窗口
    root.mainloop()  # 进入消息循环(必需组件)

打包exe

项目结构如下图所示:

1.在Python项目目录下运行cmd

        在目录路径栏输入cmd,回车

2.pyi-makespec -w Test_Data_Statistics.py生成spec文件

        Test_Data_Statistics.py是GUI主文件;-w:不打开console,-c:打开console。

3.修改生成的spec文件

        这里修改了spec文件名称:GUI_TDS.spec

# -*- mode: python ; coding: utf-8 -*-

#打包时可能出现大量的递归超出python预设的递归深度,报错:"RecursionError: maximum recursion depth exceeded",如下代码可解决
import sys
import os.path as osp
sys.setrecursionlimit(5000)
#----------------------------------------------------------------

block_cipher = None

SETUP_DIR='D:\\Python Projects\\TDS\\'
COMMON_DIR=SETUP_DIR+'common\\'

#1.以列表形式将项目中与打包文件相关的所有py文件写入Analysis
#2.pathex:打包的主目录,默认生成,写入文件名即可
#3.datas:资源文件或文件夹,以元组形式写入
#4.hiddenimports:若打包后执行程序时出现"No Module named xxx",可以将xxx模块填入

a = Analysis(
    ['Test_Data_Statistics.py',
	COMMON_DIR+'path_handle.py',
	COMMON_DIR+'yaml_handle.py',
	COMMON_DIR+'graph_handle.py'],
    pathex=['D:\\Python Projects\\TDS'],
    binaries=[],
    datas=[(SETUP_DIR+'config','config'),(SETUP_DIR+'img','img')],
    hiddenimports=['ttkbootstrap','matplotlib','pandas','yaml','PIL','openpyxl'],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=block_cipher,
    noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

#修改exe的logo,添加icon="具体路径"

exe = EXE(
    pyz,
    a.scripts,
    [],
    exclude_binaries=True,
    name='GUI_TDS',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    console=False,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
	icon=SETUP_DIR+'img\\z_icon.ico'
)
coll = COLLECT(
    exe,
    a.binaries,
    a.zipfiles,
    a.datas,
    strip=False,
    upx=True,
    upx_exclude=[],
    name='GUI_TDS',
)

4.在python项目目录下运行CMD,输入命令:pyinstaller GUI_TDS.spec

        生成dist和build文件夹,exe文件存放在dist文件夹中,双击即可运行

注意事项

1.pip install matplotlib需要同时安装openpyxl

2.打包成exe前系统环境需要安装使用到的第三方库

3.python项目虚拟环境和系统环境均需要安装pyinstaller

参考资料

1.Python基础之利用Matplotlib和Tkinter在应用程序中内嵌图表

2.Python GUI之tkinter的皮肤(ttkbootstrap)打造出你的窗口之美

3.pyinstaller打包为可单独运行的EXE(含依赖文件)

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值