leaderboard / index.html
cyhuang-tw's picture
Update index.html
d8059f2 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dynamic-SUPERB Leaderboard</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 0;
padding: 20px;
background-color: #f8f9fa;
}
/* Menu Styles */
.menu-container {
position: fixed;
top: 15px;
left: 15px;
z-index: 1000;
}
.menu-icon {
font-size: 28px;
cursor: pointer;
user-select: none;
color: #667eea;
background: white;
padding: 8px 12px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
transition: all 0.2s ease;
}
.menu-icon:hover {
background: #667eea;
color: white;
transform: scale(1.05);
}
.menu-list {
display: none;
position: absolute;
top: 50px;
left: 0;
background: white;
border: 1px solid #dee2e6;
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0,0,0,0.15);
min-width: 220px;
font-size: 14px;
overflow: hidden;
}
.menu-list.show {
display: block;
animation: slideDown 0.2s ease;
}
@keyframes slideDown {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.menu-list a {
display: block;
padding: 12px 16px;
color: #495057;
text-decoration: none;
border-bottom: 1px solid #f8f9fa;
transition: background-color 0.2s ease;
}
.menu-list a:last-child {
border-bottom: none;
}
.menu-list a:hover {
background-color: #667eea;
color: white;
}
.menu-list a::before {
content: "→ ";
margin-right: 8px;
opacity: 0;
transition: opacity 0.2s ease;
}
.menu-list a:hover::before {
opacity: 1;
}
.container {
max-width: 1400px;
margin: 0 auto;
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
text-align: center;
}
.header h1 {
margin: 0;
font-size: 2.5em;
font-weight: 300;
}
.controls {
padding: 20px 30px;
border-bottom: 1px solid #dee2e6;
background: #f8f9fa;
}
.filter-row {
display: flex;
gap: 20px;
margin-bottom: 15px;
flex-wrap: wrap;
}
.form-group {
flex: 1;
min-width: 200px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: 600;
color: #495057;
}
select {
width: 100%;
padding: 10px 15px;
border: 2px solid #dee2e6;
border-radius: 6px;
background: white;
font-size: 14px;
transition: border-color 0.3s;
}
select:focus {
outline: none;
border-color: #667eea;
}
select:disabled {
background-color: #f8f9fa;
color: #6c757d;
cursor: not-allowed;
}
.clear-filters {
padding: 8px 16px;
background: #dc3545;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s;
}
.clear-filters:hover {
background: #c82333;
}
.table-container {
overflow-x: auto;
max-height: 70vh;
overflow-y: auto;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 12px;
}
th {
background: #343a40;
color: white;
padding: 8px 6px;
text-align: center;
position: sticky;
top: 0;
z-index: 10;
border-right: 1px solid #495057;
writing-mode: horizontal-tb;
text-orientation: initial;
min-width: 80px;
max-width: 120px;
white-space: normal;
word-wrap: break-word;
word-break: break-word;
vertical-align: bottom;
height: auto;
line-height: 1.2;
}
th:first-child {
min-width: 150px;
text-align: left;
padding: 12px 8px;
white-space: nowrap;
}
.task-header {
font-size: 11px;
line-height: 1.2;
white-space: normal;
word-wrap: break-word;
word-break: break-word;
text-align: center;
vertical-align: bottom;
padding: 8px 4px;
background: #495057;
cursor: default;
}
.metric-header {
font-size: 10px;
line-height: 1.1;
text-align: center;
vertical-align: bottom;
padding: 6px 3px;
background: #6c757d;
position: sticky;
top: 40px;
z-index: 9;
cursor: pointer;
transition: background-color 0.2s;
user-select: none;
}
.metric-header:hover {
background: #5a6268;
}
.metric-header.sorted {
background: #007bff;
font-weight: bold;
}
.nar-metric {
background: #856404 !important;
color: #fff3cd;
}
.nar-metric:hover {
background: #975a16 !important;
}
.nar-metric.sorted {
background: #6c5100 !important;
}
.sort-indicator {
font-size: 12px;
margin-left: 3px;
color: #28a745;
}
.sort-arrow {
font-size: 10px;
margin-left: 2px;
color: #ffc107;
}
td {
padding: 6px;
border-bottom: 1px solid #dee2e6;
border-right: 1px solid #dee2e6;
text-align: center;
font-family: 'Monaco', 'Menlo', monospace;
font-size: 11px;
}
td:first-child {
text-align: left;
font-family: inherit;
font-weight: 600;
background: #f8f9fa;
position: sticky;
left: 0;
z-index: 5;
padding: 8px 12px;
font-size: 13px;
white-space: nowrap;
}
.score.na {
color: #6c757d;
font-style: italic;
}
.stats {
padding: 15px 30px;
background: #e9ecef;
font-size: 14px;
color: #495057;
}
.task-name {
display: block;
margin-bottom: 4px;
font-weight: 600;
}
.metric-indicator {
font-size: 9px;
color: #ffc107;
display: block;
margin-top: 2px;
font-weight: normal;
}
.nar-indicator {
color: #fff3cd;
font-weight: bold;
}
</style>
</head>
<body>
<!-- Menu Container -->
<div class="menu-container">
<div class="menu-icon" id="menuIcon"></div>
<div class="menu-list" id="menuList">
<a href="checker.html">Checker</a>
<a href="https://github.com/dynamic-superb/dynamic-superb" target="_blank">GitHub Repository</a>
<a href="https://arxiv.org/abs/2411.05361" target="_blank">Paper on arXiv</a>
</div>
</div>
<div class="container">
<div class="header">
<h1>Dynamic-SUPERB Leaderboard</h1>
</div>
<div class="controls">
<div class="filter-row">
<div class="form-group">
<label for="level1Filter">Level 1 (Top-level):</label>
<select id="level1Filter">
<option value="">All Categories</option>
</select>
</div>
<div class="form-group">
<label for="level2Filter">Level 2:</label>
<select id="level2Filter" disabled>
<option value="">Select Level 1 first</option>
</select>
</div>
<div class="form-group">
<label for="level3Filter">Level 3:</label>
<select id="level3Filter" disabled>
<option value="">Select Level 2 first</option>
</select>
</div>
<div class="form-group">
<label for="level4Filter">Level 4:</label>
<select id="level4Filter" disabled>
<option value="">Select Level 3 first</option>
</select>
</div>
<div class="form-group">
<button class="clear-filters" onclick="clearAllFilters()">Clear All</button>
</div>
</div>
</div>
<div class="stats">
<span id="taskCount">Loading...</span>
</div>
<div class="table-container">
<table id="leaderboard">
<thead id="tableHead">
<!-- Headers will be populated dynamically -->
</thead>
<tbody id="tableBody">
</tbody>
</table>
</div>
</div>
<script>
// Menu functionality
const menuIcon = document.getElementById('menuIcon');
const menuList = document.getElementById('menuList');
menuIcon.addEventListener('click', (e) => {
e.stopPropagation();
menuList.classList.toggle('show');
});
// Close menu if clicked outside
document.addEventListener('click', (e) => {
if (!menuIcon.contains(e.target) && !menuList.contains(e.target)) {
menuList.classList.remove('show');
}
});
// Utility functions
function cleanTaxonomy(tax) {
return tax.trim().replace(/^"|"$/g, '');
}
function parseCSVLine(line) {
const result = [];
let current = '';
let inQuotes = false;
for (let i = 0; i < line.length; i++) {
const char = line[i];
if (char === '"') {
inQuotes = !inQuotes;
} else if (char === ',' && !inQuotes) {
result.push(current.trim());
current = '';
} else {
current += char;
}
}
result.push(current.trim());
return result;
}
function parseTaxonomyPath(taxonomyPath) {
const cleaned = cleanTaxonomy(taxonomyPath);
const parts = cleaned.split('/').map(part => part.trim());
return {
level1: parts[0] || '',
level2: parts[1] || '',
level3: parts[2] || '',
level4: parts[3] || '',
fullPath: cleaned,
levels: parts.length
};
}
function parseScore(scoreStr) {
if (!scoreStr || scoreStr === '-' || scoreStr === '') return null;
const cleaned = scoreStr.replace(/[%$,]/g, '');
const parsed = parseFloat(cleaned);
return isNaN(parsed) ? null : parsed;
}
function clearAllFilters() {
document.getElementById('level1Filter').value = '';
document.getElementById('level2Filter').value = '';
document.getElementById('level3Filter').value = '';
document.getElementById('level4Filter').value = '';
document.getElementById('level2Filter').disabled = true;
document.getElementById('level3Filter').disabled = true;
document.getElementById('level4Filter').disabled = true;
document.getElementById('level2Filter').innerHTML = '<option value="">Select Level 1 first</option>';
document.getElementById('level3Filter').innerHTML = '<option value="">Select Level 2 first</option>';
document.getElementById('level4Filter').innerHTML = '<option value="">Select Level 3 first</option>';
// FIXED: Reset the internal filter state
if (leaderboard) {
leaderboard.currentFilters = { level1: '', level2: '', level3: '', level4: '' };
}
const headers = document.querySelectorAll('[data-taxonomy]');
headers.forEach(header => header.style.display = '');
const cells = document.querySelectorAll('td[data-taxonomy]');
cells.forEach(cell => cell.style.display = '');
// FIXED: Re-render table body to ensure consistency
if (leaderboard) {
leaderboard.renderTableBody();
leaderboard.updateStats();
}
}
class SortableDynamicSUPERBLeaderboard {
constructor() {
this.data = [];
this.models = [];
this.tasks = {};
this.taskMetrics = {};
this.higherBetterMap = {};
this.currentSort = { taskName: null, metric: null, direction: 'desc' };
this.currentFilters = { level1: '', level2: '', level3: '', level4: '' };
this.taxonomyHierarchy = {
level1: new Set(),
level2: new Map(),
level3: new Map(),
level4: new Map()
};
this.init();
}
async init() {
try {
await this.loadData();
this.processData();
this.buildTaxonomyHierarchy();
this.buildTransposedTable();
this.setupFilters();
this.setupSorting();
} catch (error) {
console.error('Error initializing leaderboard:', error);
document.getElementById('tableBody').innerHTML = '<tr><td colspan="100%">Error loading data. Please check the CSV file.</td></tr>';
}
}
async loadData() {
const response = await fetch('data.csv');
const csvText = await response.text();
const lines = csvText.trim().split('\n');
const headers = parseCSVLine(lines[0]);
// Find HigherBetter and Taxonomy column indices
const higherBetterIndex = headers.findIndex(h => h.trim().toLowerCase() === 'higherbetter');
const taxonomyIndex = headers.findIndex(h => h.trim().toLowerCase() === 'taxonomy');
if (higherBetterIndex === -1) {
console.error('HigherBetter column not found');
return;
}
// Models are between HigherBetter and Taxonomy
const startIndex = higherBetterIndex + 1;
const endIndex = taxonomyIndex !== -1 ? taxonomyIndex : headers.length - 1;
this.models = headers.slice(startIndex, endIndex);
for (let i = 1; i < lines.length; i++) {
const row = parseCSVLine(lines[i]);
if (row.length < headers.length) continue;
const taskName = row[0];
const metric = row[1];
const taxonomy = cleanTaxonomy(row[row.length - 1]);
if (metric === 'X') continue;
const scores = {};
for (let j = 0; j < this.models.length; j++) {
scores[this.models[j]] = row[startIndex + j];
}
// Parse HigherBetter as string
const higherBetterValue = row[higherBetterIndex].trim().toUpperCase();
const higherBetter = higherBetterValue === 'TRUE';
const parsedTaxonomy = parseTaxonomyPath(taxonomy);
this.data.push({
taskName,
metric,
scores,
taxonomy,
parsedTaxonomy,
higherBetter
});
this.higherBetterMap[`${taskName}|${metric}`] = higherBetter;
if (!this.tasks[taskName]) {
this.tasks[taskName] = {
taxonomy,
parsedTaxonomy,
metrics: []
};
this.taskMetrics[taskName] = [];
}
this.tasks[taskName].metrics.push({ metric, scores, higherBetter });
this.taskMetrics[taskName].push(metric);
}
}
processData() {
Object.values(this.tasks).forEach(task => {
task.metrics.sort((a, b) => {
if (a.metric.includes('NAR')) return -1;
if (b.metric.includes('NAR')) return 1;
return a.metric.localeCompare(b.metric);
});
});
Object.keys(this.taskMetrics).forEach(taskName => {
this.taskMetrics[taskName].sort((a, b) => {
if (a.includes('NAR')) return -1;
if (b.includes('NAR')) return 1;
return a.localeCompare(b);
});
});
}
buildTaxonomyHierarchy() {
Object.values(this.tasks).forEach(task => {
const parsed = task.parsedTaxonomy;
if (parsed.level1) {
this.taxonomyHierarchy.level1.add(parsed.level1);
if (parsed.level2) {
if (!this.taxonomyHierarchy.level2.has(parsed.level1)) {
this.taxonomyHierarchy.level2.set(parsed.level1, new Set());
}
this.taxonomyHierarchy.level2.get(parsed.level1).add(parsed.level2);
if (parsed.level3) {
const key3 = parsed.level1 + '/' + parsed.level2;
if (!this.taxonomyHierarchy.level3.has(key3)) {
this.taxonomyHierarchy.level3.set(key3, new Set());
}
this.taxonomyHierarchy.level3.get(key3).add(parsed.level3);
if (parsed.level4) {
const key4 = key3 + '/' + parsed.level3;
if (!this.taxonomyHierarchy.level4.has(key4)) {
this.taxonomyHierarchy.level4.set(key4, new Set());
}
this.taxonomyHierarchy.level4.get(key4).add(parsed.level4);
}
}
}
}
});
}
// Check if a task should be visible based on current filters
isTaskVisible(taskData) {
const { level1, level2, level3, level4 } = this.currentFilters;
const parsed = taskData.parsedTaxonomy;
if (level1 && parsed.level1 !== level1) return false;
if (level2 && parsed.level2 !== level2) return false;
if (level3 && parsed.level3 !== level3) return false;
if (level4 && parsed.level4 !== level4) return false;
return true;
}
buildTransposedTable() {
const thead = document.getElementById('tableHead');
let taskHeaderHTML = '<th rowspan="2">Model</th>';
let metricHeaderHTML = '';
Object.entries(this.tasks).forEach(([taskName, taskData]) => {
const taxonomyAttrs = this.buildTaxonomyDataAttributes(taskData.parsedTaxonomy);
const metrics = this.taskMetrics[taskName];
const hasNAR = metrics.some(m => m.includes('NAR'));
taskHeaderHTML += `
<th class="task-header" colspan="${metrics.length}" ${taxonomyAttrs}>
<span class="task-name">${taskName}</span>
<span class="metric-indicator ${hasNAR ? 'nar-indicator' : ''}">
${metrics.length} metrics${hasNAR ? ' (NAR)' : ''}
</span>
</th>
`;
metrics.forEach(metric => {
const metricClass = metric.includes('NAR') ? 'nar-metric' : '';
const higherBetter = this.higherBetterMap[`${taskName}|${metric}`];
const sortIndicator = higherBetter ? '↑' : '↓';
metricHeaderHTML += `
<th class="metric-header ${metricClass}"
data-task="${taskName}"
data-metric="${metric}"
${taxonomyAttrs}>
${metric}
<span class="sort-indicator">${sortIndicator}</span>
<span class="sort-arrow" id="arrow-${taskName}-${metric}"></span>
</th>
`;
});
});
thead.innerHTML = `
<tr>${taskHeaderHTML}</tr>
<tr>${metricHeaderHTML}</tr>
`;
this.renderTableBody();
this.updateStats();
}
renderTableBody() {
const tbody = document.getElementById('tableBody');
let sortedModels = [...this.models];
if (this.currentSort.taskName && this.currentSort.metric) {
sortedModels = this.sortModels(this.currentSort.taskName, this.currentSort.metric, this.currentSort.direction);
}
let tableHTML = '';
sortedModels.forEach(modelName => {
tableHTML += `<tr><td>${modelName}</td>`;
Object.entries(this.tasks).forEach(([taskName, taskData]) => {
const taxonomyAttrs = this.buildTaxonomyDataAttributes(taskData.parsedTaxonomy);
const metrics = this.taskMetrics[taskName];
metrics.forEach(metric => {
const metricData = taskData.metrics.find(m => m.metric === metric);
let score = '-';
let cellClass = 'na';
if (metricData) {
score = metricData.scores[modelName];
cellClass = this.getScoreClass(score);
}
// Apply the same visibility logic as the headers
const isVisible = this.isTaskVisible(taskData);
const displayStyle = isVisible ? '' : 'none';
tableHTML += `
<td class="score ${cellClass}" ${taxonomyAttrs} style="display: ${displayStyle};">
${this.formatScore(score)}
</td>
`;
});
});
tableHTML += '</tr>';
});
tbody.innerHTML = tableHTML;
}
sortModels(taskName, metric, direction) {
const taskData = this.tasks[taskName];
const metricData = taskData.metrics.find(m => m.metric === metric);
if (!metricData) return [...this.models];
const higherBetter = this.higherBetterMap[`${taskName}|${metric}`];
return [...this.models].sort((modelA, modelB) => {
const scoreA = parseScore(metricData.scores[modelA]);
const scoreB = parseScore(metricData.scores[modelB]);
if (scoreA === null && scoreB === null) return 0;
if (scoreA === null) return 1;
if (scoreB === null) return -1;
let comparison = scoreA - scoreB;
// Adjust for higherBetter preference
if (!higherBetter) {
comparison = -comparison;
}
// Apply sort direction
if (direction === 'asc') {
comparison = -comparison;
}
return comparison;
});
}
setupSorting() {
document.addEventListener('click', (e) => {
if (e.target.classList.contains('metric-header') || e.target.closest('.metric-header')) {
const header = e.target.classList.contains('metric-header') ? e.target : e.target.closest('.metric-header');
const taskName = header.dataset.task;
const metric = header.dataset.metric;
if (!taskName || !metric) return;
// Toggle sort direction if clicking the same column
let direction = 'desc';
if (this.currentSort.taskName === taskName && this.currentSort.metric === metric) {
direction = this.currentSort.direction === 'desc' ? 'asc' : 'desc';
}
this.currentSort = { taskName, metric, direction };
// Update visual indicators
document.querySelectorAll('.metric-header').forEach(h => {
h.classList.remove('sorted');
});
document.querySelectorAll('.sort-arrow').forEach(arrow => {
arrow.textContent = '';
});
header.classList.add('sorted');
const arrowElement = document.getElementById(`arrow-${taskName}-${metric}`);
if (arrowElement) {
arrowElement.textContent = direction === 'desc' ? '▼' : '▲';
}
this.renderTableBody();
}
});
}
buildTaxonomyDataAttributes(parsed) {
return `
data-taxonomy="${parsed.fullPath}"
data-level1="${parsed.level1}"
data-level2="${parsed.level2}"
data-level3="${parsed.level3}"
data-level4="${parsed.level4}"
`;
}
getScoreClass(score) {
if (!score || score === '-' || score === '') return 'na';
return '';
}
formatScore(score) {
if (!score || score === '-' || score === '') return '-';
return score;
}
setupFilters() {
const level1Filter = document.getElementById('level1Filter');
const level2Filter = document.getElementById('level2Filter');
const level3Filter = document.getElementById('level3Filter');
const level4Filter = document.getElementById('level4Filter');
const sortedLevel1 = Array.from(this.taxonomyHierarchy.level1).sort();
sortedLevel1.forEach(item => {
const option = document.createElement('option');
option.value = item;
option.textContent = item;
level1Filter.appendChild(option);
});
level1Filter.addEventListener('change', (e) => {
const selected = e.target.value;
this.currentFilters.level1 = selected;
level2Filter.innerHTML = '<option value="">All Sub-categories</option>';
level3Filter.innerHTML = '<option value="">Select Level 2 first</option>';
level4Filter.innerHTML = '<option value="">Select Level 3 first</option>';
level2Filter.disabled = !selected;
level3Filter.disabled = true;
level4Filter.disabled = true;
level2Filter.value = '';
level3Filter.value = '';
level4Filter.value = '';
this.currentFilters.level2 = '';
this.currentFilters.level3 = '';
this.currentFilters.level4 = '';
if (selected && this.taxonomyHierarchy.level2.has(selected)) {
const level2Options = Array.from(this.taxonomyHierarchy.level2.get(selected)).sort();
level2Options.forEach(item => {
const option = document.createElement('option');
option.value = item;
option.textContent = item;
level2Filter.appendChild(option);
});
}
this.applyFilters();
});
level2Filter.addEventListener('change', (e) => {
const level1Value = level1Filter.value;
const level2Value = e.target.value;
this.currentFilters.level2 = level2Value;
level3Filter.innerHTML = '<option value="">All Sub-categories</option>';
level4Filter.innerHTML = '<option value="">Select Level 3 first</option>';
level3Filter.disabled = !level2Value;
level4Filter.disabled = true;
level3Filter.value = '';
level4Filter.value = '';
this.currentFilters.level3 = '';
this.currentFilters.level4 = '';
if (level1Value && level2Value) {
const level2Key = `${level1Value}/${level2Value}`;
if (this.taxonomyHierarchy.level3.has(level2Key)) {
const level3Options = Array.from(this.taxonomyHierarchy.level3.get(level2Key)).sort();
level3Options.forEach(item => {
const option = document.createElement('option');
option.value = item;
option.textContent = item;
level3Filter.appendChild(option);
});
}
}
this.applyFilters();
});
level3Filter.addEventListener('change', (e) => {
const level1Value = level1Filter.value;
const level2Value = level2Filter.value;
const level3Value = e.target.value;
this.currentFilters.level3 = level3Value;
level4Filter.innerHTML = '<option value="">All Sub-categories</option>';
level4Filter.disabled = !level3Value;
level4Filter.value = '';
this.currentFilters.level4 = '';
if (level1Value && level2Value && level3Value) {
const level3Key = `${level1Value}/${level2Value}/${level3Value}`;
if (this.taxonomyHierarchy.level4.has(level3Key)) {
const level4Options = Array.from(this.taxonomyHierarchy.level4.get(level3Key)).sort();
level4Options.forEach(item => {
const option = document.createElement('option');
option.value = item;
option.textContent = item;
level4Filter.appendChild(option);
});
}
}
this.applyFilters();
});
level4Filter.addEventListener('change', (e) => {
this.currentFilters.level4 = e.target.value;
this.applyFilters();
});
}
applyFilters() {
const level1Value = document.getElementById('level1Filter').value;
const level2Value = document.getElementById('level2Filter').value;
const level3Value = document.getElementById('level3Filter').value;
const level4Value = document.getElementById('level4Filter').value;
// Update current filters
this.currentFilters = { level1: level1Value, level2: level2Value, level3: level3Value, level4: level4Value };
const taskHeaders = document.querySelectorAll('.task-header[data-taxonomy]');
const metricHeaders = document.querySelectorAll('.metric-header[data-taxonomy]');
let visibleTasks = 0;
taskHeaders.forEach((header, index) => {
const headerLevel1 = header.dataset.level1;
const headerLevel2 = header.dataset.level2;
const headerLevel3 = header.dataset.level3;
const headerLevel4 = header.dataset.level4;
let show = true;
if (level1Value && headerLevel1 !== level1Value) show = false;
if (level2Value && headerLevel2 !== level2Value) show = false;
if (level3Value && headerLevel3 !== level3Value) show = false;
if (level4Value && headerLevel4 !== level4Value) show = false;
header.style.display = show ? '' : 'none';
if (show) visibleTasks++;
});
metricHeaders.forEach((header, index) => {
const headerLevel1 = header.dataset.level1;
const headerLevel2 = header.dataset.level2;
const headerLevel3 = header.dataset.level3;
const headerLevel4 = header.dataset.level4;
let show = true;
if (level1Value && headerLevel1 !== level1Value) show = false;
if (level2Value && headerLevel2 !== level2Value) show = false;
if (level3Value && headerLevel3 !== level3Value) show = false;
if (level4Value && headerLevel4 !== level4Value) show = false;
header.style.display = show ? '' : 'none';
const columnIndex = index + 2;
const cells = document.querySelectorAll(`td:nth-child(${columnIndex})`);
cells.forEach(cell => {
cell.style.display = show ? '' : 'none';
});
});
// Re-render table body to respect filter state in data cells
this.renderTableBody();
this.updateStats(visibleTasks);
}
updateStats(visibleTasks = null) {
const totalTasks = Object.keys(this.tasks).length;
const totalMetrics = this.data.length;
const visible = visibleTasks !== null ? visibleTasks : totalTasks;
document.getElementById('taskCount').textContent =
`Showing ${visible} of ${totalTasks} tasks (${totalMetrics} total metrics) across ${this.models.length} models`;
}
}
let leaderboard;
document.addEventListener('DOMContentLoaded', () => {
leaderboard = new SortableDynamicSUPERBLeaderboard();
});
</script>
</body>
</html>