/* Function plotter — pick an activation/curve, drag the input x, and watch z = w·x + b feed through * f(z); the curve draws itself and a tracer marks (x, f(x)). Mirrors the Manim `plot` animation. */ (function () { const k = MANIMO.kit; const FNS = { sigmoid: { f: (z) => 1 / (1 + Math.exp(-z)), tex: "σ(z) = 1 / (1 + e⁻ᶻ)", yr: [0, 1] }, tanh: { f: (z) => Math.tanh(z), tex: "tanh(z)", yr: [-1, 1] }, relu: { f: (z) => Math.max(0, z), tex: "ReLU(z) = max(0, z)", yr: [0, 4] }, decay: { f: (z) => Math.exp(-Math.max(0, z)), tex: "e⁻ᶻ (loss decay)", yr: [0, 1] }, linear: { f: (z) => z, tex: "z", yr: [-4, 4] }, }; function mount(root, concept, theme) { const p = (concept && concept.params) || {}; let name = FNS[p.shape] ? p.shape : (p.shape === "parabola" ? "relu" : "sigmoid"); let w = p.w != null ? p.w : 1, b = p.b != null ? p.b : 0, x = 0; const cv = document.createElement("canvas"); cv.width = 460; cv.height = 240; cv.className = "plot"; cv.setAttribute("role", "img"); cv.setAttribute("aria-label", "Activation function plot with a draggable input tracer"); const readout = k.el("div", { class: "readout" }); const sel = k.el("select", { class: "sel", onchange: (e) => { name = e.target.value; draw(); } }, Object.keys(FNS).map((nm) => k.el("option", { value: nm }, [nm]))); sel.value = name; const sx = slider("x", -4, 4, x, (v) => { x = v; draw(); }); const sw = slider("w", -3, 3, w, (v) => { w = v; draw(); }); const sb = slider("b", -3, 3, b, (v) => { b = v; draw(); }); function draw() { const fn = FNS[name], W = cv.width, H = cv.height, ctx = cv.getContext("2d"); const xr = [-4, 4], yr = fn.yr; const X = (xv) => (xv - xr[0]) / (xr[1] - xr[0]) * (W - 30) + 20; const Y = (yv) => H - 24 - (yv - yr[0]) / (yr[1] - yr[0]) * (H - 40); ctx.fillStyle = theme.bg; ctx.fillRect(0, 0, W, H); // axes ctx.strokeStyle = theme.muted; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(20, Y(Math.max(yr[0], 0))); ctx.lineTo(W - 8, Y(Math.max(yr[0], 0))); ctx.moveTo(X(0), 16); ctx.lineTo(X(0), H - 18); ctx.stroke(); // curve ctx.strokeStyle = theme.accent; ctx.lineWidth = 3; ctx.beginPath(); for (let i = 0; i <= 200; i++) { const xv = xr[0] + (xr[1] - xr[0]) * i / 200; const z = w * xv + b; const yv = fn.f(z); (i ? ctx.lineTo : ctx.moveTo).call(ctx, X(xv), Y(yv)); } ctx.stroke(); // tracer at x const z = w * x + b, y = fn.f(z); ctx.fillStyle = theme.accent2; ctx.beginPath(); ctx.arc(X(x), Y(y), 6, 0, 7); ctx.fill(); ctx.strokeStyle = theme.accent2; ctx.setLineDash([4, 4]); ctx.beginPath(); ctx.moveTo(X(x), Y(yr[0])); ctx.lineTo(X(x), Y(y)); ctx.lineTo(X(0), Y(y)); ctx.stroke(); ctx.setLineDash([]); readout.innerHTML = "z = w·x + b = " + k.fmt(w) + "·" + k.fmt(x) + " + " + k.fmt(b) + " = " + k.fmt(z) + " → " + fn.tex + " = " + k.fmt(y) + ""; } let raf = null; function stop() { if (raf) { cancelAnimationFrame(raf); raf = null; } } function play() { stop(); const t0 = performance.now(); (function tick(t) { x = -4 + 8 * (((t - t0) / 3400) % 1); sx.set(x); draw(); raf = requestAnimationFrame(tick); })(t0); } // one-shot sweep for "Play all": glide x left→right once (slowly, so the curve reads), then signal done. function playOnce(done) { stop(); const dur = 3400, t0 = performance.now(); (function tick(t) { const u = (t - t0) / dur; if (u >= 1) { x = 4; sx.set(x); draw(); raf = null; if (done) done(); return; } x = -4 + 8 * u; sx.set(x); draw(); raf = requestAnimationFrame(tick); })(t0); } root.appendChild(k.row([k.el("label", { class: "fld" }, ["function ", sel]), readout], "ctrls")); root.appendChild(cv); root.appendChild(k.row([sx.node, sw.node, sb.node], "sliders")); root.appendChild(k.row([k.button("▶ Sweep x", play), k.button("⏹ Stop", stop, "ghost")], "ctrls")); draw(); return { play: playOnce, stop, reset: () => { stop(); x = 0; sx.set(0); draw(); } }; } function slider(label, lo, hi, val, on) { const out = MANIMO.kit.el("span", { class: "sval" }, [MANIMO.kit.fmt(val)]); const inp = MANIMO.kit.el("input", { type: "range", min: lo, max: hi, step: 0.05, value: val, oninput: (e) => { out.textContent = MANIMO.kit.fmt(+e.target.value); on(+e.target.value); } }); return { node: MANIMO.kit.el("label", { class: "fld" }, [label + " ", inp, out]), set: (v) => { inp.value = v; out.textContent = MANIMO.kit.fmt(v); } }; } MANIMO.registry["plot"] = { title: "Function Plot", mount }; })();