| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Global Leaderboard</title> |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> |
| <style> |
| :root { |
| --primary-color: #8e44ad; |
| --secondary-color: #2c3e50; |
| --background-color: #f8f9fa; |
| --text-color: #333; |
| --border-color: #dee2e6; |
| --hover-color: #f1f1f1; |
| --sidebar-width: 280px; |
| } |
| |
| body { |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
| margin: 0; |
| padding: 0; |
| background-color: var(--background-color); |
| color: var(--text-color); |
| display: flex; |
| min-height: 100vh; |
| } |
| |
| |
| .sidebar { |
| width: var(--sidebar-width); |
| background-color: var(--secondary-color); |
| color: white; |
| position: fixed; |
| height: 100vh; |
| overflow-y: auto; |
| padding: 20px; |
| box-sizing: border-box; |
| left: 0; |
| top: 0; |
| z-index: 100; |
| display: flex; |
| flex-direction: column; |
| gap: 20px; |
| } |
| |
| .sidebar h2 { |
| font-size: 1.2em; |
| margin-bottom: 10px; |
| color: #ecf0f1; |
| border-bottom: 1px solid #34495e; |
| padding-bottom: 5px; |
| } |
| |
| .nav-links { |
| list-style: none; |
| padding: 0; |
| margin: 0; |
| } |
| |
| .nav-links li a { |
| display: block; |
| padding: 10px; |
| color: #bdc3c7; |
| text-decoration: none; |
| border-radius: 4px; |
| transition: background 0.2s; |
| } |
| |
| .nav-links li a:hover, .nav-links li a.active { |
| background: rgba(255,255,255,0.1); |
| color: white; |
| } |
| |
| |
| .main-content { |
| margin-left: var(--sidebar-width); |
| flex: 1; |
| padding: 40px; |
| max-width: calc(100% - var(--sidebar-width)); |
| box-sizing: border-box; |
| min-width: 0; |
| } |
| |
| .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); |
| } |
| |
| .description-box { |
| background-color: #e8f4fd; |
| border-left: 4px solid #3498db; |
| padding: 15px; |
| margin-bottom: 20px; |
| border-radius: 4px; |
| } |
| |
| .description-box h3 { |
| margin-top: 0; |
| color: #2980b9; |
| } |
| |
| .description-box p { |
| margin: 5px 0; |
| line-height: 1.5; |
| } |
| |
| .weights-control { |
| background-color: #f1f1f1; |
| padding: 15px; |
| border-radius: 8px; |
| margin-bottom: 20px; |
| border: 1px solid #ddd; |
| } |
| |
| .weights-control h3 { |
| margin-top: 0; |
| margin-bottom: 15px; |
| font-size: 1.1em; |
| } |
| |
| .sliders-container { |
| display: flex; |
| gap: 20px; |
| flex-wrap: wrap; |
| align-items: center; |
| } |
| |
| .slider-group { |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| } |
| |
| .slider-group label { |
| font-weight: bold; |
| min-width: 60px; |
| } |
| |
| input[type="number"] { |
| width: 80px; |
| padding: 5px; |
| border: 1px solid #ccc; |
| border-radius: 4px; |
| } |
| |
| input[readonly] { |
| background-color: #e9ecef; |
| color: #666; |
| } |
| |
| button.recalc-btn { |
| background-color: var(--primary-color); |
| color: white; |
| border: none; |
| padding: 8px 20px; |
| border-radius: 4px; |
| cursor: pointer; |
| font-size: 14px; |
| margin-left: auto; |
| } |
| |
| button.recalc-btn:hover { |
| opacity: 0.9; |
| } |
| |
| 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); |
| } |
| |
| .version-tag { |
| font-size: 0.8em; |
| color: #7f8c8d; |
| margin-top: 5px; |
| } |
| </style> |
| </head> |
| <body> |
|
|
| <div class="sidebar"> |
| <div style="text-align: center; margin-bottom: 20px;"> |
| <h1 style="font-size: 1.5em; margin: 0; color: white;">AutoFS</h1> |
| <div style="font-size: 0.8em; color: #bdc3c7;">Feature Selection Leaderboard</div> |
| </div> |
|
|
| <div> |
| <h2>Navigation</h2> |
| <ul class="nav-links"> |
| <li><a href="/">π Overview</a></li> |
| <li><a href="/#main-table">π Leaderboard</a></li> |
| <li><a href="/#charts">π Charts</a></li> |
| <li><a href="/#details">βΉοΈ Details</a></li> |
| <li><a href="/global" class="active">π Global Rankings</a></li> |
| <li><a href="/submit">π€ Submit Data/Method</a></li> |
| </ul> |
| </div> |
| |
| <div style="margin-top: auto; padding-top: 20px; border-top: 1px solid #34495e;"> |
| <p style="font-size: 0.8em; color: #bdc3c7; text-align: center;"> |
| Need help?<br> |
| <a href="mailto:support@autofs.com" style="color: var(--primary-color);">Contact Support</a> |
| </p> |
| </div> |
| </div> |
|
|
| <div class="main-content"> |
| <div class="container"> |
| <header> |
| <div> |
| <h1>π Global Algorithm Rankings</h1> |
| <div id="last-updated" class="version-tag">Data Last Updated: Loading...</div> |
| </div> |
| |
| </header> |
|
|
| <div class="description-box"> |
| <h3>About Global Rankings</h3> |
| <p> |
| This page provides a comprehensive evaluation of feature selection algorithms across all available datasets. |
| Algorithms are ranked based on a weighted score combining <strong>Accuracy (F1)</strong>, <strong>Robustness (AUC)</strong>, and <strong>Efficiency (Time)</strong>. |
| You can adjust the importance of each factor below to customize the ranking criteria. |
| </p> |
| </div> |
|
|
| <div class="weights-control"> |
| <h3>π Scoring Formula: S = Ξ±Β·F1 + Ξ²Β·AUC</h3> |
| <p style="font-size: 0.9em; color: #666; margin-bottom: 10px;"> |
| Constraint: Ξ± + Ξ² = 1. |
| </p> |
| |
| <div class="sliders-container"> |
| <div class="slider-group"> |
| <label for="weight-a">F1 (Ξ±):</label> |
| <input type="number" id="weight-a" value="0.5" step="0.05" min="0" max="1"> |
| </div> |
| |
| <div class="slider-group"> |
| <label for="weight-b">AUC (Ξ²):</label> |
| <input type="number" id="weight-b" value="0.5" readonly title="Auto-calculated: 1 - Ξ±"> |
| </div> |
|
|
| <button class="recalc-btn" onclick="calculateAndRender()">Recalculate Rankings</button> |
| </div> |
| </div> |
|
|
| <div id="loading-indicator" style="text-align: center; color: #666;">Loading global stats...</div> |
|
|
| <table id="global-table"> |
| <thead> |
| <tr> |
| <th data-key="rank">#</th> |
| <th data-key="algorithm">Algorithm <span class="arrow">β</span></th> |
| <th data-key="mean_f1_global">Global F1 <span class="arrow">β</span></th> |
| <th data-key="mean_auc_global">Global AUC <span class="arrow">β</span></th> |
| <th data-key="final_score">Final Score <span class="arrow">β</span></th> |
| </tr> |
| </thead> |
| <tbody> |
| |
| </tbody> |
| </table> |
| </div> |
| </div> |
|
|
| <script> |
| let rawData = []; |
| let processedData = []; |
| let sortKey = 'final_score'; |
| let sortDirection = -1; |
| |
| const tableBody = document.querySelector("#global-table tbody"); |
| const weightA = document.getElementById("weight-a"); |
| const weightB = document.getElementById("weight-b"); |
| const lastUpdatedDiv = document.getElementById("last-updated"); |
| |
| |
| function fetchLastUpdated() { |
| fetch("/api/datasets") |
| .then(res => res.json()) |
| .then(data => { |
| if (data.length > 0) { |
| |
| |
| |
| |
| const dates = data.map(d => d.last_updated).filter(d => d !== 'Unknown').sort().reverse(); |
| if (dates.length > 0) { |
| lastUpdatedDiv.textContent = `Data Last Updated: ${dates[0]}`; |
| } else { |
| lastUpdatedDiv.textContent = `Data Last Updated: Unknown`; |
| } |
| } |
| }); |
| } |
| |
| function fetchGlobalStats() { |
| fetch("/api/global_stats") |
| .then(res => res.json()) |
| .then(data => { |
| rawData = data; |
| document.getElementById("loading-indicator").style.display = 'none'; |
| calculateAndRender(); |
| }) |
| .catch(err => { |
| console.error("Error:", err); |
| document.getElementById("loading-indicator").textContent = "Error loading data."; |
| }); |
| } |
| |
| |
| function updateWeights(changedInput) { |
| let a = parseFloat(weightA.value) || 0; |
| |
| |
| if (a < 0) a = 0; if (a > 1) a = 1; |
| |
| if (changedInput === 'a') { |
| let b = 1 - a; |
| |
| weightA.value = parseFloat(a.toFixed(2)); |
| weightB.value = parseFloat(b.toFixed(2)); |
| } |
| } |
| |
| weightA.addEventListener('input', () => updateWeights('a')); |
| |
| function calculateAndRender() { |
| const a = parseFloat(weightA.value) || 0; |
| const b = parseFloat(weightB.value) || 0; |
| |
| |
| processedData = rawData.map(d => { |
| const score = (a * d.mean_f1_global) + (b * d.mean_auc_global); |
| |
| return { |
| ...d, |
| final_score: score |
| }; |
| }); |
| |
| sortData(); |
| renderTable(); |
| } |
| |
| function sortData() { |
| processedData.sort((x, y) => { |
| const valX = x[sortKey]; |
| const valY = y[sortKey]; |
| |
| if (valX < valY) return -1 * sortDirection; |
| if (valX > valY) return 1 * sortDirection; |
| return 0; |
| }); |
| } |
| |
| function safeFixed(val, digits=4) { |
| if (val === undefined || val === null) return 'N/A'; |
| return Number(val).toFixed(digits); |
| } |
| |
| function renderTable() { |
| tableBody.innerHTML = ""; |
| |
| if (processedData.length === 0) { |
| tableBody.innerHTML = '<tr><td colspan="5" style="text-align:center;">No data available</td></tr>'; |
| return; |
| } |
| |
| processedData.forEach((row, index) => { |
| const tr = document.createElement("tr"); |
| |
| |
| let rankHtml = index + 1; |
| if (index === 0) rankHtml = 'π₯ 1'; |
| if (index === 1) rankHtml = 'π₯ 2'; |
| if (index === 2) rankHtml = 'π₯ 3'; |
| |
| tr.innerHTML = ` |
| <td>${rankHtml}</td> |
| <td><strong>${row.algorithm}</strong> <span style="font-size:0.8em; color:#999">(${row.datasets_count} datasets)</span></td> |
| <td>${safeFixed(row.mean_f1_global)}</td> |
| <td>${safeFixed(row.mean_auc_global)}</td> |
| <td> |
| <strong>${safeFixed(row.final_score)}</strong> |
| <div class="score-bar"><div class="score-fill" style="width: ${Math.min(row.final_score * 100, 100)}%"></div></div> |
| </td> |
| `; |
| tableBody.appendChild(tr); |
| }); |
| |
| updateSortArrows(); |
| } |
| |
| function updateSortArrows() { |
| document.querySelectorAll('th .arrow').forEach(span => span.textContent = 'β'); |
| const activeHeader = document.querySelector(`th[data-key="${sortKey}"] .arrow`); |
| if (activeHeader) activeHeader.textContent = sortDirection === 1 ? 'β' : 'β'; |
| } |
| |
| document.querySelectorAll('th[data-key]').forEach(th => { |
| th.addEventListener('click', () => { |
| const key = th.dataset.key; |
| if (sortKey === key) { |
| sortDirection *= -1; |
| } else { |
| sortKey = key; |
| sortDirection = (key === 'rank') ? 1 : -1; |
| } |
| if (key === 'rank') { |
| sortKey = 'final_score'; |
| sortDirection = -1; |
| } |
| sortData(); |
| renderTable(); |
| }); |
| }); |
| |
| document.addEventListener("DOMContentLoaded", () => { |
| fetchLastUpdated(); |
| fetchGlobalStats(); |
| }); |
| |
| </script> |
|
|
| </body> |
| </html> |
|
|