Fancy-yousa's picture
Upload 18 files
94e0fc9 verified
<!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 styles */
.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>
<!-- Headers generated dynamically -->
</thead>
<tbody>
<!-- Data rows will be populated here -->
</tbody>
</table>
</div>
<!-- Modal for details -->
<div id="details-modal" class="modal">
<div class="modal-content">
<span class="close">&times;</span>
<h2 id="modal-title">Algorithm Details</h2>
<div id="modal-body"></div>
</div>
</div>
<script>
let currentResults = [];
let sortDirection = 1; // 1 for asc, -1 for desc
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");
// Close modal
closeModal.onclick = () => modal.style.display = "none";
window.onclick = (event) => {
if (event.target == modal) modal.style.display = "none";
}
// Global chart instances
let scoreChartInstance = null;
let timeChartInstance = null;
function updateCharts(results) {
if (!Array.isArray(results) || results.length === 0) return;
// Limit to top 15 for readability
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
}));
}
// Score Chart
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,
// min: 0.8
}
}
}
});
// Time Chart
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;
// Re-attach sort listeners
tableHead.querySelectorAll('th[data-key]').forEach(th => {
th.addEventListener('click', () => sortTable(th.dataset.key));
});
}
function updateTable(results) {
tableBody.innerHTML = "";
// Safety check
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");
// Format features for preview
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;
}
// We don't call renderTableHeader here because it resets the sort indicators if we rebuild entirely.
// Instead, we just update the arrows.
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;
});
// Don't update currentResults global if it breaks things, but here it's fine.
// Actually, let's keep currentResults as the master list?
// No, currentResults should be the sorted list for consistent subsequent sorts.
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.";
});
}
// Initialize
document.addEventListener("DOMContentLoaded", () => {
// Setup sort listeners
document.querySelectorAll('th[data-key]').forEach(th => {
th.addEventListener('click', () => sortTable(th.dataset.key));
});
// Load datasets
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>