// 函数拟合工具 - 主脚本文件 // 页面加载完成后执行初始化 document.addEventListener('DOMContentLoaded', function() { // 初始化标签页切换功能 initTabs(); // 初始化数据表格 initDataTable(); // 初始化函数绘制功能 initFunctionPlot(); // 初始化表格排序功能 initTableSorting(); // 初始化数据导入导出功能 initDataImportExport(); // 初始化数据拟合功能 // 将在下一部分实现 // 初始化回归分析功能 // 将在后续部分实现 }); /** * 初始化标签页切换功能 */ function initTabs() { const tabButtons = document.querySelectorAll('.tab-btn'); const tabPanes = document.querySelectorAll('.tab-pane'); tabButtons.forEach(button => { button.addEventListener('click', function() { // 移除所有标签页的激活状态 tabButtons.forEach(btn => btn.classList.remove('active')); tabPanes.forEach(pane => pane.classList.remove('active')); // 激活当前点击的标签页 this.classList.add('active'); const tabId = this.getAttribute('data-tab'); document.getElementById(tabId).classList.add('active'); }); }); } /** * 初始化数据表格功能 */ function initDataTable() { // 获取表格元素 const dataTable = document.getElementById('data-table'); const tableBody = dataTable.querySelector('tbody') || document.createElement('tbody'); // 如果表格没有tbody,则添加一个 if (!dataTable.querySelector('tbody')) { dataTable.appendChild(tableBody); } // 绑定添加行按钮事件 const addRowBtn = document.getElementById('add-row'); if (addRowBtn) { addRowBtn.addEventListener('click', function() { addTableRow(tableBody); updateRowNumbers(); }); } // 使用事件委托绑定删除按钮事件 // 这样可以处理所有现有和将来添加的删除按钮 dataTable.addEventListener('click', function(event) { const target = event.target; if (target.classList.contains('remove-row')) { const row = target.closest('tr'); if (row && tableBody.contains(row)) { tableBody.removeChild(row); updateRowNumbers(); } } }); } /** * 添加表格行 * @param {HTMLElement} tableBody - 表格体元素 */ function addTableRow(tableBody) { const newRow = document.createElement('tr'); // 创建序号单元格 const indexCell = document.createElement('td'); indexCell.className = 'row-index'; indexCell.textContent = tableBody.children.length + 1; // 创建X输入单元格 const xCell = document.createElement('td'); const xInput = document.createElement('input'); xInput.type = 'number'; xInput.step = 'any'; xInput.className = 'x-value'; xCell.appendChild(xInput); // 创建Y输入单元格 const yCell = document.createElement('td'); const yInput = document.createElement('input'); yInput.type = 'number'; yInput.step = 'any'; yInput.className = 'y-value'; yCell.appendChild(yInput); // 创建操作单元格(删除按钮) const actionCell = document.createElement('td'); const removeBtn = document.createElement('button'); removeBtn.textContent = '删除'; removeBtn.className = 'remove-row'; removeBtn.addEventListener('click', function() { tableBody.removeChild(newRow); updateRowNumbers(); }); actionCell.appendChild(removeBtn); // 将单元格添加到行 newRow.appendChild(indexCell); newRow.appendChild(xCell); newRow.appendChild(yCell); newRow.appendChild(actionCell); // 将行添加到表格 tableBody.appendChild(newRow); } /** * 初始化函数绘制功能 */ function initFunctionPlot() { // 获取绘制按钮和输入元素 const plotButton = document.getElementById('plot-function'); const functionInput = document.getElementById('function-input'); const xMinInput = document.getElementById('x-min'); const xMaxInput = document.getElementById('x-max'); // 添加绘制按钮点击事件 plotButton.addEventListener('click', function() { const functionExpr = functionInput.value; const xMin = parseFloat(xMinInput.value); const xMax = parseFloat(xMaxInput.value); // 检查输入是否有效 if (!functionExpr) { alert('请输入有效的函数表达式'); return; } if (isNaN(xMin) || isNaN(xMax) || xMin >= xMax) { alert('请输入有效的X范围,且最小值应小于最大值'); return; } // 绘制函数 plotFunction(functionExpr, xMin, xMax); }); } /** * 绘制函数 * @param {string} functionExpr - 函数表达式 * @param {number} xMin - X轴最小值 * @param {number} xMax - X轴最大值 */ function plotFunction(functionExpr, xMin, xMax) { try { // 编译函数表达式 const compiledFunction = math.compile(functionExpr); // 生成X值数组 const step = (xMax - xMin) / 500; const xValues = []; const yValues = []; // 计算对应的Y值 for (let x = xMin; x <= xMax; x += step) { try { const y = compiledFunction.evaluate({x: x}); if (!isNaN(y) && isFinite(y)) { xValues.push(x); yValues.push(y); } } catch (error) { // 跳过计算错误的点 continue; } } // 创建绘图数据 const trace = { x: xValues, y: yValues, mode: 'lines', type: 'scatter', name: functionExpr, line: { color: '#3498db', width: 2 } }; // 绘图布局 const layout = { title: '函数图像', xaxis: { title: 'X', zeroline: true, zerolinecolor: '#999', gridcolor: '#eee' }, yaxis: { title: 'Y', zeroline: true, zerolinecolor: '#999', gridcolor: '#eee' }, plot_bgcolor: '#fff', paper_bgcolor: '#fff', margin: { t: 50, b: 50, l: 50, r: 30 } }; // 绘制图表 Plotly.newPlot('plot-area', [trace], layout); // 使用MathJax显示函数表达式 const equationResult = document.getElementById('equation-result'); // 将函数表达式转换为LaTeX格式 const latexExpr = convertToLatex(functionExpr); equationResult.innerHTML = '$$y = ' + latexExpr + '$$'; // 重新渲染MathJax if (window.MathJax) { MathJax.typesetPromise([equationResult]).catch(function (err) { console.error('MathJax渲染错误:', err); }); } } catch (error) { alert('函数绘制错误: ' + error.message); } } /** * 更新表格行序号 */ function updateRowNumbers() { const tableBody = document.querySelector('#data-table tbody'); const rows = tableBody.querySelectorAll('tr'); rows.forEach((row, index) => { const indexCell = row.querySelector('.row-index'); if (indexCell) { indexCell.textContent = index + 1; } }); } /** * 初始化表格排序功能 */ function initTableSorting() { const sortableHeaders = document.querySelectorAll('.professional-table th.sortable'); sortableHeaders.forEach(header => { header.addEventListener('click', function() { const table = this.closest('table'); const tbody = table.querySelector('tbody'); const rows = Array.from(tbody.querySelectorAll('tr')); const columnIndex = Array.from(this.parentNode.children).indexOf(this); const isAsc = !this.classList.contains('sorted-asc'); // 移除所有排序标记 sortableHeaders.forEach(h => { h.classList.remove('sorted-asc', 'sorted-desc'); }); // 添加当前排序标记 this.classList.add(isAsc ? 'sorted-asc' : 'sorted-desc'); // 排序行 rows.sort((a, b) => { let aValue, bValue; if (columnIndex === 0) { // 序号列 aValue = parseInt(a.cells[columnIndex].textContent); bValue = parseInt(b.cells[columnIndex].textContent); } else if (columnIndex === 1 || columnIndex === 2) { // X或Y列 const aInput = a.cells[columnIndex].querySelector('input'); const bInput = b.cells[columnIndex].querySelector('input'); aValue = aInput ? parseFloat(aInput.value) : 0; bValue = bInput ? parseFloat(bInput.value) : 0; if (isNaN(aValue)) aValue = 0; if (isNaN(bValue)) bValue = 0; } return isAsc ? aValue - bValue : bValue - aValue; }); // 重新添加排序后的行 rows.forEach(row => tbody.appendChild(row)); // 更新行序号 updateRowNumbers(); }); }); } /** * 初始化数据导入导出功能 */ function initDataImportExport() { const importBtn = document.getElementById('import-data'); const exportBtn = document.getElementById('export-data'); if (importBtn) { importBtn.addEventListener('click', function() { const input = document.createElement('input'); input.type = 'file'; input.accept = '.csv,.txt'; input.addEventListener('change', function(e) { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = function(e) { const content = e.target.result; importDataFromCSV(content); }; reader.readAsText(file); }); input.click(); }); } if (exportBtn) { exportBtn.addEventListener('click', function() { exportDataToCSV(); }); } } /** * 从CSV导入数据 */ function importDataFromCSV(csvContent) { const lines = csvContent.split('\n'); const dataPoints = []; // 跳过可能的标题行 for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (!line) continue; const values = line.split(','); if (values.length >= 2) { const x = parseFloat(values[0]); const y = parseFloat(values[1]); if (!isNaN(x) && !isNaN(y)) { dataPoints.push({ x, y }); } } } if (dataPoints.length === 0) { alert('没有找到有效的数据点'); return; } // 清空现有表格 const tableBody = document.querySelector('#data-table tbody'); tableBody.innerHTML = ''; // 添加导入的数据点 dataPoints.forEach(point => { const newRow = document.createElement('tr'); // 序号单元格 const indexCell = document.createElement('td'); indexCell.className = 'row-index'; indexCell.textContent = tableBody.children.length + 1; // X值单元格 const xCell = document.createElement('td'); const xInput = document.createElement('input'); xInput.type = 'number'; xInput.step = 'any'; xInput.className = 'x-value'; xInput.value = point.x; xCell.appendChild(xInput); // Y值单元格 const yCell = document.createElement('td'); const yInput = document.createElement('input'); yInput.type = 'number'; yInput.step = 'any'; yInput.className = 'y-value'; yInput.value = point.y; yCell.appendChild(yInput); // 操作单元格 const actionCell = document.createElement('td'); const removeBtn = document.createElement('button'); removeBtn.textContent = '删除'; removeBtn.className = 'remove-row'; removeBtn.addEventListener('click', function() { tableBody.removeChild(newRow); updateRowNumbers(); }); actionCell.appendChild(removeBtn); // 添加单元格到行 newRow.appendChild(indexCell); newRow.appendChild(xCell); newRow.appendChild(yCell); newRow.appendChild(actionCell); // 添加行到表格 tableBody.appendChild(newRow); }); alert(`成功导入 ${dataPoints.length} 个数据点`); } /** * 导出数据到CSV */ function exportDataToCSV() { const dataPoints = getDataPointsFromTable(); if (dataPoints.length === 0) { alert('没有数据可导出'); return; } let csvContent = 'X,Y\n'; dataPoints.forEach(point => { csvContent += `${point.x},${point.y}\n`; }); const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.setAttribute('download', '数据点.csv'); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); document.body.removeChild(link); } /** * 将函数表达式转换为LaTeX格式 */ function convertToLatex(expr) { // 基本替换 let latex = expr .replace(/\*/g, ' \\cdot ') .replace(/\//g, ' \\div ') .replace(/\^(\d+)/g, '^{$1}') .replace(/\^([a-zA-Z])/g, '^{$1}') .replace(/sqrt\(([^)]+)\)/g, '\\sqrt{$1}') .replace(/sin\(([^)]+)\)/g, '\\sin{($1)}') .replace(/cos\(([^)]+)\)/g, '\\cos{($1)}') .replace(/tan\(([^)]+)\)/g, '\\tan{($1)}') .replace(/log\(([^)]+)\)/g, '\\log{($1)}') .replace(/ln\(([^)]+)\)/g, '\\ln{($1)}') .replace(/exp\(([^)]+)\)/g, 'e^{$1}'); // 处理分数 latex = latex.replace(/(\d+)\/(\d+)/g, '\\frac{$1}{$2}'); return latex; }