Spaces:
Running
Running
| /* Results / graphs tester β animates the paper's headline numbers the way the Manim results scene does: | |
| * bars grow in and the paper's own method is highlighted with its delta over the best prior; line series | |
| * draw left-to-right; metric cards count up. Reads the SAME chart spec the video's chart is built from | |
| * (charts.ChartSpec.as_scene_dict: kind/categories/series/highlight/β¦), so the graph here equals the | |
| * graph there. */ | |
| (function () { | |
| const k = MANIMO.kit; | |
| function norm(p) { | |
| const kind = String(p.kind || p.primitive || "bar").toLowerCase(); | |
| const cats = (p.categories || p.labels || []).map(String); | |
| let series = p.series || []; | |
| if (!series.length && p.values) series = [{ name: "", values: p.values }]; | |
| series = series.map((s) => ({ name: String(s.name || ""), values: (s.values || []).map((v) => { const n = Number(v); return Number.isFinite(n) ? n : 0; }), points: s.points || null })); | |
| return { kind, title: p.title || "", y: p.y_label || p.ylabel || "", unit: p.unit || "", highlight: p.highlight || null, cats, series, metrics: p.metrics || [] }; | |
| } | |
| // ββ bar / line on a canvas ββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function mountChart(root, concept, theme) { | |
| const spec = norm((concept && concept.params) || {}); | |
| const cv = document.createElement("canvas"); cv.width = 560; cv.height = 260; cv.className = "plot chart"; | |
| cv.setAttribute("role", "img"); cv.setAttribute("aria-label", (spec.title || "Results") + " " + spec.kind + " chart"); | |
| const log = k.panel("steps"); | |
| const isLine = spec.kind === "line"; | |
| const cats = spec.cats.length ? spec.cats : (spec.series[0] ? spec.series[0].values.map((_, i) => "x" + i) : []); | |
| const vals = spec.series[0] ? spec.series[0].values : []; | |
| const hiIdx = cats.indexOf(spec.highlight); | |
| const best = bestOther(vals, hiIdx, cats); | |
| function bars(shown, grow) { | |
| const ctx = cv.getContext("2d"), W = cv.width, H = cv.height, n = cats.length, pad = 34; | |
| ctx.fillStyle = theme.bg; ctx.fillRect(0, 0, W, H); | |
| const mx = Math.max.apply(null, vals.concat([1e-6])) * 1.15, bw = (W - pad - 14) / Math.max(1, n); | |
| ctx.strokeStyle = theme.muted; ctx.globalAlpha = 0.5; ctx.beginPath(); ctx.moveTo(pad, H - 26); ctx.lineTo(W - 6, H - 26); ctx.stroke(); ctx.globalAlpha = 1; | |
| cats.forEach((c, i) => { | |
| const full = (vals[i] || 0) / mx * (H - 56); | |
| const h = i < shown ? full : (i === shown ? full * grow : 0); | |
| const x = pad + i * bw + 6; | |
| const hot = i === hiIdx; | |
| ctx.fillStyle = hot ? theme.accent2 : theme.accent; ctx.globalAlpha = hot ? 1 : 0.85; | |
| if (hot) { ctx.shadowColor = theme.accent2; ctx.shadowBlur = 16; } | |
| ctx.fillRect(x, H - 26 - h, bw - 12, h); ctx.shadowBlur = 0; ctx.globalAlpha = 1; | |
| ctx.fillStyle = theme.ink; ctx.font = "11px system-ui"; ctx.textAlign = "center"; | |
| ctx.fillText(c, x + (bw - 12) / 2, H - 12); | |
| if (i < shown || (i === shown && grow > 0.98)) { ctx.fillStyle = hot ? theme.accent2 : theme.ink; ctx.font = "bold 12px system-ui"; ctx.fillText(k.fmt(vals[i]) + spec.unit, x + (bw - 12) / 2, H - 30 - h); } | |
| }); | |
| if (spec.title) { ctx.fillStyle = theme.muted; ctx.font = "12px system-ui"; ctx.textAlign = "left"; ctx.fillText(spec.title + (spec.y ? " (" + spec.y + ")" : ""), pad, 16); } | |
| } | |
| function line(prog) { | |
| const ctx = cv.getContext("2d"), W = cv.width, H = cv.height, pad = 34; | |
| ctx.fillStyle = theme.bg; ctx.fillRect(0, 0, W, H); | |
| const pts = (spec.series[0] && spec.series[0].points) || vals.map((v, i) => [i, v]); | |
| if (!pts.length) { // empty/malformed series β draw the frame + a note, never index an empty array | |
| if (spec.title) { ctx.fillStyle = theme.muted; ctx.font = "12px system-ui"; ctx.textAlign = "left"; ctx.fillText(spec.title + " β no data", pad, 16); } | |
| return; | |
| } | |
| const xs = pts.map((q) => q[0]), ys = pts.map((q) => q[1]); | |
| const xlo = Math.min.apply(null, xs), xhi = Math.max.apply(null, xs), ylo = Math.min.apply(null, ys), yhi = Math.max.apply(null, ys); | |
| const X = (v) => pad + (v - xlo) / ((xhi - xlo) || 1) * (W - pad - 12); | |
| const Y = (v) => H - 26 - (v - ylo) / ((yhi - ylo) || 1) * (H - 52); | |
| ctx.strokeStyle = theme.muted; ctx.globalAlpha = .5; ctx.beginPath(); ctx.moveTo(pad, H - 26); ctx.lineTo(W - 6, H - 26); ctx.stroke(); ctx.globalAlpha = 1; | |
| const upto = Math.max(1, Math.floor(pts.length * prog)); | |
| ctx.strokeStyle = theme.accent; ctx.lineWidth = 3; ctx.beginPath(); | |
| for (let i = 0; i < upto; i++) (i ? ctx.lineTo : ctx.moveTo).call(ctx, X(pts[i][0]), Y(pts[i][1])); | |
| ctx.stroke(); | |
| const last = pts[upto - 1]; ctx.fillStyle = theme.accent2; ctx.beginPath(); ctx.arc(X(last[0]), Y(last[1]), 5, 0, 7); ctx.fill(); | |
| if (spec.title) { ctx.fillStyle = theme.muted; ctx.font = "12px system-ui"; ctx.textAlign = "left"; ctx.fillText(spec.title, pad, 16); } | |
| } | |
| let ctrl = null, raf = null; | |
| if (isLine) { | |
| root.appendChild(cv); root.appendChild(log); | |
| k.logLine(log, "training curve β " + (spec.y || "value") + " over time", true); line(1); | |
| function draw(done) { stop(); const t0 = performance.now(); (function tick(t) { const u = Math.min(1, (t - t0) / 2600); line(u); if (u >= 1) { raf = null; if (done) done(); return; } raf = requestAnimationFrame(tick); })(t0); } | |
| function stop() { if (raf) { cancelAnimationFrame(raf); raf = null; } } | |
| root.appendChild(k.row([k.button("βΆ Draw", () => draw())], "ctrls")); | |
| return { play: (d) => draw(d), stop, reset: () => { stop(); line(1); } }; | |
| } | |
| function render(step) { | |
| bars(step, 1); | |
| k.clear(log); log.appendChild(k.el("div", { class: "steps-h" }, [(spec.title || "results") + " (" + step + " / " + cats.length + ")"])); | |
| for (let i = 0; i < step; i++) { | |
| const tag = i === hiIdx ? " β this paper" : ""; | |
| k.logLine(log, cats[i] + " = <b>" + k.fmt(vals[i]) + spec.unit + "</b>" + tag, i === step - 1); | |
| } | |
| if (step >= cats.length && hiIdx >= 0 && best != null) { | |
| const d = vals[hiIdx] - best.v; | |
| k.logLine(log, "β² <b>" + (d >= 0 ? "+" : "") + k.fmt(d) + spec.unit + "</b> vs best prior (" + best.name + ")", true); | |
| } | |
| } | |
| ctrl = k.stepper(new Array(cats.length), render); | |
| root.appendChild(cv); | |
| root.appendChild(k.row([k.button("βΈ Step", () => ctrl.next()), k.button("βΆ Play", () => ctrl.play(1050)), k.button("βΊ Reset", () => ctrl.reset(), "ghost")], "ctrls")); | |
| root.appendChild(log); | |
| return k.player(ctrl, 950); | |
| } | |
| // ββ metric cards (count up) βββββββββββββββββββββββββββββββββββββββββββββββ | |
| function mountMetric(root, concept) { | |
| const spec = norm((concept && concept.params) || {}); | |
| const wrap = k.el("div", { class: "metrics" }); | |
| const log = k.panel("steps"); | |
| root.appendChild(wrap); root.appendChild(log); | |
| function render(step) { | |
| k.clear(wrap); k.clear(log); log.appendChild(k.el("div", { class: "steps-h" }, ["headline metrics (" + step + " / " + spec.metrics.length + ")"])); | |
| spec.metrics.slice(0, step).forEach((mtr, i) => { | |
| wrap.appendChild(k.el("div", { class: "metric" }, [ | |
| k.el("div", { class: "m-val" }, [k.fmt(Number(mtr.value)) + (mtr.unit || "")]), | |
| k.el("div", { class: "m-name" }, [String(mtr.name || "")]), | |
| mtr.delta != null ? k.el("div", { class: "m-delta" }, ["β² " + String(mtr.delta)]) : "", | |
| ].filter(Boolean))); | |
| k.logLine(log, (mtr.name || "metric") + " = <b>" + k.fmt(Number(mtr.value)) + (mtr.unit || "") + "</b>" + (mtr.delta != null ? " (" + mtr.delta + ")" : ""), i === step - 1); | |
| }); | |
| } | |
| const ctrl = k.stepper(new Array(spec.metrics.length), render); | |
| root.appendChild(k.row([k.button("βΈ Step", () => ctrl.next()), k.button("βΆ Play", () => ctrl.play(1150)), k.button("βΊ Reset", () => ctrl.reset(), "ghost")], "ctrls")); | |
| return k.player(ctrl, 1080); | |
| } | |
| function bestOther(vals, hiIdx, cats) { | |
| let b = null; | |
| vals.forEach((v, i) => { if (i !== hiIdx && (b == null || v > b.v)) b = { v, name: (cats && cats[i]) || ("#" + i) }; }); | |
| return b; | |
| } | |
| function mount(root, concept, theme) { | |
| const kind = String(((concept && concept.params) || {}).kind || (concept && concept.primitive) || "bar").toLowerCase(); | |
| return kind === "metric" ? mountMetric(root, concept) : mountChart(root, concept, theme); | |
| } | |
| ["chart", "results", "bar", "line", "metric", "graph"].forEach((key) => { MANIMO.registry[key] = { title: "Results", mount }; }); | |
| })(); | |