{
const {V, M, Hh, Nn, idx, win, fi, I, sNa, sK, rate} = hhState, c = hhCanvas, fg = exFg();
const blue = "#1f77b4", orange = "#ff7f0e", W = 760, H = 490, n = V.length;
c.clearRect(0, 0, W, H); c.font = exFont;
const mL = 70, trR = 480;
const top = {y0: 12, h: 150, lo: -90, hi: 50};
const gates = [
{arr: M, col: "#d62728", name: "m", y0: 180, h: 66, desc: "sodium activation, fast"},
{arr: Hh, col: "#2ca02c", name: "h", y0: 258, h: 66, desc: "sodium inactivation, slow"},
{arr: Nn, col: "#9467bd", name: "n", y0: 336, h: 66, desc: "potassium activation, slow"}
];
const sx = i => mL + (trR - mL) * i / (n - 1), sy = (p, v) => p.y0 + p.h * (1 - (v - p.lo) / (p.hi - p.lo));
axisBox(c, mL, top.y0, trR - mL, top.h);
for (const g of gates) axisBox(c, mL, g.y0, trR - mL, g.h);
// oscilloscope sweep: the traces stay in place and are overwritten at the cursor
const sweep = (arr, yf) => { for (const [a, b] of [[0, idx], [idx + 1, n - 1]]) { if (b <= a) continue; c.beginPath(); for (let i = a; i <= b; i++) i === a ? c.moveTo(sx(i), yf(arr[i])) : c.lineTo(sx(i), yf(arr[i])); c.stroke(); } };
c.strokeStyle = blue; c.lineWidth = 1.5; sweep(V, v => sy(top, v));
for (const g of gates) { const p = {y0: g.y0, h: g.h, lo: 0, hi: 1}; c.strokeStyle = g.col; c.lineWidth = 1.5; sweep(g.arr, v => sy(p, v)); }
c.strokeStyle = "#bbb"; c.lineWidth = 1;
for (const p of [top, ...gates]) { c.beginPath(); c.moveTo(sx(idx), p.y0); c.lineTo(sx(idx), p.y0 + p.h); c.stroke(); }
c.fillStyle = fg; c.textAlign = "right";
c.fillText("V (mV)", mL - 8, top.y0 + top.h / 2 + 5); c.fillText("50", mL - 8, sy(top, 50) + 5); c.fillText("-65", mL - 8, sy(top, -65) + 5);
for (const g of gates) { c.fillStyle = g.col; c.fillText(g.name, mL - 8, g.y0 + g.h / 2 + 5); c.fillStyle = fg; c.fillText("1", mL - 8, g.y0 + 5); c.fillText("0", mL - 8, g.y0 + g.h + 2); }
const gBottom = gates[gates.length - 1].y0 + gates[gates.length - 1].h;
c.textAlign = "center"; c.fillText(`${win} ms sweep`, (mL + trR) / 2, gBottom + 24);
c.textAlign = "left"; c.fillText(`${rate.toFixed(0)} spikes / s`, mL + 8, top.y0 + 20);
// legend below the axes
let lx = mL, ly = gBottom + 48;
for (const g of gates) { const txt = `${g.name}: ${g.desc}`; c.fillStyle = g.col; c.fillText(txt, lx, ly); lx += c.measureText(txt).width + 18; }
// f-I curve, right
const fP = {x0: 560, y0: 12, w: 180, h: 390}, Imax = 200, fmax = 200;
const fx = I => fP.x0 + fP.w * I / Imax, fy = f => fP.y0 + fP.h * (1 - Math.min(f, fmax) / fmax);
axisBox(c, fP.x0, fP.y0, fP.w, fP.h);
c.fillStyle = fg; c.textAlign = "center";
c.fillText("I (µA/cm²)", fP.x0 + fP.w / 2, fP.y0 + fP.h + 24);
c.fillText("0", fx(0), fP.y0 + fP.h + 24); c.fillText(Imax, fx(Imax), fP.y0 + fP.h + 24);
c.save(); c.translate(fP.x0 - 40, fP.y0 + fP.h / 2); c.rotate(-Math.PI / 2); c.fillText("spikes / s", 0, 0); c.restore();
c.textAlign = "right"; c.fillText("0", fP.x0 - 6, fy(0) + 5); c.fillText(fmax, fP.x0 - 6, fy(fmax) + 5);
for (const [, p] of fi) { c.fillStyle = p.std ? blue : orange; c.beginPath(); c.arc(fx(p.I), fy(p.rate), 5, 0, 2 * Math.PI); c.fill(); }
c.strokeStyle = sNa === 1 && sK === 1 ? blue : orange; c.lineWidth = 2; c.beginPath(); c.arc(fx(I), fy(rate), 8, 0, 2 * Math.PI); c.stroke();
c.textAlign = "left"; c.fillStyle = blue; c.fillText("standard channels", fP.x0 + 8, fP.y0 + 22);
c.fillStyle = orange; c.fillText("modified channels", fP.x0 + 8, fP.y0 + 44);
return c.canvas;
}