目录
实现步骤
效果展示
核心代码
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文件
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
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() # 进入消息循环(必需组件)
项目结构如下图所示:
在目录路径栏输入cmd,回车
Test_Data_Statistics.py是GUI主文件;-w:不打开console,-c:打开console。
这里修改了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',
)
生成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(含依赖文件)