{
const {V, Iarr, win, fi, thr, tau, sig, I0, R, Vrest, refr, rate} = lifState;
const blue = "#1f77b4", orange = "#ff7f0e", c = lifCanvas, fg = exFg();
const W = 760, H = 360, n = V.length;
c.clearRect(0, 0, W, H); c.font = exFont;
// traces, left
const mL = 80, trR = 470;
const top = {y0: 12, h: 80, lo: -10, hi: 35};
const bot = {y0: 112, h: 200, lo: -80, hi: 30};
const sx = i => mL + (trR - mL) * i / (n - 1);
const sy = (p, v) => p.y0 + p.h * (1 - (v - p.lo) / (p.hi - p.lo));
for (const p of [top, bot]) axisBox(c, mL, p.y0, trR - mL, p.h);
c.fillStyle = fg; c.textAlign = "right";
c.fillText("I (nA)", mL - 8, top.y0 + top.h / 2 + 5);
c.fillText("V (mV)", mL - 8, bot.y0 + bot.h / 2 + 5);
c.fillText(thr, mL - 8, sy(bot, thr) + 5); c.fillText("-65", mL - 8, sy(bot, -65) + 5);
c.textAlign = "center"; c.fillText(`last ${win} ms`, (mL + trR) / 2, H - 14);
c.textAlign = "right"; c.fillText(`${rate.toFixed(0)} spikes / s`, trR - 8, bot.y0 + 20);
c.setLineDash([4, 4]); c.strokeStyle = "red"; c.beginPath(); c.moveTo(mL, sy(bot, thr)); c.lineTo(trR, sy(bot, thr)); c.stroke(); c.setLineDash([]);
c.strokeStyle = "#888"; c.lineWidth = 1; c.beginPath();
for (let i = 0; i < n; i++) { const y = sy(top, Math.max(top.lo, Math.min(top.hi, Iarr[i]))); i ? c.lineTo(sx(i), y) : c.moveTo(sx(0), y); }
c.stroke();
c.strokeStyle = sig > 0 ? orange : blue; c.lineWidth = 1.5; c.beginPath();
for (let i = 0; i < n; i++) i ? c.lineTo(sx(i), sy(bot, V[i])) : c.moveTo(sx(0), sy(bot, V[0]));
c.stroke();
// f-I curve, right
const fP = {x0: 560, y0: 12, w: 180, h: 300};
const Imax = 25, fmax = Math.max(100, ...Array.from(fi.values()).map(p => p.rate)) * 1.1;
const fx = I => fP.x0 + fP.w * I / Imax, fy = f => fP.y0 + fP.h * (1 - f / fmax);
axisBox(c, fP.x0, fP.y0, fP.w, fP.h);
c.fillStyle = fg; c.textAlign = "center";
c.fillText("input (nA)", fP.x0 + fP.w / 2, H - 14);
c.fillText("0", fx(0), fP.y0 + fP.h + 20); c.fillText(Imax, fx(Imax), fP.y0 + fP.h + 20);
c.save(); c.translate(fP.x0 - 44, 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.toFixed(0), fP.x0 - 6, fy(fmax) + 5);
c.setLineDash([3, 3]); c.strokeStyle = blue; c.lineWidth = 1; c.beginPath(); let started = false;
for (let I = 0; I <= Imax; I += 0.1) {
const d = R * I - (thr - Vrest); if (d <= 0) continue;
const f = Math.min(fmax, 1000 / (refr + tau * Math.log(R * I / d)));
started ? c.lineTo(fx(I), fy(f)) : c.moveTo(fx(I), fy(f)); started = true;
}
c.stroke(); c.setLineDash([]);
for (const [k, p] of fi) { c.fillStyle = p.noisy ? orange : blue; c.beginPath(); c.arc(fx(p.I), fy(p.rate), 5, 0, 2 * Math.PI); c.fill(); }
c.strokeStyle = sig > 0 ? orange : blue; c.lineWidth = 2; c.beginPath(); c.arc(fx(I0), fy(Math.min(rate, fmax)), 8, 0, 2 * Math.PI); c.stroke();
c.textAlign = "left"; c.fillStyle = blue; c.fillText("noise-free", fP.x0 + 8, fP.y0 + 22);
c.fillStyle = orange; c.fillText(sig > 0 ? `noise ${sig}` : "noise (off)", fP.x0 + 8, fP.y0 + 44);
return c.canvas;
}