| | |
| | |
| |
|
| | (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(); |
| | }; |
| | })(); |
| |
|