| | const COL_ORDER = [ |
| | "Model Name", |
| | "WorldScore-Static", |
| | "WorldScore-Dynamic", |
| | "Camera Control", |
| | "Object Control", |
| | "Content Alignment", |
| | "3D Consistency", |
| | "Photometric Consistency", |
| | "Style Consistency", |
| | "Subjective Quality", |
| | "Motion Accuracy", |
| | "Motion Magnitude", |
| | "Motion Smoothness", |
| | "Model Type", |
| | "Ability", |
| | "Sampled by", |
| | "Evaluated by", |
| | "Accessibility", |
| | "Date", |
| | ]; |
| |
|
| | |
| | function parseCsv(text) { |
| | const lines = text.trim().split(/\r?\n/); |
| | const header = lines[0].split(","); |
| | const rows = lines |
| | .slice(1) |
| | .map((line) => { |
| | if (!line.trim()) return null; |
| | const parts = line.split(","); |
| | const obj = {}; |
| | header.forEach((h, i) => { |
| | obj[h.trim()] = (parts[i] || "").trim(); |
| | }); |
| | return obj; |
| | }) |
| | .filter(Boolean); |
| | return { header, rows }; |
| | } |
| |
|
| | function parseMarkdownLink(s) { |
| | const m = s.match(/^\[(.*?)\]\((.*?)\)$/); |
| | if (m) { |
| | return { text: m[1], url: m[2] }; |
| | } |
| | return { text: s, url: null }; |
| | } |
| |
|
| | function isNumericColumn(colName) { |
| | const numericCols = new Set([ |
| | "WorldScore-Static", |
| | "WorldScore-Dynamic", |
| | "Camera Control", |
| | "Object Control", |
| | "Content Alignment", |
| | "3D Consistency", |
| | "Photometric Consistency", |
| | "Style Consistency", |
| | "Subjective Quality", |
| | "Motion Accuracy", |
| | "Motion Magnitude", |
| | "Motion Smoothness", |
| | ]); |
| | return numericCols.has(colName); |
| | } |
| |
|
| | function buildTable(rows) { |
| | const container = document.getElementById("table-container"); |
| | container.innerHTML = ""; |
| |
|
| | const table = document.createElement("table"); |
| | table.className = "ws-table"; |
| |
|
| | const thead = document.createElement("thead"); |
| | const headRow = document.createElement("tr"); |
| |
|
| | const firstRow = rows[0] || {}; |
| | const cols = COL_ORDER.filter((c) => c in firstRow); |
| |
|
| | |
| | cols.forEach((col) => { |
| | const th = document.createElement("th"); |
| | th.textContent = col; |
| | th.classList.add("sortable"); |
| | th.dataset.col = col; |
| |
|
| | |
| | if (col === "WorldScore-Static") { |
| | th.classList.add("desc"); |
| | } |
| | headRow.appendChild(th); |
| | }); |
| |
|
| | thead.appendChild(headRow); |
| | table.appendChild(thead); |
| |
|
| | const tbody = document.createElement("tbody"); |
| | table.appendChild(tbody); |
| | container.appendChild(table); |
| |
|
| | |
| | function computeTopInfo(data) { |
| | const info = {}; |
| | cols.forEach((col) => { |
| | if (!isNumericColumn(col)) return; |
| |
|
| | const vals = []; |
| | data.forEach((row) => { |
| | const v = parseFloat(row[col] ?? ""); |
| | if (!isNaN(v)) vals.push(v); |
| | }); |
| |
|
| | if (vals.length === 0) { |
| | info[col] = { max: null, second: null }; |
| | return; |
| | } |
| |
|
| | vals.sort((a, b) => b - a); |
| |
|
| | const max = vals[0]; |
| | let second = null; |
| | for (let i = 1; i < vals.length; i++) { |
| | if (vals[i] !== max) { |
| | second = vals[i]; |
| | break; |
| | } |
| | } |
| | info[col] = { max, second }; |
| | }); |
| | return info; |
| | } |
| |
|
| | |
| | function renderBody(data, topInfo) { |
| | tbody.innerHTML = ""; |
| | data.forEach((row) => { |
| | const tr = document.createElement("tr"); |
| | cols.forEach((col) => { |
| | const td = document.createElement("td"); |
| | const val = row[col] ?? ""; |
| |
|
| | if (col === "Model Name") { |
| | const { text, url } = parseMarkdownLink(val); |
| | if (url) { |
| | const a = document.createElement("a"); |
| | a.href = url; |
| | a.target = "_blank"; |
| | a.rel = "noopener noreferrer"; |
| | a.textContent = text; |
| | td.appendChild(a); |
| | } else { |
| | td.textContent = text; |
| | } |
| | } else { |
| | td.textContent = val; |
| |
|
| | if (isNumericColumn(col)) { |
| | const num = parseFloat(val); |
| | const info = topInfo && topInfo[col]; |
| |
|
| | if (!isNaN(num) && info) { |
| | if (info.max != null && num === info.max) { |
| | |
| | td.style.fontWeight = "700"; |
| | } else if (info.second != null && num === info.second) { |
| | |
| | td.style.textDecoration = "underline"; |
| | } |
| | } |
| | } |
| | } |
| |
|
| | tr.appendChild(td); |
| | }); |
| | tbody.appendChild(tr); |
| | }); |
| | } |
| |
|
| | |
| | let currentSortCol = "WorldScore-Static"; |
| | let currentAsc = false; |
| |
|
| | |
| | function sortAndRender(col, asc) { |
| | const sorted = [...rows].sort((a, b) => { |
| | const va = a[col] ?? ""; |
| | const vb = b[col] ?? ""; |
| |
|
| | if (isNumericColumn(col)) { |
| | const na = parseFloat(va); |
| | const nb = parseFloat(vb); |
| | if (isNaN(na) && isNaN(nb)) return 0; |
| | if (isNaN(na)) return asc ? -1 : 1; |
| | if (isNaN(nb)) return asc ? 1 : -1; |
| | return asc ? na - nb : nb - na; |
| | } else { |
| | return asc |
| | ? String(va).localeCompare(String(vb)) |
| | : String(vb).localeCompare(String(va)); |
| | } |
| | }); |
| |
|
| | const topInfo = computeTopInfo(sorted); |
| | renderBody(sorted, topInfo); |
| |
|
| | |
| | const headers = Array.from(thead.querySelectorAll("th.sortable")); |
| | headers.forEach((h) => { |
| | h.classList.remove("asc", "desc"); |
| | }); |
| | const target = headers.find((h) => h.dataset.col === col); |
| | if (target) { |
| | target.classList.add(asc ? "asc" : "desc"); |
| | } |
| | } |
| |
|
| | |
| | sortAndRender(currentSortCol, currentAsc); |
| |
|
| | |
| | const headers = Array.from(thead.querySelectorAll("th.sortable")); |
| | headers.forEach((th) => { |
| | th.addEventListener("click", () => { |
| | const col = th.dataset.col; |
| | if (col === currentSortCol) { |
| | currentAsc = !currentAsc; |
| | } else { |
| | currentSortCol = col; |
| | currentAsc = true; |
| | } |
| | sortAndRender(currentSortCol, currentAsc); |
| | }); |
| | }); |
| | } |
| |
|
| | |
| | fetch("leaderboard.csv") |
| | .then((res) => { |
| | if (!res.ok) { |
| | throw new Error("HTTP " + res.status); |
| | } |
| | return res.text(); |
| | }) |
| | .then((text) => { |
| | const parsed = parseCsv(text); |
| | if (!parsed.rows || parsed.rows.length === 0) { |
| | document.getElementById("table-container").innerHTML = |
| | "<p>No data rows in leaderboard.csv</p>"; |
| | return; |
| | } |
| |
|
| | |
| | const rows = parsed.rows.slice(); |
| | buildTable(rows); |
| | }) |
| | .catch((err) => { |
| | console.error("Failed to load CSV:", err); |
| | document.getElementById("table-container").innerHTML = |
| | "<p style='color:#f97316'>Failed to load leaderboard.csv: " + String(err) + "</p>"; |
| | }); |
| |
|