Spaces:
Configuration error
Configuration error
| 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", | |
| ]; | |
| // 简单 CSV 解析(你的数据没有带逗号的字段,这样已经够用) | |
| 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); | |
| // 表头:全部 sortable | |
| cols.forEach((col) => { | |
| const th = document.createElement("th"); | |
| th.textContent = col; | |
| th.classList.add("sortable"); | |
| th.dataset.col = col; | |
| // 默认排序列:WorldScore-Static,标记为 desc | |
| 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; | |
| } | |
| // 根据当前 rows 渲染 body(使用 topInfo 做加粗/下划线) | |
| 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); | |
| // 更新箭头 class | |
| 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"); | |
| } | |
| } | |
| // 初始渲染:WorldScore-Static 降序 | |
| sortAndRender(currentSortCol, currentAsc); | |
| // 给所有表头绑定 click 排序 | |
| 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); | |
| }); | |
| }); | |
| } | |
| // 入口:加载 CSV,默认在 JS 里先按 WorldScore-Static 降序排一次,然后 buildTable | |
| 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; | |
| } | |
| // 这里的 rows 保留原始字符串形式(包括 [Name](url)) | |
| 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>"; | |
| }); | |