|
|
# -*- 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(' ')#以空格分割x,y
|
|
|
print(f'sx: {float(sx)}, sy: {float(sy)}')#将sx、sy转换为浮点数并打印
|
|
|
|
|
|
def read_sample_data(file_path):
|
|
|
x, y = [], []#初始化x,y
|
|
|
with open(file_path, 'r') as file:
|
|
|
for line in file:#逐行读文件
|
|
|
line = line.strip('\n')#移除换行符
|
|
|
sx, sy = line.split(' ')#以空格分割x,y
|
|
|
x.append(float(sx))#将sx转换为浮点数并加入数组
|
|
|
y.append(float(sy))#将sy转换为浮点数并加入数组
|
|
|
gl_data.X, gl_data.Y = np.array(x), np.array(y)#x,y数组转为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
|