Spaces:
Running
Running
<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 ; | |
color: #fff3cd; | |
} | |
.nar-metric:hover { | |
background: #975a16 ; | |
} | |
.nar-metric.sorted { | |
background: #6c5100 ; | |
} | |
.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> | |