| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485 |
- // 函数拟合工具 - 主脚本文件
- // 页面加载完成后执行初始化
- 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;
- }
|