// 函数拟合工具 - 数据拟合模块 // 页面加载完成后执行初始化 document.addEventListener('DOMContentLoaded', function() { // 初始化数据拟合功能 initDataFitting(); // 初始化多项式次数控制 const regressionType = document.getElementById('regression-type'); const polynomialDegreeContainer = document.getElementById('polynomial-degree-container'); regressionType.addEventListener('change', function() { if (this.value === 'polynomial') { polynomialDegreeContainer.style.display = 'inline-block'; } else { polynomialDegreeContainer.style.display = 'none'; } }); }); /** * 初始化数据拟合功能 */ function initDataFitting() { const fitButton = document.getElementById('fit-data'); const fitType = document.getElementById('fit-type'); fitButton.addEventListener('click', function() { // 获取数据点 const fitTable = document.getElementById('fit-excel-table'); const dataPoints = window.dataTable.getDataPointsFromExcelTable(fitTable); // 检查数据点是否足够 if (dataPoints.length < 2) { alert('请至少输入两个有效的数据点'); return; } // 根据选择的函数类型进行拟合 const fitResult = fitData(dataPoints, fitType.value); // 显示拟合结果 displayFitResult(fitResult, dataPoints); }); } // 使用utils.js中的getDataPointsFromTable函数 /** * 拟合数据 * @param {Array} dataPoints - 数据点数组 * @param {string} fitType - 拟合函数类型 * @returns {Object} 拟合结果 */ function fitData(dataPoints, fitType) { // 提取x和y值数组 const xValues = dataPoints.map(point => point.x); const yValues = dataPoints.map(point => point.y); let result = { fitType: fitType, equation: '', parameters: [], r2: 0, functionExpr: '' }; try { switch (fitType) { case 'linear': result = fitLinear(xValues, yValues); break; case 'quadratic': result = fitPolynomial(xValues, yValues, 2); break; case 'cubic': result = fitPolynomial(xValues, yValues, 3); break; case 'exponential': result = fitExponential(xValues, yValues); break; case 'logarithmic': result = fitLogarithmic(xValues, yValues); break; case 'power': result = fitPower(xValues, yValues); break; default: throw new Error('不支持的拟合类型'); } return result; } catch (error) { alert('拟合计算错误: ' + error.message); return null; } } /** * 线性拟合 (y = ax + b) * @param {Array} xValues - x值数组 * @param {Array} yValues - y值数组 * @returns {Object} 拟合结果 */ function fitLinear(xValues, yValues) { // 使用最小二乘法计算线性回归参数 const n = xValues.length; // 计算各项和 let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0; for (let i = 0; i < n; i++) { sumX += xValues[i]; sumY += yValues[i]; sumXY += xValues[i] * yValues[i]; sumX2 += xValues[i] * xValues[i]; } // 计算斜率和截距 const a = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX); const b = (sumY - a * sumX) / n; // 计算拟合优度 R² const r2 = calculateRSquared(xValues, yValues, x => a * x + b); // 格式化方程 const equation = `y = ${a.toFixed(4)}x ${b >= 0 ? '+ ' + b.toFixed(4) : '- ' + Math.abs(b).toFixed(4)}`; const functionExpr = `${a}*x ${b >= 0 ? '+ ' + b : '- ' + Math.abs(b)}`; return { fitType: 'linear', equation: equation, parameters: [a, b], r2: r2, functionExpr: functionExpr }; } /** * 多项式拟合 * @param {Array} xValues - x值数组 * @param {Array} yValues - y值数组 * @param {number} degree - 多项式次数 * @returns {Object} 拟合结果 */ function fitPolynomial(xValues, yValues, degree) { // 使用math.js的多项式回归 const coeffs = polyfit(xValues, yValues, degree); // 构建方程字符串 let equation = 'y = '; let functionExpr = ''; for (let i = 0; i <= degree; i++) { const power = degree - i; const coeff = coeffs[i]; if (i > 0 && coeff >= 0) { equation += ' + '; functionExpr += ' + '; } else if (i > 0 && coeff < 0) { equation += ' - '; functionExpr += ' - '; } if (power === 0) { equation += `${Math.abs(coeff).toFixed(4)}`; functionExpr += `${Math.abs(coeff)}`; } else if (power === 1) { equation += `${Math.abs(coeff).toFixed(4)}x`; functionExpr += `${Math.abs(coeff)}*x`; } else { equation += `${Math.abs(coeff).toFixed(4)}x^${power}`; functionExpr += `${Math.abs(coeff)}*x^${power}`; } } // 计算拟合优度 R² const r2 = calculateRSquared(xValues, yValues, x => { let result = 0; for (let i = 0; i <= degree; i++) { result += coeffs[i] * Math.pow(x, degree - i); } return result; }); // 计算RMSE const rmse = calculateRMSE(xValues, yValues, x => { let result = 0; for (let i = 0; i <= degree; i++) { result += coeffs[i] * Math.pow(x, degree - i); } return result; }); return { fitType: degree === 2 ? 'quadratic' : 'cubic', equation: equation, parameters: coeffs, r2: r2, rmse: rmse, functionExpr: functionExpr }; } /** * 指数拟合 (y = a*e^(bx)) * @param {Array} xValues - x值数组 * @param {Array} yValues - y值数组 * @returns {Object} 拟合结果 */ function fitExponential(xValues, yValues) { // 检查y值是否都为正 for (let i = 0; i < yValues.length; i++) { if (yValues[i] <= 0) { throw new Error('指数拟合要求所有y值必须为正数'); } } // 对y取对数,转换为线性问题: ln(y) = ln(a) + bx const lnY = yValues.map(y => Math.log(y)); // 使用线性拟合 const linearFit = fitLinear(xValues, lnY); // 转换回指数参数 const a = Math.exp(linearFit.parameters[1]); const b = linearFit.parameters[0]; // 计算拟合优度 R² const r2 = calculateRSquared(xValues, yValues, x => a * Math.exp(b * x)); // 格式化方程 const equation = `y = ${a.toFixed(4)}e^(${b.toFixed(4)}x)`; const functionExpr = `${a}*e^(${b}*x)`; return { fitType: 'exponential', equation: equation, parameters: [a, b], r2: r2, functionExpr: functionExpr }; } /** * 对数拟合 (y = a*ln(x) + b) * @param {Array} xValues - x值数组 * @param {Array} yValues - y值数组 * @returns {Object} 拟合结果 */ function fitLogarithmic(xValues, yValues) { // 检查x值是否都为正 for (let i = 0; i < xValues.length; i++) { if (xValues[i] <= 0) { throw new Error('对数拟合要求所有x值必须为正数'); } } // 对x取对数,转换为线性问题: y = a*ln(x) + b const lnX = xValues.map(x => Math.log(x)); // 使用线性拟合 const linearFit = fitLinear(lnX, yValues); // 获取参数 const a = linearFit.parameters[0]; const b = linearFit.parameters[1]; // 计算拟合优度 R² const r2 = calculateRSquared(xValues, yValues, x => a * Math.log(x) + b); // 格式化方程 const equation = `y = ${a.toFixed(4)}ln(x) ${b >= 0 ? '+ ' + b.toFixed(4) : '- ' + Math.abs(b).toFixed(4)}`; const functionExpr = `${a}*ln(x) ${b >= 0 ? '+ ' + b : '- ' + Math.abs(b)}`; return { fitType: 'logarithmic', equation: equation, parameters: [a, b], r2: r2, functionExpr: functionExpr }; } /** * 幂函数拟合 (y = a*x^b) * @param {Array} xValues - x值数组 * @param {Array} yValues - y值数组 * @returns {Object} 拟合结果 */ function fitPower(xValues, yValues) { // 检查x和y值是否都为正 for (let i = 0; i < xValues.length; i++) { if (xValues[i] <= 0 || yValues[i] <= 0) { throw new Error('幂函数拟合要求所有x和y值必须为正数'); } } // 对x和y取对数,转换为线性问题: ln(y) = ln(a) + b*ln(x) const lnX = xValues.map(x => Math.log(x)); const lnY = yValues.map(y => Math.log(y)); // 使用线性拟合 const linearFit = fitLinear(lnX, lnY); // 转换回幂函数参数 const a = Math.exp(linearFit.parameters[1]); const b = linearFit.parameters[0]; // 计算拟合优度 R² const r2 = calculateRSquared(xValues, yValues, x => a * Math.pow(x, b)); // 格式化方程 const equation = `y = ${a.toFixed(4)}x^${b.toFixed(4)}`; const functionExpr = `${a}*x^${b}`; return { fitType: 'power', equation: equation, parameters: [a, b], r2: r2, functionExpr: functionExpr }; } // 使用utils.js中的calculateRSquared和calculateRMSE函数 /** * 显示拟合结果 * @param {Object} fitResult - 拟合结果 * @param {Array} dataPoints - 数据点 */ function displayFitResult(fitResult, dataPoints) { if (!fitResult) return; // 显示拟合方程(使用MathJax渲染) const equationResult = document.getElementById('equation-result'); // 根据拟合类型生成LaTeX格式的方程 let latexEquation = ''; switch(fitResult.fitType) { case 'linear': latexEquation = `y = ${formatNumber(fitResult.parameters[0])}x ${fitResult.parameters[1] >= 0 ? '+ ' + formatNumber(fitResult.parameters[1]) : '- ' + formatNumber(Math.abs(fitResult.parameters[1]))}`; break; case 'quadratic': latexEquation = `y = ${formatNumber(fitResult.parameters[0])}x^2 ${fitResult.parameters[1] >= 0 ? '+ ' + formatNumber(fitResult.parameters[1]) : '- ' + formatNumber(Math.abs(fitResult.parameters[1]))}x ${fitResult.parameters[2] >= 0 ? '+ ' + formatNumber(fitResult.parameters[2]) : '- ' + formatNumber(Math.abs(fitResult.parameters[2]))}`; break; case 'cubic': latexEquation = `y = ${formatNumber(fitResult.parameters[0])}x^3 ${fitResult.parameters[1] >= 0 ? '+ ' + formatNumber(fitResult.parameters[1]) : '- ' + formatNumber(Math.abs(fitResult.parameters[1]))}x^2 ${fitResult.parameters[2] >= 0 ? '+ ' + formatNumber(fitResult.parameters[2]) : '- ' + formatNumber(Math.abs(fitResult.parameters[2]))}x ${fitResult.parameters[3] >= 0 ? '+ ' + formatNumber(fitResult.parameters[3]) : '- ' + formatNumber(Math.abs(fitResult.parameters[3]))}`; break; case 'exponential': latexEquation = `y = ${formatNumber(fitResult.parameters[0])}e^{${formatNumber(fitResult.parameters[1])}x}`; break; case 'logarithmic': latexEquation = `y = ${formatNumber(fitResult.parameters[0])}\\ln(x) ${fitResult.parameters[1] >= 0 ? '+ ' + formatNumber(fitResult.parameters[1]) : '- ' + formatNumber(Math.abs(fitResult.parameters[1]))}`; break; case 'power': latexEquation = `y = ${formatNumber(fitResult.parameters[0])}x^{${formatNumber(fitResult.parameters[1])}}`; break; default: latexEquation = fitResult.equation; } // 使用MathJax渲染方程 equationResult.innerHTML = `\\[${latexEquation}\\]`; if (window.MathJax) { MathJax.typeset([equationResult]); } // 使用generateStatsHTML生成统计信息 const statsResult = document.getElementById('stats-result'); // 计算RMSE(如果fitResult中没有) let rmse = 0; if (fitResult.rmse) { rmse = fitResult.rmse; } else { // 提取x和y值数组 const xValues = dataPoints.map(point => point.x); const yValues = dataPoints.map(point => point.y); // 根据拟合类型创建预测函数 let predictFn; switch(fitResult.fitType) { case 'linear': predictFn = x => fitResult.parameters[0] * x + fitResult.parameters[1]; break; case 'quadratic': predictFn = x => fitResult.parameters[0] * x * x + fitResult.parameters[1] * x + fitResult.parameters[2]; break; case 'cubic': predictFn = x => fitResult.parameters[0] * x * x * x + fitResult.parameters[1] * x * x + fitResult.parameters[2] * x + fitResult.parameters[3]; break; case 'exponential': predictFn = x => fitResult.parameters[0] * Math.exp(fitResult.parameters[1] * x); break; case 'logarithmic': predictFn = x => fitResult.parameters[0] * Math.log(x) + fitResult.parameters[1]; break; case 'power': predictFn = x => fitResult.parameters[0] * Math.pow(x, fitResult.parameters[1]); break; default: predictFn = x => 0; } rmse = calculateRMSE(xValues, yValues, predictFn); } // 生成统计HTML statsResult.innerHTML = generateStatsHTML({ coefficients: fitResult.parameters, rSquared: fitResult.r2, rmse: rmse, formula: fitResult.fitType, dataPoints: dataPoints.length }); // 绘制拟合曲线和数据点 plotFitResult(fitResult, dataPoints); } /** * 绘制拟合结果 * @param {Object} fitResult - 拟合结果 * @param {Array} dataPoints - 数据点 */ function plotFitResult(fitResult, dataPoints) { // 提取数据点的x和y值 const xData = dataPoints.map(point => point.x); const yData = dataPoints.map(point => point.y); // 找出x的最小值和最大值 const xMin = Math.min(...xData); const xMax = Math.max(...xData); // 为了平滑曲线,生成更多的点 const xRange = xMax - xMin; const xStart = xMin - xRange * 0.1; const xEnd = xMax + xRange * 0.1; const step = xRange / 100; // 生成拟合曲线的点 const xFit = []; const yFit = []; try { // 编译拟合函数表达式 const compiledFunction = math.compile(fitResult.functionExpr); for (let x = xStart; x <= xEnd; x += step) { try { // 跳过对数和幂函数的负值 if ((fitResult.fitType === 'logarithmic' || fitResult.fitType === 'power') && x <= 0) { continue; } const y = compiledFunction.evaluate({x: x}); if (!isNaN(y) && isFinite(y)) { xFit.push(x); yFit.push(y); } } catch (error) { continue; } } // 创建数据点散点图 const dataTrace = { x: xData, y: yData, mode: 'markers', type: 'scatter', name: '数据点', marker: { size: 8, color: 'rgba(255, 0, 0, 0.7)' } }; // 创建拟合曲线 const fitTrace = { x: xFit, y: yFit, mode: 'lines', type: 'scatter', name: '拟合曲线', line: { color: 'blue', width: 2 } }; // 绘图布局 const layout = { title: '数据拟合结果', xaxis: { title: 'X' }, yaxis: { title: 'Y' }, legend: { x: 0, y: 1 } }; // 绘制图表 Plotly.newPlot('plot-area', [dataTrace, fitTrace], layout); } catch (error) { console.error('绘制拟合结果错误:', error); alert('绘制拟合结果错误: ' + error.message); } } /** * 多项式拟合函数 * @param {Array} x - x值数组 * @param {Array} y - y值数组 * @param {number} degree - 多项式次数 * @returns {Array} 多项式系数数组 */ function polyfit(x, y, degree) { // 构建范德蒙德矩阵 const X = []; const n = x.length; for (let i = 0; i < n; i++) { X[i] = []; for (let j = 0; j <= degree; j++) { X[i][j] = Math.pow(x[i], degree - j); } } // 使用math.js的最小二乘法求解 try { // 转换为math.js矩阵 const Xmatrix = math.matrix(X); const Ymatrix = math.matrix(y); // 计算 (X^T * X)^-1 * X^T * Y const Xt = math.transpose(Xmatrix); const XtX = math.multiply(Xt, Xmatrix); const XtXinv = math.inv(XtX); const XtY = math.multiply(Xt, Ymatrix); const coeffs = math.multiply(XtXinv, XtY); // 转换回普通数组 return Array.from(coeffs.valueOf()); } catch (error) { console.error('多项式拟合计算错误:', error); throw new Error('多项式拟合计算失败'); } }