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