| | <!DOCTYPE html> |
| | <html lang="en"> |
| | <head> |
| | <meta charset="UTF-8"> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | <title>AutoFS Leaderboard</title> |
| | <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> |
| | <style> |
| | :root { |
| | --primary-color: #3498db; |
| | --secondary-color: #2c3e50; |
| | --background-color: #f8f9fa; |
| | --text-color: #333; |
| | --border-color: #dee2e6; |
| | --hover-color: #f1f1f1; |
| | } |
| | |
| | body { |
| | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
| | margin: 0; |
| | padding: 20px; |
| | background-color: var(--background-color); |
| | color: var(--text-color); |
| | } |
| | |
| | .container { |
| | max-width: 1200px; |
| | margin: 0 auto; |
| | background: white; |
| | padding: 20px; |
| | border-radius: 8px; |
| | box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
| | } |
| | |
| | header { |
| | display: flex; |
| | justify-content: space-between; |
| | align-items: center; |
| | margin-bottom: 20px; |
| | border-bottom: 2px solid var(--primary-color); |
| | padding-bottom: 10px; |
| | } |
| | |
| | h1 { |
| | margin: 0; |
| | color: var(--secondary-color); |
| | } |
| | |
| | .controls { |
| | display: flex; |
| | gap: 10px; |
| | align-items: center; |
| | } |
| | |
| | select { |
| | padding: 8px 12px; |
| | border: 1px solid var(--border-color); |
| | border-radius: 4px; |
| | font-size: 14px; |
| | } |
| | |
| | table { |
| | width: 100%; |
| | border-collapse: collapse; |
| | margin-top: 10px; |
| | } |
| | |
| | th, td { |
| | padding: 12px 15px; |
| | text-align: left; |
| | border-bottom: 1px solid var(--border-color); |
| | } |
| | |
| | th { |
| | background-color: var(--secondary-color); |
| | color: white; |
| | cursor: pointer; |
| | user-select: none; |
| | position: sticky; |
| | top: 0; |
| | } |
| | |
| | th:hover { |
| | background-color: #34495e; |
| | } |
| | |
| | th .arrow { |
| | font-size: 10px; |
| | margin-left: 5px; |
| | opacity: 0.7; |
| | } |
| | |
| | tr:hover { |
| | background-color: var(--hover-color); |
| | } |
| | |
| | .score-bar { |
| | height: 6px; |
| | background-color: #e9ecef; |
| | border-radius: 3px; |
| | overflow: hidden; |
| | margin-top: 5px; |
| | } |
| | |
| | .score-fill { |
| | height: 100%; |
| | background-color: var(--primary-color); |
| | } |
| | |
| | .features-cell { |
| | max-width: 200px; |
| | white-space: nowrap; |
| | overflow: hidden; |
| | text-overflow: ellipsis; |
| | color: #666; |
| | font-size: 0.9em; |
| | cursor: pointer; |
| | } |
| | |
| | .features-cell:hover { |
| | text-decoration: underline; |
| | color: var(--primary-color); |
| | } |
| | |
| | |
| | .modal { |
| | display: none; |
| | position: fixed; |
| | z-index: 1000; |
| | left: 0; |
| | top: 0; |
| | width: 100%; |
| | height: 100%; |
| | background-color: rgba(0,0,0,0.5); |
| | } |
| | |
| | .modal-content { |
| | background-color: white; |
| | margin: 10% auto; |
| | padding: 20px; |
| | border-radius: 8px; |
| | width: 50%; |
| | max-width: 600px; |
| | box-shadow: 0 4px 6px rgba(0,0,0,0.1); |
| | } |
| | |
| | .close { |
| | color: #aaa; |
| | float: right; |
| | font-size: 28px; |
| | font-weight: bold; |
| | cursor: pointer; |
| | } |
| | |
| | .close:hover { |
| | color: black; |
| | } |
| | |
| | .feature-tag { |
| | display: inline-block; |
| | background-color: #e1ecf4; |
| | color: #2c3e50; |
| | padding: 4px 8px; |
| | border-radius: 4px; |
| | margin: 2px; |
| | font-size: 0.9em; |
| | } |
| | |
| | .loading { |
| | text-align: center; |
| | padding: 20px; |
| | color: #666; |
| | } |
| | </style> |
| | </head> |
| | <body> |
| |
|
| | <div class="container"> |
| | <header> |
| | <h1>🏆 AutoFS Leaderboard</h1> |
| | <div class="controls"> |
| | <label for="dataset-select">Dataset:</label> |
| | <select id="dataset-select"> |
| | <option value="" disabled selected>Loading...</option> |
| | </select> |
| | </div> |
| | </header> |
| |
|
| | <div id="loading-indicator" class="loading" style="display: none;">Loading data...</div> |
| |
|
| | <div class="chart-controls" style="text-align:center; margin-top: 20px; margin-bottom: 15px;"> |
| | <label style="margin-right:15px; font-weight:bold;">View Mode:</label> |
| | <input type="radio" id="view-overall" name="chart-view" value="overall" checked onchange="updateView()"> |
| | <label for="view-overall" style="margin-right:10px;">Overall (Mean)</label> |
| | |
| | <input type="radio" id="view-classifiers-f1" name="chart-view" value="classifiers-f1" onchange="updateView()"> |
| | <label for="view-classifiers-f1" style="margin-right:10px;">F1 by Classifier</label> |
| | |
| | <input type="radio" id="view-classifiers-auc" name="chart-view" value="classifiers-auc" onchange="updateView()"> |
| | <label for="view-classifiers-auc">AUC by Classifier</label> |
| | </div> |
| |
|
| | <div class="charts-container" style="display: flex; gap: 20px; margin-bottom: 20px;"> |
| | <div style="flex: 1; background: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);"> |
| | <canvas id="scoreChart"></canvas> |
| | </div> |
| | <div style="flex: 1; background: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);"> |
| | <canvas id="timeChart"></canvas> |
| | </div> |
| | </div> |
| |
|
| | <table id="result-table"> |
| | <thead> |
| | |
| | </thead> |
| | <tbody> |
| | |
| | </tbody> |
| | </table> |
| | </div> |
| |
|
| | |
| | <div id="details-modal" class="modal"> |
| | <div class="modal-content"> |
| | <span class="close">×</span> |
| | <h2 id="modal-title">Algorithm Details</h2> |
| | <div id="modal-body"></div> |
| | </div> |
| | </div> |
| |
|
| | <script> |
| | let currentResults = []; |
| | let sortDirection = 1; |
| | let lastSortKey = ''; |
| | |
| | const VIEW_CONFIG = { |
| | 'overall': [ |
| | { key: 'mean_f1', label: 'Mean F1' }, |
| | { key: 'mean_auc', label: 'Mean AUC' } |
| | ], |
| | 'classifiers-f1': [ |
| | { key: 'metrics.nb.f1', label: 'NB F1' }, |
| | { key: 'metrics.svm.f1', label: 'SVM F1' }, |
| | { key: 'metrics.rf.f1', label: 'RF F1' } |
| | ], |
| | 'classifiers-auc': [ |
| | { key: 'metrics.nb.auc', label: 'NB AUC' }, |
| | { key: 'metrics.svm.auc', label: 'SVM AUC' }, |
| | { key: 'metrics.rf.auc', label: 'RF AUC' } |
| | ] |
| | }; |
| | |
| | const tableHead = document.querySelector("#result-table thead"); |
| | const tableBody = document.querySelector("#result-table tbody"); |
| | const datasetSelect = document.getElementById("dataset-select"); |
| | const loadingIndicator = document.getElementById("loading-indicator"); |
| | const modal = document.getElementById("details-modal"); |
| | const closeModal = document.querySelector(".close"); |
| | |
| | |
| | closeModal.onclick = () => modal.style.display = "none"; |
| | window.onclick = (event) => { |
| | if (event.target == modal) modal.style.display = "none"; |
| | } |
| | |
| | |
| | let scoreChartInstance = null; |
| | let timeChartInstance = null; |
| | |
| | function updateCharts(results) { |
| | if (!Array.isArray(results) || results.length === 0) return; |
| | |
| | |
| | const topResults = results.slice(0, 15); |
| | const labels = topResults.map(r => r.algorithm || 'Unknown'); |
| | const times = topResults.map(r => r.time || 0); |
| | |
| | const viewMode = document.querySelector('input[name="chart-view"]:checked').value; |
| | let datasets = []; |
| | |
| | if (viewMode === 'overall') { |
| | const f1Scores = topResults.map(r => r.mean_f1 || 0); |
| | const aucScores = topResults.map(r => r.mean_auc || 0); |
| | datasets = [ |
| | { |
| | label: 'Mean F1', |
| | data: f1Scores, |
| | backgroundColor: 'rgba(52, 152, 219, 0.7)', |
| | borderColor: 'rgba(52, 152, 219, 1)', |
| | borderWidth: 1 |
| | }, |
| | { |
| | label: 'Mean AUC', |
| | data: aucScores, |
| | backgroundColor: 'rgba(46, 204, 113, 0.7)', |
| | borderColor: 'rgba(46, 204, 113, 1)', |
| | borderWidth: 1 |
| | } |
| | ]; |
| | } else if (viewMode === 'classifiers-f1') { |
| | const classifiers = ['nb', 'svm', 'rf']; |
| | const colors = ['rgba(255, 206, 86, 0.5)', 'rgba(75, 192, 192, 0.5)', 'rgba(153, 102, 255, 0.5)']; |
| | const borderColors = ['rgba(255, 206, 86, 1)', 'rgba(75, 192, 192, 1)', 'rgba(153, 102, 255, 1)']; |
| | |
| | datasets = classifiers.map((cls, idx) => ({ |
| | label: cls.toUpperCase() + ' F1', |
| | data: topResults.map(r => (r.metrics && r.metrics[cls]) ? r.metrics[cls].f1 : 0), |
| | backgroundColor: colors[idx], |
| | borderColor: borderColors[idx], |
| | borderWidth: 1 |
| | })); |
| | } else if (viewMode === 'classifiers-auc') { |
| | const classifiers = ['nb', 'svm', 'rf']; |
| | const colors = ['rgba(255, 206, 86, 0.5)', 'rgba(75, 192, 192, 0.5)', 'rgba(153, 102, 255, 0.5)']; |
| | const borderColors = ['rgba(255, 206, 86, 1)', 'rgba(75, 192, 192, 1)', 'rgba(153, 102, 255, 1)']; |
| | |
| | datasets = classifiers.map((cls, idx) => ({ |
| | label: cls.toUpperCase() + ' AUC', |
| | data: topResults.map(r => (r.metrics && r.metrics[cls]) ? r.metrics[cls].auc : 0), |
| | backgroundColor: colors[idx], |
| | borderColor: borderColors[idx], |
| | borderWidth: 1 |
| | })); |
| | } |
| | |
| | |
| | const scoreCtx = document.getElementById('scoreChart').getContext('2d'); |
| | if (scoreChartInstance) scoreChartInstance.destroy(); |
| | |
| | scoreChartInstance = new Chart(scoreCtx, { |
| | type: 'bar', |
| | data: { |
| | labels: labels, |
| | datasets: datasets |
| | }, |
| | options: { |
| | responsive: true, |
| | maintainAspectRatio: false, |
| | plugins: { |
| | title: { |
| | display: true, |
| | text: viewMode === 'overall' ? 'Top Algorithms Performance (Mean)' : |
| | (viewMode === 'classifiers-f1' ? 'F1-Score by Classifier' : 'AUC by Classifier') |
| | } |
| | }, |
| | scales: { |
| | y: { |
| | beginAtZero: false, |
| | |
| | } |
| | } |
| | } |
| | }); |
| | |
| | |
| | const timeCtx = document.getElementById('timeChart').getContext('2d'); |
| | if (timeChartInstance) timeChartInstance.destroy(); |
| | |
| | timeChartInstance = new Chart(timeCtx, { |
| | type: 'line', |
| | data: { |
| | labels: labels, |
| | datasets: [{ |
| | label: 'Time (s)', |
| | data: times, |
| | backgroundColor: 'rgba(231, 76, 60, 0.2)', |
| | borderColor: 'rgba(231, 76, 60, 1)', |
| | borderWidth: 2, |
| | tension: 0.3, |
| | fill: true |
| | }] |
| | }, |
| | options: { |
| | responsive: true, |
| | maintainAspectRatio: false, |
| | plugins: { |
| | title: { display: true, text: 'Execution Time' } |
| | }, |
| | scales: { |
| | y: { beginAtZero: true } |
| | } |
| | } |
| | }); |
| | } |
| | |
| | function showDetails(result) { |
| | const title = document.getElementById("modal-title"); |
| | const body = document.getElementById("modal-body"); |
| | |
| | title.textContent = `${result.algorithm} Details`; |
| | |
| | let featuresHtml = result.selected_features.map(f => |
| | `<span class="feature-tag">${f}</span>` |
| | ).join(''); |
| | |
| | let metricsHtml = '<div style="margin-top: 15px;"><h3>Metrics Breakdown</h3>'; |
| | for (const [clf, m] of Object.entries(result.metrics || {})) { |
| | metricsHtml += ` |
| | <div style="margin-bottom: 10px;"> |
| | <strong>${clf.toUpperCase()}:</strong> |
| | F1: ${m.f1.toFixed(4)}, AUC: ${m.auc.toFixed(4)} |
| | </div>`; |
| | } |
| | metricsHtml += '</div>'; |
| | |
| | body.innerHTML = ` |
| | <p><strong>Time:</strong> ${result.time.toFixed(4)}s</p> |
| | <p><strong>Num Features:</strong> ${result.num_features}</p> |
| | <p><strong>Selected Features (${result.selected_features.length}):</strong></p> |
| | <div>${featuresHtml}</div> |
| | ${metricsHtml} |
| | `; |
| | |
| | modal.style.display = "block"; |
| | } |
| | |
| | function getValue(obj, path) { |
| | if (!path) return undefined; |
| | return path.split('.').reduce((acc, part) => (acc && acc[part] !== undefined) ? acc[part] : undefined, obj); |
| | } |
| | |
| | function safeFixed(value, digits=4) { |
| | if (value === undefined || value === null) return 'N/A'; |
| | return Number(value).toFixed(digits); |
| | } |
| | |
| | function renderTableHeader() { |
| | const viewMode = document.querySelector('input[name="chart-view"]:checked').value; |
| | const dynamicCols = VIEW_CONFIG[viewMode] || VIEW_CONFIG['overall']; |
| | |
| | let html = '<tr>'; |
| | html += '<th data-key="rank" style="width: 60px;">#</th>'; |
| | html += '<th data-key="algorithm">Algorithm <span class="arrow">↕</span></th>'; |
| | |
| | dynamicCols.forEach(col => { |
| | html += `<th data-key="${col.key}">${col.label} <span class="arrow">↕</span></th>`; |
| | }); |
| | |
| | html += '<th data-key="time">Time (s) <span class="arrow">↕</span></th>'; |
| | html += '<th data-key="selected_features">Selected Features</th>'; |
| | html += '</tr>'; |
| | |
| | tableHead.innerHTML = html; |
| | |
| | |
| | tableHead.querySelectorAll('th[data-key]').forEach(th => { |
| | th.addEventListener('click', () => sortTable(th.dataset.key)); |
| | }); |
| | } |
| | |
| | function updateTable(results) { |
| | tableBody.innerHTML = ""; |
| | |
| | |
| | if (!Array.isArray(results)) { |
| | tableBody.innerHTML = '<tr><td colspan="10" style="text-align:center; color:red;">Error: Invalid data format</td></tr>'; |
| | return; |
| | } |
| | |
| | if (results.length === 0) { |
| | tableBody.innerHTML = '<tr><td colspan="10" style="text-align:center;">No results found</td></tr>'; |
| | return; |
| | } |
| | |
| | const viewMode = document.querySelector('input[name="chart-view"]:checked').value; |
| | const dynamicCols = VIEW_CONFIG[viewMode] || VIEW_CONFIG['overall']; |
| | |
| | results.forEach((r, idx) => { |
| | const row = document.createElement("tr"); |
| | |
| | |
| | const featurePreview = (r.selected_features && Array.isArray(r.selected_features)) |
| | ? r.selected_features.slice(0, 5).join(', ') + (r.selected_features.length > 5 ? '...' : '') |
| | : 'N/A'; |
| | |
| | let html = `<td>${idx + 1}</td>`; |
| | html += `<td><strong>${r.algorithm || 'Unknown'}</strong></td>`; |
| | |
| | dynamicCols.forEach(col => { |
| | const val = getValue(r, col.key); |
| | const score = val !== undefined ? val : 0; |
| | html += ` |
| | <td> |
| | ${safeFixed(val)} |
| | <div class="score-bar"><div class="score-fill" style="width: ${Math.min(score * 100, 100)}%"></div></div> |
| | </td>`; |
| | }); |
| | |
| | const time = r.time || 0; |
| | html += `<td>${safeFixed(time, 2)}</td>`; |
| | html += ` |
| | <td class="features-cell" onclick="showDetails(currentResults[${idx}])" title="Click for details"> |
| | ${featurePreview} <span style="font-size:0.8em; color:#999;">(Click for details)</span> |
| | </td>`; |
| | |
| | row.innerHTML = html; |
| | tableBody.appendChild(row); |
| | }); |
| | } |
| | |
| | function sortTable(key) { |
| | if (lastSortKey === key) { |
| | sortDirection *= -1; |
| | } else { |
| | sortDirection = key === 'time' || key === 'rank' ? 1 : -1; |
| | lastSortKey = key; |
| | } |
| | |
| | |
| | |
| | document.querySelectorAll('th .arrow').forEach(span => span.textContent = '↕'); |
| | const activeHeader = document.querySelector(`th[data-key="${key}"] .arrow`); |
| | if (activeHeader) activeHeader.textContent = sortDirection === 1 ? '↑' : '↓'; |
| | |
| | const sorted = [...currentResults].sort((a, b) => { |
| | let valA = getValue(a, key); |
| | let valB = getValue(b, key); |
| | |
| | if (key === 'rank') return 0; |
| | |
| | if (valA === undefined) valA = -Infinity; |
| | if (valB === undefined) valB = -Infinity; |
| | |
| | if (valA < valB) return -1 * sortDirection; |
| | if (valA > valB) return 1 * sortDirection; |
| | return 0; |
| | }); |
| | |
| | |
| | |
| | |
| | currentResults = sorted; |
| | updateTable(sorted); |
| | } |
| | |
| | function updateView() { |
| | renderTableHeader(); |
| | updateTable(currentResults); |
| | updateCharts(currentResults); |
| | } |
| | |
| | function fetchResults(dataset) { |
| | loadingIndicator.style.display = 'block'; |
| | tableBody.innerHTML = ''; |
| | |
| | console.log("Fetching results for:", dataset); |
| | fetch(`/api/results?dataset=${dataset}`) |
| | .then(res => { |
| | if (!res.ok) throw new Error("Network response was not ok"); |
| | return res.json(); |
| | }) |
| | .then(data => { |
| | console.log("Data received:", data); |
| | currentResults = data; |
| | updateView(); |
| | loadingIndicator.style.display = 'none'; |
| | }) |
| | .catch(err => { |
| | console.error("Error fetching results:", err); |
| | loadingIndicator.textContent = "Error loading data. Make sure the server is running."; |
| | }); |
| | } |
| | |
| | |
| | document.addEventListener("DOMContentLoaded", () => { |
| | |
| | document.querySelectorAll('th[data-key]').forEach(th => { |
| | th.addEventListener('click', () => sortTable(th.dataset.key)); |
| | }); |
| | |
| | |
| | fetch("/api/datasets") |
| | .then(res => res.json()) |
| | .then(datasets => { |
| | datasetSelect.innerHTML = ""; |
| | datasets.forEach(ds => { |
| | const option = document.createElement("option"); |
| | option.value = ds; |
| | option.textContent = ds; |
| | datasetSelect.appendChild(option); |
| | }); |
| | |
| | if (datasets.includes("Authorship")) { |
| | datasetSelect.value = "Authorship"; |
| | fetchResults("Authorship"); |
| | } else if (datasets.length > 0) { |
| | datasetSelect.value = datasets[0]; |
| | fetchResults(datasets[0]); |
| | } |
| | }) |
| | .catch(err => { |
| | console.error("Error fetching datasets:", err); |
| | datasetSelect.innerHTML = "<option>Error loading datasets</option>"; |
| | }); |
| | |
| | datasetSelect.addEventListener('change', (e) => { |
| | fetchResults(e.target.value); |
| | }); |
| | }); |
| | </script> |
| |
|
| | </body> |
| | </html> |
| |
|