python拟合曲线
!/usr/bin/env python
-- coding: utf-8 --
"""
散点曲线拟合系统 - 美化版
使用tkinter创建美观的用户界面
"""
兼容Python 2.x和3.x的tkinter导入
try:
# Python 3.x
import tkinter as tk
from tkinter import messagebox, simpledialog, ttk
from tkinter import font as tkfont
except ImportError:
# Python 2.x
import Tkinter as tk
import tkMessageBox as messagebox
import tkSimpleDialog as simpledialog
import ttk
import tkFont as tkfont
import random
import math
定义颜色主题
THEME_COLOR = {
"background": "#f5f5f5", # 背景色
"button_bg": "#e74c3c", # 按钮背景色(改为更醒目的红色)
"button_fg": "#ffffff", # 按钮文字颜色(白色)
"button_text": "#ff0000", # 按钮内文字颜色(红色)
"button_active": "#c0392b", # 按钮激活颜色
"canvas_bg": "white", # 画布背景色
"axis_color": "#333333", # 坐标轴颜色
"point_color": "#e74c3c", # 数据点颜色
"line_color": "#2980b9", # 拟合线颜色
"text_color": "#2c3e50", # 文字颜色
"header_bg": "#2c3e50", # 标题栏背景色
"header_fg": "white", # 标题栏文字颜色
"grid_color": "#e0e0e0", # 网格线颜色
"axis_label": "#555555", # 坐标轴标签颜色
}
class EnhancedApp:
def init(self, root):
self.root = root
self.root.title("散点曲线拟合系统 - 美化版")
self.root.geometry("900x650")
self.root.configure(bg=THEME_COLOR["background"])
# 设置窗口图标和样式
self.setup_styles()
# 创建界面
self.create_widgets()
# 初始数据
self.x_data = []
self.y_data = []
# 调用随机数据生成函数,初始化时不显示消息
self.generate_data(show_message=False)
def setup_styles(self):
"""设置自定义样式"""
# 创建自定义样式
self.style = ttk.Style()
self.style.configure("TFrame", background=THEME_COLOR["background"])
self.style.configure("TButton",
background=THEME_COLOR["button_bg"],
foreground=THEME_COLOR["button_text"], # 使用红色文字
font=("微软雅黑", 12, "bold"), # 增加字体大小
padding=10) # 增加内边距
self.style.map("TButton",
foreground=[('active', THEME_COLOR["button_text"])], # 激活时也保持红色
background=[('active', THEME_COLOR["button_active"])])
# 设置标题字体
self.title_font = tkfont.Font(family="微软雅黑", size=20, weight="bold") # 增大标题字体
self.info_font = tkfont.Font(family="微软雅黑", size=12) # 增大信息字体
self.formula_font = tkfont.Font(family="微软雅黑", size=14, weight="bold") # 增大公式字体
self.axis_font = tkfont.Font(family="微软雅黑", size=10) # 增大坐标轴字体
def create_widgets(self):
"""创建界面组件"""
# 创建标题栏
header_frame = tk.Frame(self.root, bg=THEME_COLOR["header_bg"], height=60)
header_frame.pack(fill=tk.X, pady=0)
# 标题标签
title_label = tk.Label(header_frame,
text="散点曲线拟合系统",
font=self.title_font,
bg=THEME_COLOR["header_bg"],
fg=THEME_COLOR["header_fg"])
title_label.pack(pady=15)
# 创建主框架
main_frame = ttk.Frame(self.root)
main_frame.pack(fill=tk.BOTH, expand=True, padx=15, pady=15)
# 创建按钮框架
button_frame = ttk.Frame(main_frame)
button_frame.pack(side=tk.TOP, fill=tk.X, pady=10)
# 添加按钮,增加间距使文字更清晰
ttk.Button(button_frame, text="生成随机数据", command=self.generate_data).pack(side=tk.LEFT, padx=10, pady=8)
ttk.Button(button_frame, text="手动输入数据", command=self.input_data).pack(side=tk.LEFT, padx=10, pady=8)
ttk.Button(button_frame, text="清空数据", command=self.clear_data).pack(side=tk.LEFT, padx=10, pady=8)
ttk.Button(button_frame, text="执行拟合", command=self.fit_data).pack(side=tk.LEFT, padx=10, pady=8)
# 创建拟合阶数选择框架
fit_frame = ttk.Frame(main_frame)
fit_frame.pack(fill=tk.X, pady=5)
# 添加拟合阶数选择标签
ttk.Label(fit_frame, text="拟合阶数:",
font=("微软雅黑", 11, "bold")).pack(side=tk.LEFT, padx=5)
# 创建拟合阶数下拉框
self.degree_var = tk.StringVar()
self.degree_var.set("1") # 默认为一阶(线性)拟合
degree_options = ["1", "2", "3", "4", "5"]
degree_menu = ttk.Combobox(fit_frame,
textvariable=self.degree_var,
values=degree_options,
width=5,
font=("微软雅黑", 10))
degree_menu.pack(side=tk.LEFT, padx=5)
# 创建信息框架
info_frame = ttk.Frame(main_frame)
info_frame.pack(fill=tk.X, pady=8)
self.info_label = tk.Label(info_frame,
text="当前数据点: 0",
font=self.info_font,
bg=THEME_COLOR["background"],
fg=THEME_COLOR["text_color"])
self.info_label.pack(side=tk.LEFT)
self.formula_label = tk.Label(info_frame,
text="",
font=self.formula_font,
bg=THEME_COLOR["background"],
fg=THEME_COLOR["line_color"])
self.formula_label.pack(side=tk.RIGHT)
# 创建画布框架(带边框效果)
canvas_container = ttk.Frame(main_frame, style="TFrame")
canvas_container.pack(fill=tk.BOTH, expand=True, pady=5)
# 创建画布
self.canvas = tk.Canvas(canvas_container,
bg=THEME_COLOR["canvas_bg"],
highlightthickness=1,
highlightbackground="#cccccc")
self.canvas.pack(fill=tk.BOTH, expand=True)
# 创建状态栏
status_frame = tk.Frame(self.root, bg="#e0e0e0", height=25)
status_frame.pack(side=tk.BOTTOM, fill=tk.X)
status_label = tk.Label(status_frame,
text="准备就绪",
bg="#e0e0e0",
fg="#555555",
anchor=tk.W)
status_label.pack(side=tk.LEFT, padx=10)
def generate_data(self, show_message=True):
"""生成随机数据"""
self.x_data = []
self.y_data = []
# 设置随机种子,确保每次生成不同的随机数据
random.seed()
# 随机决定数据点数量(8-15个点)
num_points = random.randint(8, 15)
for i in range(num_points):
# 生成随机x值(0-10之间)
x = random.uniform(0, 10)
# 使用更多随机性来生成数据
y = 2 * x + 3 + random.uniform(-2, 2)
self.x_data.append(x)
self.y_data.append(y)
# 按x值排序,便于画图
points = sorted(zip(self.x_data, self.y_data))
self.x_data = [p[0] for p in points]
self.y_data = [p[1] for p in points]
self.plot_data()
self.update_info_label()
self.formula_label.config(text="")
if show_message:
messagebox.showinfo("成功", "已生成{}个新的随机数据点".format(num_points))
def input_data(self):
"""手动输入多组数据"""
# 创建自定义对话框,使文字更清晰
dialog = tk.Toplevel(self.root)
dialog.title("输入数据")
dialog.geometry("400x200")
dialog.resizable(False, False)
dialog.transient(self.root)
dialog.grab_set()
# 说明标签
tk.Label(dialog,
text="请输入数据点,格式为:x1,y1;x2,y2;x3,y3...",
font=("微软雅黑", 12)).pack(pady=10)
tk.Label(dialog,
text="例如:1,2;3,4;5,6",
font=("微软雅黑", 11)).pack(pady=5)
# 输入框
entry = tk.Entry(dialog, font=("微软雅黑", 12), width=35)
entry.pack(pady=10, padx=20)
entry.focus_set()
# 结果变量
result = [None]
# 确定和取消按钮
def on_ok():
result[0] = entry.get()
dialog.destroy()
def on_cancel():
dialog.destroy()
button_frame = tk.Frame(dialog)
button_frame.pack(pady=15)
tk.Button(button_frame,
text="确定",
font=("微软雅黑", 11, "bold"),
fg="#ff0000", # 红色文字
command=on_ok).pack(side=tk.LEFT, padx=20)
tk.Button(button_frame,
text="取消",
font=("微软雅黑", 11),
command=on_cancel).pack(side=tk.LEFT)
# 等待对话框关闭
self.root.wait_window(dialog)
# 处理输入数据
data_input = result[0]
if not data_input:
return
try:
# 清空现有数据
self.x_data = []
self.y_data = []
# 解析输入数据
point_pairs = data_input.strip().split(';')
for pair in point_pairs:
if not pair.strip():
continue
x_str, y_str = pair.split(',')
x = float(x_str.strip())
y = float(y_str.strip())
self.x_data.append(x)
self.y_data.append(y)
# 按x值排序
points = sorted(zip(self.x_data, self.y_data))
self.x_data = [p[0] for p in points]
self.y_data = [p[1] for p in points]
self.plot_data()
self.update_info_label()
self.formula_label.config(text="")
messagebox.showinfo("成功", "已输入{}个数据点".format(len(self.x_data)))
except Exception as e:
messagebox.showerror("错误", "数据格式错误: {}\n请使用正确格式: x1,y1;x2,y2;...".format(str(e)))
def clear_data(self):
"""清空所有数据"""
self.x_data = []
self.y_data = []
self.plot_data()
self.update_info_label()
self.formula_label.config(text="")
messagebox.showinfo("成功", "已清空所有数据")
def update_info_label(self):
"""更新信息标签"""
self.info_label.config(text="当前数据点: {}".format(len(self.x_data)))
def fit_data(self):
"""进行多项式拟合"""
if not self.x_data or not self.y_data:
messagebox.showerror("错误", "没有数据可拟合")
return
try:
# 获取用户选择的拟合阶数
degree = int(self.degree_var.get())
# 使用纯Python进行多项式拟合
coeffs = self.polyfit(self.x_data, self.y_data, degree)
# 绘制拟合曲线
self.plot_data()
# 获取画布尺寸
width = self.canvas.winfo_width()
height = self.canvas.winfo_height()
# 计算数据范围
x_min, x_max = min(self.x_data), max(self.x_data)
# 生成拟合曲线的点
x_fit = []
y_fit = []
num_points = 100
for i in range(num_points):
x = x_min + (x_max - x_min) * i / (num_points - 1)
y = self.polyval(coeffs, x)
x_fit.append(x)
y_fit.append(y)
# 绘制拟合曲线
points = []
for x, y in zip(x_fit, y_fit):
cx, cy = self.data_to_canvas(x, y, width, height)
points.append(cx)
points.append(cy)
if len(points) >= 4: # 至少需要两个点才能画线
self.canvas.create_line(points, fill=THEME_COLOR["line_color"], width=2, smooth=True, tags="fit_line")
# 构建拟合公式字符串
formula = self.get_formula_string(coeffs)
# 显示拟合公式
self.formula_label.config(text=formula)
# 在画布上也显示公式
self.canvas.create_text(width / 2, 30,
text=formula,
font=self.formula_font,
fill=THEME_COLOR["line_color"],
tags="formula")
messagebox.showinfo("成功", "拟合完成: " + formula)
except Exception as e:
messagebox.showerror("错误", "拟合过程中出现错误: {}".format(str(e)))
def polyfit(self, x_data, y_data, degree):
"""使用最小二乘法进行多项式拟合(不使用numpy)"""
# 对于一阶拟合(线性拟合),使用简单的线性回归
if degree == 1:
n = len(x_data)
sum_x = sum(x_data)
sum_y = sum(y_data)
sum_xx = sum(x*x for x in x_data)
sum_xy = sum(x*y for x, y in zip(x_data, y_data))
# 计算斜率和截距
a = (n * sum_xy - sum_x * sum_y) / (n * sum_xx - sum_x * sum_x)
b = (sum_y - a * sum_x) / n
return [a, b] # 返回系数 [a, b] 对应 ax + b
# 对于高阶拟合,使用矩阵方法
else:
# 构建范德蒙德矩阵
X = []
for x in x_data:
row = [x ** p for p in range(degree, -1, -1)]
X.append(row)
# 转置矩阵X
X_T = [[X[j][i] for j in range(len(X))] for i in range(len(X[0]))]
# 计算X^T * X
X_T_X = []
for i in range(len(X_T)):
row = []
for j in range(len(X_T)):
val = sum(X_T[i][k] * X_T[j][k] for k in range(len(X_T[0])))
row.append(val)
X_T_X.append(row)
# 计算X^T * y
X_T_y = []
for i in range(len(X_T)):
val = sum(X_T[i][j] * y_data[j] for j in range(len(y_data)))
X_T_y.append(val)
# 求解线性方程组 X^T * X * coeffs = X^T * y
coeffs = self.solve_linear_system(X_T_X, X_T_y)
return coeffs
def solve_linear_system(self, A, b):
"""使用高斯消元法求解线性方程组"""
n = len(A)
# 创建增广矩阵
augmented = [row[:] + [b[i]] for i, row in enumerate(A)]
# 前向消元
for i in range(n):
# 寻找主元
max_row = i
for j in range(i + 1, n):
if abs(augmented[j][i]) > abs(augmented[max_row][i]):
max_row = j
# 交换行
augmented[i], augmented[max_row] = augmented[max_row], augmented[i]
# 消元
for j in range(i + 1, n):
factor = augmented[j][i] / augmented[i][i]
for k in range(i, n + 1):
augmented[j][k] -= factor * augmented[i][k]
# 回代求解
x = [0] * n
for i in range(n - 1, -1, -1):
x[i] = augmented[i][n]
for j in range(i + 1, n):
x[i] -= augmented[i][j] * x[j]
x[i] /= augmented[i][i]
return x
def polyval(self, coeffs, x):
"""计算多项式在x点的值(不使用numpy)"""
result = 0
for i, coeff in enumerate(coeffs):
power = len(coeffs) - 1 - i
result += coeff * (x ** power)
return result
def get_formula_string(self, coeffs):
"""根据系数生成公式字符串"""
degree = len(coeffs) - 1
terms = []
for i, coeff in enumerate(coeffs):
power = degree - i
if power == 0:
# 常数项
terms.append("{:.2f}".format(coeff))
elif power == 1:
# 一次项
terms.append("{:.2f}x".format(coeff))
else:
# 高次项
terms.append("{:.2f}x^{}".format(coeff, power))
# 组合所有项
formula = "y = " + " + ".join(terms)
# 替换"+ -"为"-"
formula = formula.replace("+ -", "- ")
return formula
def plot_data(self):
"""绘制数据点"""
self.canvas.delete("all")
if not self.x_data or not self.y_data:
return
# 获取画布尺寸
width = self.canvas.winfo_width()
height = self.canvas.winfo_height()
# 计算数据范围
x_min, x_max = min(self.x_data), max(self.x_data)
y_min, y_max = min(self.y_data), max(self.y_data)
# 添加边距
margin = (y_max - y_min) * 0.1
y_min -= margin
y_max += margin
x_margin = (x_max - x_min) * 0.1
x_min -= x_margin
x_max += x_margin
# 计算规则的刻度间隔
def calculate_nice_interval(min_val, max_val, target_steps=5):
range_val = max_val - min_val
if range_val == 0:
return 1 # 防止除零错误
# 计算粗略的步长
rough_step = range_val / target_steps
# 寻找合适的步长(1, 2, 5, 10, 20, 50, ...的倍数)
magnitude = 10 ** math.floor(math.log10(rough_step))
possible_steps = [magnitude, 2*magnitude, 5*magnitude, 10*magnitude]
# 选择最接近目标步数的步长
step = min(possible_steps, key=lambda x: abs(range_val/x - target_steps))
return step
# 计算规则的X轴和Y轴刻度
x_step = calculate_nice_interval(x_min, x_max)
y_step = calculate_nice_interval(y_min, y_max)
# 计算起始刻度(向下取整到最接近的刻度倍数)
x_start = math.floor(x_min / x_step) * x_step
y_start = math.floor(y_min / y_step) * y_step
# 画坐标轴
self.canvas.create_line(50, height - 50, width - 50, height - 50,
arrow=tk.LAST, width=3, # 增加线宽
fill=THEME_COLOR["axis_color"]) # X轴
self.canvas.create_line(50, height - 50, 50, 50,
arrow=tk.LAST, width=3, # 增加线宽
fill=THEME_COLOR["axis_color"]) # Y轴
# 添加坐标轴标签
self.canvas.create_text(width - 35, height - 30,
text="X",
font=("微软雅黑", 12, "bold"), # 增大字体
fill=THEME_COLOR["text_color"])
self.canvas.create_text(30, 50,
text="Y",
font=("微软雅黑", 12, "bold"), # 增大字体
fill=THEME_COLOR["text_color"])
# 添加X轴刻度和数字标识(使用规则间隔)
x = x_start
while x <= x_max:
if x >= x_min: # 只显示范围内的刻度
# 计算画布坐标
x_pos = 50 + (x - x_min) / (x_max - x_min) * (width - 100)
# 绘制刻度线
self.canvas.create_line(x_pos, height - 50, x_pos, height - 45,
width=1, fill=THEME_COLOR["axis_color"])
# 绘制数字标识(根据数值大小选择格式)
if abs(x) < 0.1 or abs(x) >= 100:
format_str = "{:.1e}" # 科学计数法
elif abs(x) >= 10:
format_str = "{:.0f}" # 整数
else:
format_str = "{:.1f}" # 一位小数
self.canvas.create_text(x_pos, height - 30,
text=format_str.format(x),
font=self.axis_font,
fill=THEME_COLOR["axis_label"])
x += x_step
# 添加Y轴刻度和数字标识(使用规则间隔)
y = y_start
while y <= y_max:
if y >= y_min: # 只显示范围内的刻度
# 计算画布坐标
y_pos = height - 50 - (y - y_min) / (y_max - y_min) * (height - 100)
# 绘制刻度线
self.canvas.create_line(50, y_pos, 45, y_pos,
width=1, fill=THEME_COLOR["axis_color"])
# 绘制数字标识(根据数值大小选择格式)
if abs(y) < 0.1 or abs(y) >= 100:
format_str = "{:.1e}" # 科学计数法
elif abs(y) >= 10:
format_str = "{:.0f}" # 整数
else:
format_str = "{:.1f}" # 一位小数
self.canvas.create_text(30, y_pos,
text=format_str.format(y),
font=self.axis_font,
fill=THEME_COLOR["axis_label"])
y += y_step
# 绘制网格线(淡色)
self.draw_grid(width, height, x_min, x_max, y_min, y_max, x_step, y_step)
# 绘制数据点
for x, y in zip(self.x_data, self.y_data):
cx, cy = self.data_to_canvas(x, y, width, height)
# 绘制点外圈
self.canvas.create_oval(cx-5, cy-5, cx+5, cy+5,
outline=THEME_COLOR["point_color"],
width=2)
# 绘制点内圈
self.canvas.create_oval(cx-3, cy-3, cx+3, cy+3,
fill=THEME_COLOR["point_color"],
outline="")
def draw_grid(self, width, height, x_min, x_max, y_min, y_max, x_step, y_step):
"""绘制网格线"""
# 绘制基于刻度的水平网格线
y = y_step * math.ceil(y_min / y_step)
while y <= y_max:
y_pos = height - 50 - (y - y_min) / (y_max - y_min) * (height - 100)
self.canvas.create_line(50, y_pos, width - 50, y_pos,
fill=THEME_COLOR["grid_color"],
dash=(2, 4))
y += y_step
# 绘制基于刻度的垂直网格线
x = x_step * math.ceil(x_min / x_step)
while x <= x_max:
x_pos = 50 + (x - x_min) / (x_max - x_min) * (width - 100)
self.canvas.create_line(x_pos, height - 50, x_pos, 50,
fill=THEME_COLOR["grid_color"],
dash=(2, 4))
x += x_step
def data_to_canvas(self, x, y, width, height):
"""将数据坐标转换为画布坐标"""
if not self.x_data or not self.y_data:
return 50, height - 50
x_min, x_max = min(self.x_data), max(self.x_data)
y_min, y_max = min(self.y_data), max(self.y_data)
# 添加边距
margin = (y_max - y_min) * 0.1
y_min -= margin
y_max += margin
x_margin = (x_max - x_min) * 0.1
x_min -= x_margin
x_max += x_margin
# 转换坐标
canvas_x = 50 + (x - x_min) / (x_max - x_min) * (width - 100)
canvas_y = height - 50 - (y - y_min) / (y_max - y_min) * (height - 100)
return canvas_x, canvas_y
def main():
root = tk.Tk()
app = EnhancedApp(root)
root.mainloop()
if name == "main":
main()