Result
y_hat = ${fmt(last.theta[0])} + ${fmt(last.theta[1])}*x1
parameters: 2 (theta0, theta1)
initial cost: ${first.cost.toFixed(6)}
final cost: ${last.cost.toFixed(6)}
`;
}
function renderStepSummary() {
const s = state.steps[state.currentStep];
stepSummary.innerHTML = `
Current Step ${s.step}
cost = ${s.cost.toFixed(6)}
theta = [${s.theta.map(fmt).join(", ")}]
gradient = [${s.grad.map(fmt).join(", ")}]
`;
}
function renderLogTable() {
gdLogBody.innerHTML = "";
for (const s of state.steps) {
const tr = document.createElement("tr");
if (s.step === state.currentStep) tr.classList.add("active-log-row");
const cells = [
String(s.step),
s.cost.toFixed(6),
`[${s.theta.map(fmt).join(", ")}]`,
`[${s.grad.map(fmt).join(", ")}]`,
];
cells.forEach((txt) => {
const td = document.createElement("td");
td.textContent = txt;
tr.appendChild(td);
});
gdLogBody.appendChild(tr);
}
}
function drawAxes2D(ctx, width, height, pad, xLabel, yLabel) {
ctx.strokeStyle = "#8f9ba1";
ctx.lineWidth = 1.2;
ctx.beginPath();
ctx.moveTo(pad, height - pad);
ctx.lineTo(width - pad, height - pad);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(pad, height - pad);
ctx.lineTo(pad, pad);
ctx.stroke();
ctx.fillStyle = "#4c5961";
ctx.font = "13px 'Courier New', monospace";
ctx.fillText(xLabel, width - pad - 50, height - pad + 22);
ctx.fillText(yLabel, pad - 30, pad - 10);
}
function startPlayback() {
if (state.timer || !state.steps.length) return;
playGdBtn.textContent = "Pause";
state.timer = setInterval(() => {
if (!document.body.contains(gdStep)) {
stopPlayback();
return;
}
if (state.currentStep >= state.steps.length - 1) {
stopPlayback();
return;
}
state.currentStep += 1;
gdStep.value = String(state.currentStep);
renderAll();
}, 220);
}
function stopPlayback() {
if (!state.timer) {
playGdBtn.textContent = "Play";
return;
}
clearInterval(state.timer);
state.timer = null;
playGdBtn.textContent = "Play";
}
function fmt(n) {
return Number(n).toFixed(4);
}
function rand(min, max) {
return min + Math.random() * (max - min);
}
function clampInt(v, min, max, fallback) {
if (!Number.isFinite(v)) return fallback;
return Math.max(min, Math.min(max, Math.round(v)));
}
function clampNum(v, min, max, fallback) {
if (!Number.isFinite(v)) return fallback;
return Math.max(min, Math.min(max, v));
}
init();