Howieeeee's picture
Update script.js
94c6762 verified
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>";
});