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> | |