diff --git a/.DS_Store b/.DS_Store index e1626b4..aae880c 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/MR_Toolbox.py b/MR_Toolbox.py new file mode 100644 index 0000000..eedcc6c --- /dev/null +++ b/MR_Toolbox.py @@ -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_()) \ No newline at end of file diff --git a/data.xlsx b/data.xlsx new file mode 100644 index 0000000..391c07e Binary files /dev/null and b/data.xlsx differ diff --git a/pitch.png b/pitch.png new file mode 100644 index 0000000..e15ce17 Binary files /dev/null and b/pitch.png differ diff --git a/polynomial.py b/polynomial.py new file mode 100644 index 0000000..c0b6805 --- /dev/null +++ b/polynomial.py @@ -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("", 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("", 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 ", "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 ", "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() \ No newline at end of file