You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

365 lines
17 KiB

11 months ago
# -*- coding: utf-8 -*-
# Time : 2023/8/9 10:20
# Author : lirunsheng
# User : l'r's
# Software: PyCharm
# File : X5.py
# -*- encoding: utf-8 -*-
"""
@Author: packy945
@FileName: main.py
@DateTime: 2023/5/30 15:29
@SoftWare: PyCharm
"""
import re
from tkinter import messagebox
from PIL import ImageTk, Image
from pandas import DataFrame
import pandas as pd
from sympy import sympify, SympifyError
import sys
from fitting import *
import numpy as np
import data as gl_data
import input
import tkinter as tk
from tkinter import *
import tkinter.filedialog # 注意次数要将文件对话框导入
# from demo1 import Addfunc
from newly_added.formula import expression_output
global Q_root,F_root
global root_window
global label1,label2,label3
from screeninfo import get_monitors
import os
# 创建弹出窗口
def creat_window(title):
top = tk.Toplevel(root_window)
top.geometry("300x350")
top.title(title)
return top
# 输入框
def create_input_box(top, text, value):
box_label = tk.Label(top, text=text)
box_label.pack(padx=10, pady=10)
box_size = tk.IntVar(top, value=value) # 创建一个IntVar对象并设置默认值为3
box_size_entry = tk.Entry(top, textvariable=box_size) # 关联IntVar对象
box_size_entry.pack(padx=20, pady=20)
return box_size_entry
def check_formula_syntax(formula):
try:
sympify(formula)
return True
except SympifyError:
return False
def create_func(code_str):
# 创建一个空的命名空间
namespace = {}
# 使用exec函数执行字符串代码并指定命名空间为locals
exec(code_str, globals(), namespace)
# 返回命名空间中的函数对象
return namespace['func']
def fitting(letters,result):
code_str = '''
def func({}):
return {}
'''.format(letters,result)
return code_str
def save(result1, letters, result , result2):
gl_data.INDEX = len(gl_data.FITT_SAVE['no'])
gl_data.FITT_SAVE['no'][gl_data.INDEX] = gl_data.INDEX
gl_data.FITT_SAVE['name'][gl_data.INDEX] = result1
gl_data.FITT_SAVE['variable'][gl_data.INDEX] = letters
gl_data.FITT_SAVE['function'][gl_data.INDEX] = result
gl_data.FITT_SAVE['demo'][gl_data.INDEX] = result2
file_path = 'functions_new.xlsx'#设置路径
df = pd.DataFrame(gl_data.FITT_SAVE) # 将字典转换为DataFrame
df.to_excel(file_path, index=False) # 将DataFrame保存为Excel文件
def read_fitting():
file_path = 'functions_new.xlsx' # 设置路径
df = pd.read_excel(io=file_path, header=0) # 设置表格
gl_data.FITT_SAVE = df.to_dict()
def create_func(code_str):
# 创建一个空的命名空间
namespace = {}
# 使用exec函数执行字符串代码并指定命名空间为locals
exec(code_str, globals(), namespace)
# 返回命名空间中的函数对象
return namespace['func']
def addfunc():
top = creat_window('新增拟合函数') # 创建弹出窗口
function_entry = create_input_box(top, "拟合函数名称:", '') # 创建一个输入框,获取拟合函数名称
math_entry = create_input_box(top, "数学式:", '') # 创建一个输入框,获取数学式
def get_input(): # 创建
# 获取输入框的内容
result1 = function_entry.get()
result2 = math_entry.get()
if result1 and result2:
if check_formula_syntax(result2):
top.destroy() # 关闭窗口
letters, result = expression_output(result2)
save(result1, letters, result, result2)
gl_data.Xian_index = gl_data.INDEX
f_button()
else:
messagebox.showinfo('提示', '你输入的数学公式错误!')
else:
messagebox.showinfo('提示','你未输入的完成!')
button = tk.Button(top, text="确定", command=get_input)
button.pack()
def jis():
global screen_size
# 获取当前屏幕的宽度和高度
monitors = get_monitors()
screen_width = monitors[0].width
# 计算按钮的宽度和高度
button_width = int(screen_width * 0.85)
# 打印按钮的宽度和高度
screen_size = round(button_width*0.9/900, 1)
gl_data.MAGNIDICATION = screen_size
read_fitting()
def element(path):
# print(path)
# 加载图像并创建一个 PhotoImage 对象
image = tk.PhotoImage(file=path)
# 保存图片的引用,防止被垃圾回收
root_window.image = image
return image
def window():
global root_window
global label1, label2, label3
root_window = Tk()
root_window.title('函数拟合')
width, height= int(900*screen_size),int(560*screen_size)
root_window.geometry(str(width)+'x'+str(height)) # 设置窗口大小:宽x高,注,此处不能为 "*",必须使用 "x"
# 设置主窗口的背景颜色,颜色值可以是英文单词或者颜色值的16进制数,除此之外还可以使用Tk内置的颜色常量
img_path = ["background/add.png", "background/下方输入框.png", "background/右侧未选中.png", "background/右侧背景.png",
"background/右侧选中.png","background/大背景.png", "background/导航拦.png", "background/导航输入框.png",
"background/小直线.png", "background/手动输入数据集.png", "background/拟合.png", "background/新增拟合曲线类型.png",
"background/显示数据集与曲线.png", "background/未选中.png", "background/生成数据集.png", "background/矩形.png",
"background/背景图片.png", "background/装载.png", "background/选中.png"
]
global list_image
list_image = []
for path in img_path:
list_image.append(element(path))
root_window["background"] = "white"
root_window.resizable(0, 0) # 防止用户调整尺寸
label1 = tk.Label(root_window, text="样本数据\n集文件", font=('Times', 8), bg="white",
width=13, height=3, # 设置标签内容区大小
padx=0, pady=0, borderwidth=0, )
label1.place(x=int(10*screen_size), y=int(4*screen_size)) # 设置填充区距离、边框宽度和其样式(凹陷式)
label3 = tk.Label(root_window, text="", font=('Times', 8), bg="white", fg="black",
width=39, height=2, padx=0, pady=0, borderwidth=0, relief="ridge", highlightcolor="blue")
label3.place(x=int(122*screen_size), y=int(10*screen_size))
# 使用按钮控件调用函数
tk.Button(root_window, image=list_image[17], relief=RAISED, command=lambda: askfile()).place(x=int(370*screen_size), y=int(12*screen_size))
label2 = tk.Label(root_window, text="拟合曲线类型", font=('Times', 12), bg="white",
width=20, height=3, # 设置标签内容区大小
padx=0, pady=0, borderwidth=0, )
# 设置填充区距离、边框宽度和其样式(凹陷式)
label2.place(x=int(450*screen_size), y=int(4*screen_size))
gl_data.Canvas2 = tk.Canvas(root_window, bg='white', width=int(450*screen_size), height=int(330*screen_size))
gl_data.Canvas2.place(x=int(4*screen_size), y=int(60*screen_size))
# 定义一个处理文件的相关函数
def askfile():
# 从本地选择一个文件,并返回文件的路径
filename = tkinter.filedialog.askopenfilename()
if filename != '':#若选中了一个文件,则对文件进行读取
label3.config(text=filename)#显示文件路径
read_sample_data(filename)#将文件读取并存到sampleData中
# print_sample_data(filename)#将文件读取并逐行输出
selfdata_show(gl_data.X, gl_data.Y, gl_data.LOW, gl_data.HIGH)
else:#若未选择文件,则显示为空
label3.config(text='')
def print_sample_data(file_path):#打开对应文件
with open(file_path, 'r') as file:
for line in file:#逐行读取文件
line = line.strip('\n')#移除换行符
sx, sy = line.split(' ')#以空格分割xy
print(f'sx: {float(sx)}, sy: {float(sy)}')#将sx、sy转换为浮点数并打印
def read_sample_data(file_path):
x, y = [], []#初始化xy
with open(file_path, 'r') as file:
for line in file:#逐行读文件
line = line.strip('\n')#移除换行符
sx, sy = line.split(' ')#以空格分割xy
x.append(float(sx))#将sx转换为浮点数并加入数组
y.append(float(sy))#将sy转换为浮点数并加入数组
gl_data.X, gl_data.Y = np.array(x), np.array(y)#xy数组转为array并赋值给全局变量
def buttons():
# 随机生成样本点并显示
b_dot = tk.Button(root_window, text="生成\n数据集", relief=RAISED, bd=4, bg="white", font=("微软雅黑", 10),
command=lambda: generate_and_plot_sample_data(gl_data.LOW, gl_data.HIGH))
b_dot.place(x=int(455*screen_size), y=int(410*screen_size))
# 显示当前样本点
b_show = tk.Button(root_window, text="显示\n数据集", relief=RAISED, bd=4, bg="white", font=("微软雅黑", 10),
command=lambda: selfdata_show(gl_data.X, gl_data.Y, gl_data.LOW, gl_data.HIGH))
b_show.place(x=int(510*screen_size), y=int(410*screen_size))
# 显示数据集与曲线
b_line = tk.Button(root_window, text="显示数据\n集与曲线", relief=RAISED, bd=4, bg="white", font=("微软雅黑", 10),
command=lambda: draw_dots_and_line(gl_data.Xian_index, gl_data.LOW, gl_data.HIGH, gl_data.X, gl_data.Y))
b_line.place(x=int(565*screen_size), y=int(410*screen_size))
# 手动输入数据集
b_input = tk.Button(root_window, text="手动输入数据集", relief=RAISED, bd=4, bg="white", pady=7, font=("微软雅黑", 13),
command=lambda: input.input_num(root_window))
b_input.place(x=int(633*screen_size), y=int(410*screen_size))
# 拟合并输出拟合结果
b_fit = tk.Button(root_window, text="拟合", relief=RAISED, bd=4, bg="white", pady=7, font=("微软雅黑", 13),
command=lambda: fit_data(gl_data.Xian_index, gl_data.X, gl_data.Y))
b_fit.place(x=int(771*screen_size), y=int(410*screen_size))
def show_fit():
L = tk.Label(root_window, text='结果输出:', bg='white', font=("微软雅黑", 16)
, anchor=W)
L.place(x=20, y=int(480*screen_size), width=int(100*screen_size), height=int(30*screen_size))
sout = str(gl_data.Out)
ans = tk.Label(root_window, text=sout, font=("微软雅黑", 14)
, anchor=W, justify='left')
ans.place(x=int(120*screen_size), y=int(480*screen_size), width=int(760*screen_size), height=100)
print(sout)
def fit_data(xian_index, sx, sy):
letters, result = gl_data.FITT_SAVE['variable'][xian_index], gl_data.FITT_SAVE['function'][xian_index]
code_str = fitting(letters, result)
func = create_func(code_str) # 获取当前函数func
popt, pcov = curve_fit(func, sx, sy) # 用curve_fit来对点进行拟合
yvals_pow = func(gl_data.X, *popt)
rr = gl_data.goodness_of_fit(yvals_pow, gl_data.Y)# 计算本次拟合的R*R值用于表示拟合优度
# 输出拟合后的各个参数值
ans = '\n函数系数F(x) = '
letter = str(letters[2:]).split(',')
pattern = re.compile('|'.join(letter))
s = pattern.sub('{:.2f}', gl_data.FITT_SAVE['demo'][xian_index])
ans += str(s.format(*popt))
gl_data.Out = '函数形式:' + gl_data.FITT_SAVE['name'][xian_index] + ' '
gl_data.Out += gl_data.FITT_SAVE['demo'][xian_index]
gl_data.Out += ans
gl_data.Out += '\n拟合优度(R\u00b2)' + str(round(rr, 5))
show_fit()# 显示函数信息到输出框
def change_Q(no):
gl_data.Quadrant = no #更改全局变量的象限显示
if no:#若为一象限则修改显示下限为0
gl_data.LOW = 0
else:#若为四象限,则修改显示下限为-gl_data.maxV
gl_data.LOW = -gl_data.MAXV
q_button()#更新象限显示面板
def q_button():
r = 7.5
rr = 2.5
for widget in Q_root.winfo_children():
widget.destroy()
q_cv = tk.Canvas(Q_root, width=int(400*screen_size), height=int(50*screen_size))
q_cv.place(x=0, y=0)
l = tk.Label(Q_root, text='坐标轴', bd=0, font=("微软雅黑", 16)
, anchor=W)
l.place(x=20, y=0, width=int(80*screen_size), height=int(50*screen_size),)
# 四象限按钮
b1 = tk.Button(Q_root, text='四象限', bd=0, font=("微软雅黑", 16)
, command=lambda: change_Q(0), anchor=W)
b1.place(x=int(170*screen_size), y=0, width=int(80*screen_size), height=int(50*screen_size),)
# 一象限按钮
b2 = tk.Button(Q_root, text='一象限', bd=0, font=("微软雅黑", 16)
, command=lambda: change_Q(1), anchor=W)
b2.place(x=int(320*screen_size), y=0, width=int(80*screen_size), height=int(50*screen_size),)
# 绘制标记框
q_cv.create_oval(int((140 - r)*screen_size), int((25 - r)*screen_size), int((140 + r)*screen_size), int((25 + r)*screen_size)
, fill="white", width=1, outline="black")
q_cv.create_oval(int((290 - r)*screen_size), int((25 - r)*screen_size), int((290 + r)*screen_size), int((25 + r)*screen_size)
, fill="white", width=1, outline="black")
# 根据当前的象限选择值来填充标记框
if gl_data.Quadrant == 0:
q_cv.create_oval(int((140 - rr)*screen_size), int((25 - rr)*screen_size), int((140 + rr)*screen_size), int((25 + rr)*screen_size)
, fill="black", width=1, outline="black")
else:
q_cv.create_oval(int((290 - rr)*screen_size), int((25 - rr)*screen_size), int((290 + rr)*screen_size), int((25 + rr)*screen_size)
, fill="black", width=1, outline="black")
def change_f(no):
gl_data.Xian_index = no#设置全局函数编号为该函数
f_button()#重新绘制函数显示界面
def f_button():
r = 7.5 # 设置按钮大小
rr = 2.5 # 设置按钮大小
for widget in F_root.winfo_children():
widget.destroy() # 清空原有按钮
gl_data.INDEX = len(gl_data.FITT_SAVE['no'])
# 创建滚动条
scrollbar = tk.Scrollbar(F_root)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 创建Canvas对象
f_cv = tk.Canvas(F_root, width=int(370 * screen_size), height=int(310 * screen_size), yscrollcommand=scrollbar.set)
f_cv.pack(side=tk.LEFT)
# 设置滚动条与Canvas的关联
scrollbar.config(command=f_cv.yview)
# # 绘制矩形边框及底色
# f_cv.create_rectangle(2, 2, int(398 * screen_size), int(328 * screen_size), fill='white',
# outline="#0099ff")
cur = 0 # 函数计数
for i in range(gl_data.INDEX): # 遍历函数库
f_cv.create_oval(int((20 - r) * screen_size), int((20 + 15 + cur * 30 - r) * screen_size),
int((20 + r) * screen_size), int((20 + 15 + cur * 30 + r) * screen_size)
, fill="white", width=1, outline="black") # 绘制标记框
if gl_data.FITT_SAVE['no'][i] == gl_data.Xian_index: # 若选择为当前函数则标记
f_cv.create_oval(int((20 - rr) * screen_size), int((20 + 15 + cur * 30 - rr) * screen_size),
int((20 + rr) * screen_size), int((20 + 15 + cur * 30 + rr) * screen_size)
, fill="black", width=int(screen_size), outline="black")
# 绘制切换按钮,单击则将当前使用函数切换为该函数
button = tk.Button(F_root, text=gl_data.FITT_SAVE['name'][i] + ' ' + gl_data.FITT_SAVE['demo'][i], bd=0,
bg="white", font=("微软雅黑", 12),
command=lambda x=gl_data.FITT_SAVE['no'][i]: change_f(x), anchor=W)
# 将按钮放置在Canvas中
f_cv.create_window(int(40 * screen_size), int((35 + cur * 30) * screen_size), window=button, anchor="w")
cur += 1 # 计数加一
f_cv.configure(scrollregion=f_cv.bbox("all"))
def close_window():
sys.exit()
def main():
global Q_root, F_root,root_window
jis()
# gl_data.LOW, gl_data.HIGH = int(gl_data.LOW*screen_size), int(gl_data.HIGH*screen_size)
gl_data.X = []
gl_data.X = []
window()
# 函数选择相关界面
F_root = tk.Frame(root_window, width=int(400*screen_size), height=int(330*screen_size), bg='white', )
F_root.place(x=int(490*screen_size), y=int(60*screen_size))
# 象限选择相关界面
Q_root = tk.Frame(root_window, width=int(400*screen_size), height=int(50*screen_size), bg='white', )
Q_root.place(x=int(20*screen_size), y=int(410*screen_size))
buttons()
f_button()
q_button()
show_fit()
tk.Button(root_window, text="新增拟合曲线类型", bd=4, command=addfunc).place(x=int(740*screen_size), y=int(12)*screen_size)
# 设置Grid布局的列和行权重
root_window.protocol("WM_DELETE_WINDOW", close_window)
root_window.mainloop()
if __name__ == '__main__':
main()
pass