| |
| |
|
|
| (function () { |
| "use strict"; |
|
|
| |
| let activeFilters = { tier: "all", game: "all", modality: "all" }; |
| let sortCol = "avg"; |
| let sortAsc = false; |
|
|
| |
| function heatColor(val, min, max) { |
| if (val === null || val === undefined) return null; |
| const range = max - min; |
| const t = range > 0 ? (val - min) / range : 0.5; |
|
|
| let r, g, b; |
| if (t < 0.5) { |
| const s = t / 0.5; |
| r = 220 + (240 - 220) * s; |
| g = 130 + (210 - 130) * s; |
| b = 120 + (130 - 120) * s; |
| } else { |
| const s = (t - 0.5) / 0.5; |
| r = 240 - (240 - 130) * s; |
| g = 210 - (210 - 195) * s; |
| b = 130 - (130 - 100) * s; |
| } |
| return `rgb(${Math.round(r)},${Math.round(g)},${Math.round(b)})`; |
| } |
|
|
| function textColorForBg() { |
| return "#1a1a1a"; |
| } |
|
|
| |
| function getVisibleColumns() { |
| const cols = []; |
| const games = activeFilters.game === "all" ? GAMES : [activeFilters.game]; |
| const mods = activeFilters.modality === "all" ? MODALITIES : [activeFilters.modality]; |
| for (const g of games) { |
| for (const m of mods) { |
| cols.push({ game: g, modality: m, key: g + "|" + m }); |
| } |
| } |
| return cols; |
| } |
|
|
| |
| function getVisibleRows() { |
| const tiers = activeFilters.tier === "all" ? TIERS : [activeFilters.tier]; |
| return BENCHMARK_DATA.filter(d => tiers.includes(d.tier)); |
| } |
|
|
| |
| function rowAvg(row, cols) { |
| let sum = 0, n = 0; |
| for (const c of cols) { |
| const v = row.scores[c.key]; |
| if (v !== null && v !== undefined) { sum += v; n++; } |
| } |
| return n > 0 ? sum / n : null; |
| } |
|
|
| |
| function renderLeaderboard() { |
| const cols = getVisibleColumns(); |
| let rows = getVisibleRows(); |
|
|
| |
| const avgMap = new Map(); |
| for (const r of rows) avgMap.set(r, rowAvg(r, cols)); |
|
|
| |
| rows = rows.slice().sort((a, b) => { |
| const tierA = TIERS.indexOf(a.tier); |
| const tierB = TIERS.indexOf(b.tier); |
| if (tierA !== tierB) return tierA - tierB; |
|
|
| if (sortCol) { |
| let va, vb; |
| if (sortCol === "avg") { |
| va = avgMap.get(a); |
| vb = avgMap.get(b); |
| } else { |
| va = a.scores[sortCol]; |
| vb = b.scores[sortCol]; |
| } |
| if (va === null || va === undefined) va = -Infinity; |
| if (vb === null || vb === undefined) vb = -Infinity; |
| return sortAsc ? va - vb : vb - va; |
| } |
| return 0; |
| }); |
|
|
| |
| let allVals = []; |
| for (const r of rows) { |
| for (const c of cols) { |
| const v = r.scores[c.key]; |
| if (v !== null && v !== undefined) allVals.push(v); |
| } |
| const avg = avgMap.get(r); |
| if (avg !== null) allVals.push(avg); |
| } |
| const minVal = allVals.length > 0 ? Math.min(...allVals) : 0; |
| const maxVal = allVals.length > 0 ? Math.max(...allVals) : 1; |
|
|
| |
| const thead = document.getElementById("leaderboard-thead"); |
| thead.innerHTML = ""; |
|
|
| |
| const showGameRow = activeFilters.modality === "all"; |
| if (showGameRow) { |
| const gameRow = document.createElement("tr"); |
| gameRow.className = "game-header"; |
|
|
| |
| const corner = document.createElement("th"); |
| corner.textContent = ""; |
| corner.rowSpan = 2; |
| gameRow.appendChild(corner); |
|
|
| |
| const avgTh = document.createElement("th"); |
| avgTh.className = "avg-col"; |
| avgTh.rowSpan = 2; |
| avgTh.style.cursor = "pointer"; |
| avgTh.onclick = () => { toggleSort("avg"); }; |
| avgTh.innerHTML = 'Avg ' + sortIndicator("avg"); |
| gameRow.appendChild(avgTh); |
|
|
| const visibleGames = activeFilters.game === "all" ? GAMES : [activeFilters.game]; |
| for (const g of visibleGames) { |
| const th = document.createElement("th"); |
| th.textContent = GAME_LABELS[g] || g; |
| th.colSpan = MODALITIES.length; |
| gameRow.appendChild(th); |
| } |
|
|
| thead.appendChild(gameRow); |
| } |
|
|
| |
| const modRow = document.createElement("tr"); |
| modRow.className = "mod-header"; |
|
|
| if (!showGameRow) { |
| const corner = document.createElement("th"); |
| corner.textContent = "Model"; |
| modRow.appendChild(corner); |
|
|
| |
| const avgTh = document.createElement("th"); |
| avgTh.className = "avg-col"; |
| avgTh.innerHTML = 'Avg ' + sortIndicator("avg"); |
| avgTh.style.cursor = "pointer"; |
| avgTh.onclick = () => { toggleSort("avg"); }; |
| modRow.appendChild(avgTh); |
| } |
|
|
| for (const c of cols) { |
| const th = document.createElement("th"); |
| const label = showGameRow ? c.modality : (GAME_LABELS[c.game] || c.game) + " / " + c.modality; |
| th.innerHTML = label + " " + sortIndicator(c.key); |
| th.onclick = () => { toggleSort(c.key); }; |
| modRow.appendChild(th); |
| } |
|
|
| thead.appendChild(modRow); |
|
|
| |
| const tbody = document.getElementById("leaderboard-tbody"); |
| tbody.innerHTML = ""; |
|
|
| const showTierGroups = activeFilters.tier === "all"; |
| let lastTier = null; |
| const totalCols = 2 + cols.length; |
|
|
| for (const r of rows) { |
| |
| if (showTierGroups && r.tier !== lastTier) { |
| const sepTr = document.createElement("tr"); |
| sepTr.className = "tier-separator"; |
| const sepTd = document.createElement("td"); |
| sepTd.colSpan = totalCols; |
| sepTd.textContent = TIER_LABELS[r.tier] || r.tier; |
| sepTr.appendChild(sepTd); |
| tbody.appendChild(sepTr); |
| lastTier = r.tier; |
| } |
|
|
| const tr = document.createElement("tr"); |
|
|
| |
| const modelTd = document.createElement("td"); |
| modelTd.className = "model-cell"; |
| modelTd.textContent = r.model; |
| tr.appendChild(modelTd); |
|
|
| |
| const avgTd = document.createElement("td"); |
| avgTd.className = "score-cell avg-col"; |
| const avg = avgMap.get(r); |
| if (avg === null) { |
| avgTd.textContent = "\u2014"; |
| avgTd.classList.add("null-cell"); |
| } else { |
| avgTd.textContent = (avg * 100).toFixed(1); |
| const bg = heatColor(avg, minVal, maxVal); |
| if (bg) { |
| avgTd.style.backgroundColor = bg; |
| avgTd.style.color = textColorForBg(); |
| } |
| } |
| tr.appendChild(avgTd); |
|
|
| |
| for (const c of cols) { |
| const td = document.createElement("td"); |
| td.className = "score-cell"; |
| const v = r.scores[c.key]; |
| if (v === null || v === undefined) { |
| td.textContent = "\u2014"; |
| td.classList.add("null-cell"); |
| } else { |
| td.textContent = (v * 100).toFixed(1); |
| const bg = heatColor(v, minVal, maxVal); |
| if (bg) { |
| td.style.backgroundColor = bg; |
| td.style.color = textColorForBg(); |
| } |
| } |
| tr.appendChild(td); |
| } |
|
|
| tbody.appendChild(tr); |
| } |
| } |
|
|
| |
| function sortIndicator(key) { |
| if (sortCol !== key) return '<span class="sort-arrow">\u2195</span>'; |
| const arrow = sortAsc ? "\u2191" : "\u2193"; |
| return `<span class="sort-arrow active">${arrow}</span>`; |
| } |
|
|
| function toggleSort(key) { |
| if (sortCol === key) { |
| sortAsc = !sortAsc; |
| } else { |
| sortCol = key; |
| sortAsc = false; |
| } |
| renderLeaderboard(); |
| } |
|
|
| |
| function initFilters() { |
| document.querySelectorAll("#leaderboard-filters .filter-buttons").forEach(group => { |
| const filterType = group.dataset.filter; |
| group.querySelectorAll(".filter-btn").forEach(btn => { |
| btn.addEventListener("click", () => { |
| group.querySelectorAll(".filter-btn").forEach(b => b.classList.remove("active")); |
| btn.classList.add("active"); |
| activeFilters[filterType] = btn.dataset.value; |
| sortCol = null; |
| renderLeaderboard(); |
| }); |
| }); |
| }); |
| } |
|
|
| |
| window.initLeaderboard = function () { |
| initFilters(); |
| renderLeaderboard(); |
| }; |
| })(); |
|
|