Spaces:
Running
Running
| const canvas = document.getElementById("simplePlot"); | |
| const ctx = canvas.getContext("2d"); | |
| const zSlider = document.getElementById("z"); | |
| const zValue = document.getElementById("zValue"); | |
| const showHalf = document.getElementById("showHalf"); | |
| const showFormula = document.getElementById("showFormula"); | |
| const resetBtn = document.getElementById("resetSimple"); | |
| const animateBtn = document.getElementById("animateSimple"); | |
| const stats = document.getElementById("simpleStats"); | |
| const MARGIN = { left: 72, right: 24, top: 24, bottom: 58 }; | |
| const Z_MIN = -10; | |
| const Z_MAX = 10; | |
| let timer = null; | |
| let direction = 1; | |
| function sigmoid(z) { | |
| return 1 / (1 + Math.exp(-z)); | |
| } | |
| function zToX(z) { | |
| const width = canvas.width - MARGIN.left - MARGIN.right; | |
| return MARGIN.left + ((z - Z_MIN) / (Z_MAX - Z_MIN)) * width; | |
| } | |
| function yToCanvas(y) { | |
| const height = canvas.height - MARGIN.top - MARGIN.bottom; | |
| return canvas.height - MARGIN.bottom - y * height; | |
| } | |
| function drawAxes() { | |
| const left = MARGIN.left; | |
| const right = canvas.width - MARGIN.right; | |
| const top = MARGIN.top; | |
| const bottom = canvas.height - MARGIN.bottom; | |
| ctx.strokeStyle = "#c9d9df"; | |
| ctx.lineWidth = 1; | |
| for (let z = -10; z <= 10; z += 2) { | |
| const x = zToX(z); | |
| ctx.beginPath(); | |
| ctx.moveTo(x, top); | |
| ctx.lineTo(x, bottom); | |
| ctx.stroke(); | |
| } | |
| for (let y = 0; y <= 1.001; y += 0.1) { | |
| const py = yToCanvas(y); | |
| ctx.beginPath(); | |
| ctx.moveTo(left, py); | |
| ctx.lineTo(right, py); | |
| ctx.stroke(); | |
| } | |
| ctx.strokeStyle = "#14343f"; | |
| ctx.lineWidth = 1.4; | |
| ctx.beginPath(); | |
| ctx.moveTo(left, yToCanvas(0)); | |
| ctx.lineTo(right, yToCanvas(0)); | |
| ctx.stroke(); | |
| ctx.beginPath(); | |
| ctx.moveTo(zToX(0), top); | |
| ctx.lineTo(zToX(0), bottom); | |
| ctx.stroke(); | |
| ctx.fillStyle = "#14343f"; | |
| ctx.font = "13px 'Avenir Next', sans-serif"; | |
| for (let z = -10; z <= 10; z += 2) { | |
| ctx.fillText(String(z), zToX(z) - 8, yToCanvas(0) + 19); | |
| } | |
| for (let y = 0; y <= 1.001; y += 0.2) { | |
| ctx.fillText(y.toFixed(1), left - 40, yToCanvas(y) + 4); | |
| } | |
| ctx.fillText("z", right - 12, yToCanvas(0) + 38); | |
| ctx.fillText("y", zToX(0) + 12, top + 12); | |
| } | |
| function drawSigmoid() { | |
| ctx.strokeStyle = "#0a8f7b"; | |
| ctx.lineWidth = 3; | |
| ctx.beginPath(); | |
| const steps = 600; | |
| for (let i = 0; i <= steps; i += 1) { | |
| const z = Z_MIN + (i / steps) * (Z_MAX - Z_MIN); | |
| const y = sigmoid(z); | |
| const x = zToX(z); | |
| const py = yToCanvas(y); | |
| if (i === 0) ctx.moveTo(x, py); | |
| else ctx.lineTo(x, py); | |
| } | |
| ctx.stroke(); | |
| } | |
| function drawHalfLine() { | |
| ctx.strokeStyle = "#d8534f"; | |
| ctx.lineWidth = 1.3; | |
| ctx.setLineDash([7, 5]); | |
| ctx.beginPath(); | |
| ctx.moveTo(MARGIN.left, yToCanvas(0.5)); | |
| ctx.lineTo(canvas.width - MARGIN.right, yToCanvas(0.5)); | |
| ctx.stroke(); | |
| ctx.setLineDash([]); | |
| } | |
| function drawProbe(z) { | |
| const y = sigmoid(z); | |
| const px = zToX(z); | |
| const py = yToCanvas(y); | |
| ctx.strokeStyle = "#14343f"; | |
| ctx.setLineDash([4, 4]); | |
| ctx.beginPath(); | |
| ctx.moveTo(px, yToCanvas(0)); | |
| ctx.lineTo(px, py); | |
| ctx.stroke(); | |
| ctx.setLineDash([]); | |
| ctx.fillStyle = "#14343f"; | |
| ctx.beginPath(); | |
| ctx.arc(px, py, 6, 0, Math.PI * 2); | |
| ctx.fill(); | |
| if (showFormula.checked) { | |
| ctx.font = "13px 'Avenir Next', sans-serif"; | |
| ctx.fillText(`z=${z.toFixed(2)}, y=${y.toFixed(4)}`, px + 8, py - 8); | |
| } | |
| } | |
| function renderStats(z) { | |
| const y = sigmoid(z); | |
| const inverse = Math.log(y / (1 - y)); | |
| stats.innerHTML = ` | |
| <span>Function: y = 1 / (1 + np.exp(-z))</span> | |
| <span>Current z: ${z.toFixed(2)}</span> | |
| <span>Output y: ${y.toFixed(4)}</span> | |
| <span>Cross-check logit(y): ${inverse.toFixed(4)}</span> | |
| `; | |
| } | |
| function render() { | |
| const z = Number(zSlider.value); | |
| zValue.textContent = z.toFixed(2); | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| drawAxes(); | |
| drawSigmoid(); | |
| if (showHalf.checked) drawHalfLine(); | |
| drawProbe(z); | |
| renderStats(z); | |
| } | |
| function stopAnimation() { | |
| if (timer) { | |
| clearInterval(timer); | |
| timer = null; | |
| } | |
| animateBtn.textContent = "Animate z"; | |
| } | |
| function startAnimation() { | |
| if (timer) return; | |
| animateBtn.textContent = "Stop Animation"; | |
| timer = setInterval(() => { | |
| let next = Number(zSlider.value) + direction * 0.18; | |
| if (next >= Z_MAX) { | |
| next = Z_MAX; | |
| direction = -1; | |
| } else if (next <= Z_MIN) { | |
| next = Z_MIN; | |
| direction = 1; | |
| } | |
| zSlider.value = next; | |
| render(); | |
| }, 30); | |
| } | |
| function reset() { | |
| zSlider.value = 0; | |
| showHalf.checked = true; | |
| showFormula.checked = true; | |
| stopAnimation(); | |
| render(); | |
| } | |
| zSlider.addEventListener("input", render); | |
| showHalf.addEventListener("input", render); | |
| showFormula.addEventListener("input", render); | |
| resetBtn.addEventListener("click", reset); | |
| animateBtn.addEventListener("click", () => { | |
| if (timer) stopAnimation(); | |
| else startAnimation(); | |
| }); | |
| reset(); | |