mirror of
https://github.com/goldenfishs/MRobot.git
synced 2025-06-15 22:36:37 +08:00
创建函数拟合工具,准备创建MR工具箱
This commit is contained in:
parent
918f6b443c
commit
2e8c902dd2
92
MR_Toolbox.py
Normal file
92
MR_Toolbox.py
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import sys
|
||||||
|
from PyQt5.QtWidgets import (
|
||||||
|
QApplication, QWidget, QLabel, QPushButton, QTextEdit, QVBoxLayout,
|
||||||
|
QHBoxLayout, QStackedWidget, QSizePolicy, QFrame
|
||||||
|
)
|
||||||
|
from PyQt5.QtGui import QPixmap, QFont
|
||||||
|
from PyQt5.QtCore import Qt
|
||||||
|
|
||||||
|
class ToolboxUI(QWidget):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.setWindowTitle("MRobot 工具箱")
|
||||||
|
self.setMinimumSize(900, 600)
|
||||||
|
self.init_ui()
|
||||||
|
|
||||||
|
def init_ui(self):
|
||||||
|
# 主布局
|
||||||
|
main_layout = QHBoxLayout(self)
|
||||||
|
main_layout.setContentsMargins(10, 10, 10, 10)
|
||||||
|
main_layout.setSpacing(10)
|
||||||
|
|
||||||
|
# 左半区
|
||||||
|
left_frame = QFrame()
|
||||||
|
left_frame.setFrameShape(QFrame.StyledPanel)
|
||||||
|
left_layout = QVBoxLayout(left_frame)
|
||||||
|
left_layout.setSpacing(20)
|
||||||
|
|
||||||
|
# Logo
|
||||||
|
logo_label = QLabel()
|
||||||
|
logo_pixmap = QPixmap(180, 80)
|
||||||
|
logo_pixmap.fill(Qt.transparent)
|
||||||
|
logo_label.setPixmap(logo_pixmap)
|
||||||
|
logo_label.setText("MRobot")
|
||||||
|
logo_label.setAlignment(Qt.AlignCenter)
|
||||||
|
logo_label.setFont(QFont("Arial", 28, QFont.Bold))
|
||||||
|
logo_label.setStyleSheet("color: #3498db;")
|
||||||
|
logo_label.setFixedHeight(100)
|
||||||
|
left_layout.addWidget(logo_label)
|
||||||
|
|
||||||
|
# 按钮区
|
||||||
|
self.buttons = []
|
||||||
|
button_names = ["功能一", "功能二", "功能三", "功能四"]
|
||||||
|
for idx, name in enumerate(button_names):
|
||||||
|
btn = QPushButton(name)
|
||||||
|
btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||||
|
btn.setMinimumHeight(40)
|
||||||
|
btn.setStyleSheet("""
|
||||||
|
QPushButton {
|
||||||
|
background-color: #2980b9; color: white;
|
||||||
|
border-radius: 8px; font-size: 16px;
|
||||||
|
}
|
||||||
|
QPushButton:hover {
|
||||||
|
background-color: #3498db;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
btn.clicked.connect(lambda checked, i=idx: self.switch_function(i))
|
||||||
|
self.buttons.append(btn)
|
||||||
|
left_layout.addWidget(btn)
|
||||||
|
|
||||||
|
left_layout.addStretch(1)
|
||||||
|
|
||||||
|
# 文本输出框
|
||||||
|
self.output_box = QTextEdit()
|
||||||
|
self.output_box.setReadOnly(True)
|
||||||
|
self.output_box.setFixedHeight(80)
|
||||||
|
self.output_box.setStyleSheet("background: #f4f6f7; border-radius: 6px;")
|
||||||
|
left_layout.addWidget(self.output_box)
|
||||||
|
|
||||||
|
left_frame.setMaximumWidth(240)
|
||||||
|
main_layout.addWidget(left_frame)
|
||||||
|
|
||||||
|
# 右半区
|
||||||
|
self.stack = QStackedWidget()
|
||||||
|
for i in range(len(button_names)):
|
||||||
|
page = QLabel(f"这里是 {button_names[i]} 的功能界面")
|
||||||
|
page.setAlignment(Qt.AlignCenter)
|
||||||
|
page.setFont(QFont("微软雅黑", 20))
|
||||||
|
self.stack.addWidget(page)
|
||||||
|
main_layout.addWidget(self.stack)
|
||||||
|
|
||||||
|
# 默认输出
|
||||||
|
self.output_box.append("欢迎使用 MRobot 工具箱!请选择左侧功能。")
|
||||||
|
|
||||||
|
def switch_function(self, idx):
|
||||||
|
self.stack.setCurrentIndex(idx)
|
||||||
|
self.output_box.append(f"已切换到功能:{self.buttons[idx].text()}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
win = ToolboxUI()
|
||||||
|
win.show()
|
||||||
|
sys.exit(app.exec_())
|
276
polynomial.py
Normal file
276
polynomial.py
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
import tkinter as tk
|
||||||
|
from tkinter import filedialog, messagebox, ttk
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
||||||
|
from matplotlib.figure import Figure
|
||||||
|
|
||||||
|
class PolyFitApp:
|
||||||
|
def __init__(self, root):
|
||||||
|
self.root = root
|
||||||
|
self.root.title("多项式拟合工具")
|
||||||
|
self.data_x = []
|
||||||
|
self.data_y = []
|
||||||
|
self.last_coeffs = None
|
||||||
|
|
||||||
|
# ===== 主布局 =====
|
||||||
|
main_frame = ttk.Frame(root, padding=8)
|
||||||
|
main_frame.pack(fill="both", expand=True)
|
||||||
|
main_frame.columnconfigure(0, weight=0)
|
||||||
|
main_frame.columnconfigure(1, weight=1)
|
||||||
|
main_frame.rowconfigure(0, weight=1)
|
||||||
|
|
||||||
|
# ===== 左侧区 =====
|
||||||
|
left_frame = ttk.Frame(main_frame, width=320, height=580) # 设置固定宽度
|
||||||
|
left_frame.grid(row=0, column=0, sticky="nsw") # 只贴左侧
|
||||||
|
left_frame.columnconfigure(0, weight=1)
|
||||||
|
left_frame.grid_propagate(False) # 防止自动缩放
|
||||||
|
|
||||||
|
# --- 数据输入区 ---
|
||||||
|
input_frame = ttk.LabelFrame(left_frame, text="数据输入", padding=6)
|
||||||
|
input_frame.grid(row=0, column=0, sticky="ew", pady=(0, 8))
|
||||||
|
input_frame.columnconfigure(0, weight=1)
|
||||||
|
|
||||||
|
self.excel_btn = ttk.Button(input_frame, text="导入excel文件", command=self.load_excel)
|
||||||
|
self.excel_btn.grid(row=0, column=0, sticky="ew", padx=2, pady=2)
|
||||||
|
|
||||||
|
# 滚动区域
|
||||||
|
self.scroll_canvas = tk.Canvas(input_frame, height=200)
|
||||||
|
self.scroll_canvas.grid(row=1, column=0, sticky="ew", pady=4)
|
||||||
|
self.scrollbar = ttk.Scrollbar(input_frame, orient="vertical", command=self.scroll_canvas.yview)
|
||||||
|
self.scrollbar.grid(row=1, column=1, sticky="ns")
|
||||||
|
self.scroll_canvas.configure(yscrollcommand=self.scrollbar.set)
|
||||||
|
|
||||||
|
self.manual_frame = ttk.Frame(self.scroll_canvas)
|
||||||
|
self.manual_frame.bind("<Configure>", lambda e: self.scroll_canvas.configure(scrollregion=self.scroll_canvas.bbox("all")))
|
||||||
|
self.scroll_canvas.create_window((0, 0), window=self.manual_frame, anchor="nw")
|
||||||
|
|
||||||
|
self.point_rows = []
|
||||||
|
self.add_row_btn = ttk.Button(input_frame, text="添加数据", command=self.add_point_row)
|
||||||
|
self.add_row_btn.grid(row=2, column=0, sticky="ew", padx=2, pady=2)
|
||||||
|
|
||||||
|
# --- 参数区 ---
|
||||||
|
param_frame = ttk.LabelFrame(left_frame, text="拟合参数", padding=6)
|
||||||
|
param_frame.grid(row=1, column=0, sticky="ew", pady=(0, 8))
|
||||||
|
ttk.Label(param_frame, text="选择多项式阶数:").grid(row=0, column=0, padx=2, pady=2, sticky="e")
|
||||||
|
self.order_spin = ttk.Spinbox(param_frame, from_=1, to=10, width=5)
|
||||||
|
self.order_spin.set(2)
|
||||||
|
self.order_spin.grid(row=0, column=1, padx=2, pady=2, sticky="w")
|
||||||
|
# 优化复选框和拟合按钮同一行
|
||||||
|
self.fit_btn = ttk.Button(param_frame, text="拟合并显示", command=self.fit_and_plot)
|
||||||
|
self.fit_btn.grid(row=1, column=0, padx=2, pady=4, sticky="ew")
|
||||||
|
self.optimize_var = tk.BooleanVar(value=True)
|
||||||
|
self.optimize_check = ttk.Checkbutton(param_frame, text="优化曲线(防止突起)", variable=self.optimize_var)
|
||||||
|
self.optimize_check.grid(row=1, column=1, padx=2, pady=4, sticky="w")
|
||||||
|
|
||||||
|
# --- 输出区 ---
|
||||||
|
output_frame = ttk.LabelFrame(left_frame, text="表达式与代码", padding=6)
|
||||||
|
output_frame.grid(row=2, column=0, sticky="ew")
|
||||||
|
self.output = tk.Text(output_frame, height=6, width=40, font=("Consolas", 10))
|
||||||
|
self.output.grid(row=0, column=0, columnspan=3, padx=2, pady=2)
|
||||||
|
ttk.Label(output_frame, text="输出代码格式:").grid(row=1, column=0, padx=2, pady=2, sticky="e")
|
||||||
|
self.code_type = ttk.Combobox(output_frame, values=["C", "C++", "Python"], width=8)
|
||||||
|
self.code_type.set("C")
|
||||||
|
self.code_type.grid(row=1, column=1, padx=2, pady=2, sticky="w")
|
||||||
|
self.gen_code_btn = ttk.Button(output_frame, text="生成函数代码", command=self.generate_code)
|
||||||
|
self.gen_code_btn.grid(row=1, column=2, padx=2, pady=2, sticky="ew")
|
||||||
|
|
||||||
|
# ===== 右侧区 =====
|
||||||
|
right_frame = ttk.Frame(main_frame)
|
||||||
|
# right_frame.grid(row=0, column=1, sticky="nsew") # 填满剩余空间
|
||||||
|
right_frame.grid(row=0, column=1, sticky="nsew", padx=(8, 0))
|
||||||
|
right_frame.rowconfigure(0, weight=1)
|
||||||
|
right_frame.columnconfigure(0, weight=1)
|
||||||
|
|
||||||
|
plot_frame = ttk.LabelFrame(right_frame, text="拟合曲线", padding=6)
|
||||||
|
plot_frame.grid(row=0, column=0, sticky="nsew")
|
||||||
|
plot_frame.rowconfigure(0, weight=1)
|
||||||
|
plot_frame.columnconfigure(0, weight=1)
|
||||||
|
self.figure = Figure(figsize=(5, 4))
|
||||||
|
self.canvas = FigureCanvasTkAgg(self.figure, master=plot_frame)
|
||||||
|
self.canvas.get_tk_widget().pack(fill="both", expand=True)
|
||||||
|
self.scroll_canvas.bind_all("<MouseWheel>", self._on_mousewheel)
|
||||||
|
|
||||||
|
|
||||||
|
def _on_mousewheel(self, event):
|
||||||
|
# Windows下event.delta为120的倍数,负值向下
|
||||||
|
self.scroll_canvas.yview_scroll(int(-1*(event.delta/120)), "units")
|
||||||
|
|
||||||
|
def add_point_row(self, x_val="", y_val=""):
|
||||||
|
row = {}
|
||||||
|
idx = len(self.point_rows)
|
||||||
|
row['x'] = ttk.Entry(self.manual_frame, width=10)
|
||||||
|
row['x'].insert(0, str(x_val))
|
||||||
|
row['y'] = ttk.Entry(self.manual_frame, width=10)
|
||||||
|
row['y'].insert(0, str(y_val))
|
||||||
|
row['del'] = ttk.Button(self.manual_frame, text="删除", width=5, command=lambda r=idx: self.delete_point_row(r))
|
||||||
|
row['x'].grid(row=idx, column=0, padx=1, pady=1)
|
||||||
|
row['y'].grid(row=idx, column=1, padx=1, pady=1)
|
||||||
|
row['del'].grid(row=idx, column=2, padx=1, pady=1)
|
||||||
|
self.point_rows.append(row)
|
||||||
|
self.refresh_point_rows()
|
||||||
|
|
||||||
|
def delete_point_row(self, idx):
|
||||||
|
for widget in self.point_rows[idx].values():
|
||||||
|
widget.grid_forget()
|
||||||
|
widget.destroy()
|
||||||
|
self.point_rows.pop(idx)
|
||||||
|
self.refresh_point_rows()
|
||||||
|
|
||||||
|
def refresh_point_rows(self):
|
||||||
|
for i, row in enumerate(self.point_rows):
|
||||||
|
row['x'].grid(row=i, column=0, padx=1, pady=1)
|
||||||
|
row['y'].grid(row=i, column=1, padx=1, pady=1)
|
||||||
|
row['del'].config(command=lambda r=i: self.delete_point_row(r))
|
||||||
|
row['del'].grid(row=i, column=2, padx=1, pady=1)
|
||||||
|
|
||||||
|
def load_excel(self):
|
||||||
|
file = filedialog.askopenfilename(filetypes=[("Excel files", "*.xlsx *.xls")])
|
||||||
|
if file:
|
||||||
|
try:
|
||||||
|
data = pd.read_excel(file, usecols=[0, 1])
|
||||||
|
new_x = data.iloc[:, 0].values.tolist()
|
||||||
|
new_y = data.iloc[:, 1].values.tolist()
|
||||||
|
messagebox.showinfo("成功", "数据导入成功!")
|
||||||
|
for x, y in zip(new_x, new_y):
|
||||||
|
self.add_point_row(x, y)
|
||||||
|
except Exception as e:
|
||||||
|
messagebox.showerror("错误", f"读取Excel失败: {e}")
|
||||||
|
|
||||||
|
def get_manual_points(self):
|
||||||
|
x_list, y_list = [], []
|
||||||
|
for row in self.point_rows:
|
||||||
|
try:
|
||||||
|
x = float(row['x'].get())
|
||||||
|
y = float(row['y'].get())
|
||||||
|
x_list.append(x)
|
||||||
|
y_list.append(y)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
return x_list, y_list
|
||||||
|
|
||||||
|
|
||||||
|
def fit_and_plot(self):
|
||||||
|
# 始终以手动输入区为准
|
||||||
|
self.data_x, self.data_y = self.get_manual_points()
|
||||||
|
try:
|
||||||
|
order = int(self.order_spin.get())
|
||||||
|
except ValueError:
|
||||||
|
messagebox.showwarning("输入错误", "阶数必须为整数!")
|
||||||
|
return
|
||||||
|
n_points = len(self.data_x)
|
||||||
|
if n_points < order + 1:
|
||||||
|
messagebox.showwarning("数据不足", "数据点数量不足以拟合该阶多项式!")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 阶数过高判断,只提示不强制修改
|
||||||
|
max_order = max(2, min(6, n_points // 3))
|
||||||
|
if order > max_order:
|
||||||
|
messagebox.showwarning("阶数过高", f"当前数据点数为{n_points},建议阶数不超过{max_order},否则容易出现异常突起!")
|
||||||
|
|
||||||
|
x = np.array(self.data_x, dtype=np.float64)
|
||||||
|
y = np.array(self.data_y, dtype=np.float64)
|
||||||
|
|
||||||
|
# ----------- 新增归一化 -----------
|
||||||
|
x_min, x_max = x.min(), x.max()
|
||||||
|
if x_max - x_min == 0:
|
||||||
|
messagebox.showwarning("数据错误", "所有x值都相同,无法拟合!")
|
||||||
|
return
|
||||||
|
x_norm = (x - x_min) / (x_max - x_min)
|
||||||
|
# ---------------------------------
|
||||||
|
|
||||||
|
try:
|
||||||
|
if self.optimize_var.get():
|
||||||
|
# 优化:加大rcond,减少高阶影响
|
||||||
|
coeffs = np.polyfit(x_norm, y, order, rcond=1e-3)
|
||||||
|
else:
|
||||||
|
coeffs = np.polyfit(x_norm, y, order)
|
||||||
|
except Exception as e:
|
||||||
|
messagebox.showerror("拟合错误", f"多项式拟合失败:{e}")
|
||||||
|
return
|
||||||
|
poly = np.poly1d(coeffs)
|
||||||
|
expr = "y = " + " + ".join([f"{c:.6g}*x_norm^{order-i}" for i, c in enumerate(coeffs)])
|
||||||
|
self.output.delete(1.0, tk.END)
|
||||||
|
self.output.insert(tk.END, f"归一化x: x_norm=(x-{x_min:.6g})/({x_max:.6g}-{x_min:.6g})\n")
|
||||||
|
self.output.insert(tk.END, expr + "\n")
|
||||||
|
# 绘图
|
||||||
|
self.figure.clear()
|
||||||
|
ax = self.figure.add_subplot(111)
|
||||||
|
ax.scatter(x, y, color='red', label='数据点')
|
||||||
|
x_fit = np.linspace(x_min, x_max, 200)
|
||||||
|
x_fit_norm = (x_fit - x_min) / (x_max - x_min)
|
||||||
|
y_fit = poly(x_fit_norm)
|
||||||
|
ax.plot(x_fit, y_fit, label='拟合曲线')
|
||||||
|
ax.legend()
|
||||||
|
self.canvas.draw()
|
||||||
|
self.last_coeffs = coeffs
|
||||||
|
self.last_xmin = x_min
|
||||||
|
self.last_xmax = x_max
|
||||||
|
|
||||||
|
def generate_code(self):
|
||||||
|
if self.last_coeffs is None:
|
||||||
|
messagebox.showwarning("未拟合", "请先拟合数据!")
|
||||||
|
return
|
||||||
|
coeffs = self.last_coeffs
|
||||||
|
order = len(coeffs) - 1
|
||||||
|
code_type = self.code_type.get()
|
||||||
|
if code_type == "C":
|
||||||
|
code = self.create_c_function(coeffs)
|
||||||
|
elif code_type == "C++":
|
||||||
|
code = self.create_cpp_function(coeffs)
|
||||||
|
else:
|
||||||
|
code = self.create_py_function(coeffs)
|
||||||
|
self.output.delete(1.0, tk.END)
|
||||||
|
self.output.insert(tk.END, code)
|
||||||
|
|
||||||
|
def create_c_function(self, coeffs):
|
||||||
|
lines = ["#include <math.h>", "double polynomial(double x) {", " return "]
|
||||||
|
n = len(coeffs)
|
||||||
|
terms = []
|
||||||
|
for i, c in enumerate(coeffs):
|
||||||
|
exp = n - i - 1
|
||||||
|
if exp == 0:
|
||||||
|
terms.append(f"{c:.8g}")
|
||||||
|
elif exp == 1:
|
||||||
|
terms.append(f"{c:.8g}*x")
|
||||||
|
else:
|
||||||
|
terms.append(f"{c:.8g}*pow(x,{exp})")
|
||||||
|
lines[-1] += " + ".join(terms) + ";"
|
||||||
|
lines.append("}")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
def create_cpp_function(self, coeffs):
|
||||||
|
lines = ["#include <cmath>", "double polynomial(double x) {", " return "]
|
||||||
|
n = len(coeffs)
|
||||||
|
terms = []
|
||||||
|
for i, c in enumerate(coeffs):
|
||||||
|
exp = n - i - 1
|
||||||
|
if exp == 0:
|
||||||
|
terms.append(f"{c:.8g}")
|
||||||
|
elif exp == 1:
|
||||||
|
terms.append(f"{c:.8g}*x")
|
||||||
|
else:
|
||||||
|
terms.append(f"{c:.8g}*pow(x,{exp})")
|
||||||
|
lines[-1] += " + ".join(terms) + ";"
|
||||||
|
lines.append("}")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
def create_py_function(self, coeffs):
|
||||||
|
n = len(coeffs)
|
||||||
|
lines = ["def polynomial(x):", " return "]
|
||||||
|
terms = []
|
||||||
|
for i, c in enumerate(coeffs):
|
||||||
|
exp = n - i - 1
|
||||||
|
if exp == 0:
|
||||||
|
terms.append(f"{c:.8g}")
|
||||||
|
elif exp == 1:
|
||||||
|
terms.append(f"{c:.8g}*x")
|
||||||
|
else:
|
||||||
|
terms.append(f"{c:.8g}*x**{exp}")
|
||||||
|
lines[-1] += " + ".join(terms)
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
root = tk.Tk()
|
||||||
|
app = PolyFitApp(root)
|
||||||
|
root.mainloop()
|
Loading…
Reference in New Issue
Block a user