<!-- Newton-Raphson for a 2D system — solve f1(x,y)=0 and f2(x,y)=0 simultaneously -->
<div class="hint">Click the canvas to set a starting point, then step through Newton-Raphson iterations. Blue = start, orange = intermediate, green = converged root.</div>
<canvas id="cnv" width="340" height="280" title="Newton-Raphson iteration canvas"></canvas>
<div class="controls">
<button id="btn-step" type="button">Step</button>
<button id="btn-run" type="button">Run</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<div id="status" class="status"></div>
<div id="iter-log" class="iter-log"></div>
/* layout and colour scheme */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; background: #fff; }
.hint { font-size: .85rem; color: #444; margin-bottom: .5rem; line-height: 1.5; }
canvas { display: block; border: 1px solid #cdd9e3; border-radius: 8px;
cursor: crosshair; margin-bottom: .5rem; max-width: 100%; }
.controls { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .5rem; }
button { font: 600 13px system-ui; padding: .4rem .85rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.status { font-size: .9rem; font-weight: 600; min-height: 1.3em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.status.info { color: #1d3557; }
.iter-log { font-size: .78rem; color: #555; margin-top: .3rem; max-height: 80px;
overflow-y: auto; line-height: 1.6; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
/* System of two nonlinear equations */
/* f1(x,y) = x^2 + y^2 - 4 (circle r=2) */
/* f2(x,y) = x*y - 1 (hyperbola) */
/* Jacobian of the system */
/* J = [[2x, 2y], [y, x]] */
const W = 340, H = 280;
const SCALE = 55; // pixels per unit
const OX = W / 2, OY = H / 2;
// canvas / math coordinate conversion
const toCanvas = (x, y) => [OX + x * SCALE, OY - y * SCALE];
const fromCanvas = (cx, cy) => [(cx - OX) / SCALE, (OY - cy) / SCALE];
function f1(x, y) { return x * x + y * y - 4; }
function f2(x, y) { return x * y - 1; }
// evaluate the 2x2 Jacobian at (x, y)
function jacobian(x, y) {
return [[2 * x, 2 * y], [y, x]];
}
// solve the 2x2 linear system J * delta = b via Cramer's rule
function solve2x2(J, b) {
const det = J[0][0] * J[1][1] - J[0][1] * J[1][0];
if (Math.abs(det) < 1e-14) return null; // singular or near-singular Jacobian — step cannot be computed
return [
(b[0] * J[1][1] - b[1] * J[0][1]) / det,
(J[0][0] * b[1] - J[1][0] * b[0]) / det
];
}
// iteration state
let pts = []; // list of [x,y] iterates
let running = false;
let intervalId = null;
const cnv = document.getElementById('cnv');
const ctx = cnv.getContext('2d');
// draw axes, grid, and the two curves
function drawBackground() {
ctx.clearRect(0, 0, W, H);
// light grid lines
ctx.strokeStyle = '#e8eef3';
ctx.lineWidth = 1;
for (let gx = -3; gx <= 3; gx++) {
const [cx] = toCanvas(gx, 0);
ctx.beginPath(); ctx.moveTo(cx, 0); ctx.lineTo(cx, H); ctx.stroke();
}
for (let gy = -3; gy <= 3; gy++) {
const [, cy] = toCanvas(0, gy);
ctx.beginPath(); ctx.moveTo(0, cy); ctx.lineTo(W, cy); ctx.stroke();
}
// coordinate axes
ctx.strokeStyle = '#adb1b8';
ctx.lineWidth = 1.2;
ctx.beginPath(); ctx.moveTo(0, OY); ctx.lineTo(W, OY); ctx.stroke();
ctx.beginPath(); ctx.moveTo(OX, 0); ctx.lineTo(OX, H); ctx.stroke();
// blue circle x^2+y^2=4
ctx.strokeStyle = '#3a86ff55';
ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.arc(OX, OY, 2 * SCALE, 0, 2 * Math.PI);
ctx.stroke();
// red hyperbola xy=1
ctx.strokeStyle = '#ff595e55';
ctx.lineWidth = 1.5;
for (let branch = 0; branch < 2; branch++) {
const sign = branch === 0 ? 1 : -1;
ctx.beginPath();
let first = true;
for (let ix = -3; ix <= 3; ix += 0.02) {
const iy = sign / ix;
if (!isFinite(iy) || Math.abs(iy) > 3.5) { first = true; continue; }
const [cx, cy] = toCanvas(ix, iy);
if (first) { ctx.moveTo(cx, cy); first = false; }
else ctx.lineTo(cx, cy);
}
ctx.stroke();
}
}
// draw iteration path with arrows
function drawPath() {
if (pts.length === 0) return;
// draw arrows between consecutive iterates
for (let i = 0; i + 1 < pts.length; i++) {
const [ax, ay] = toCanvas(pts[i][0], pts[i][1]);
const [bx, by] = toCanvas(pts[i + 1][0], pts[i + 1][1]);
ctx.strokeStyle = '#1d3557cc';
ctx.lineWidth = 1.8;
ctx.beginPath(); ctx.moveTo(ax, ay); ctx.lineTo(bx, by); ctx.stroke();
// arrowhead tip
const angle = Math.atan2(by - ay, bx - ax);
const hs = 7;
ctx.fillStyle = '#1d3557cc';
ctx.beginPath();
ctx.moveTo(bx, by);
ctx.lineTo(bx - hs * Math.cos(angle - 0.4), by - hs * Math.sin(angle - 0.4));
ctx.lineTo(bx - hs * Math.cos(angle + 0.4), by - hs * Math.sin(angle + 0.4));
ctx.closePath(); ctx.fill();
}
// colored dots for each iterate
for (let i = 0; i < pts.length; i++) {
const [cx, cy] = toCanvas(pts[i][0], pts[i][1]);
ctx.fillStyle = i === 0 ? '#3a86ff' : i === pts.length - 1 ? '#0a7d33' : '#ff8800';
ctx.beginPath(); ctx.arc(cx, cy, 5, 0, 2 * Math.PI); ctx.fill();
}
}
function redraw() { drawBackground(); drawPath(); }
// perform one Newton-Raphson step; returns true if more steps are possible
function newtonStep() {
if (pts.length === 0) {
setStatus('Click the canvas to pick a starting point.', 'info');
return false;
}
const [x, y] = pts[pts.length - 1];
const F = [f1(x, y), f2(x, y)];
const res = Math.sqrt(F[0] * F[0] + F[1] * F[1]);
if (res < 1e-9) {
setStatus(i18nFmt('Converged! Root ≈ (%x%, %y%)', { x: x.toFixed(6), y: y.toFixed(6) }), 'ok');
return false;
}
const J = jacobian(x, y);
const dx = solve2x2(J, [-F[0], -F[1]]);
if (!dx) {
setStatus('Jacobian is singular here — try a different starting point.', 'bad');
return false;
}
const nx = x + dx[0], ny = y + dx[1];
if (Math.abs(nx) > 6 || Math.abs(ny) > 6) {
setStatus('Iterates left the visible region — try a different starting point.', 'bad');
return false;
}
pts.push([nx, ny]);
const nres = Math.sqrt(f1(nx, ny) ** 2 + f2(nx, ny) ** 2);
appendLog(i18nFmt('#%n%: (%x%, %y%) res=%r%', { n: pts.length - 1, x: nx.toFixed(5), y: ny.toFixed(5), r: nres.toExponential(2) }));
setStatus(i18nFmt('Step %n% · residual %r%', { n: pts.length - 1, r: nres.toExponential(2) }), 'info');
redraw();
if (nres < 1e-9) {
setStatus(i18nFmt('Converged! Root ≈ (%x%, %y%)', { x: nx.toFixed(6), y: ny.toFixed(6) }), 'ok');
return false;
}
return true;
}
function setStatus(t, cls) {
const s = document.getElementById('status');
s.textContent = t; s.className = 'status ' + cls;
}
function appendLog(line) {
const el = document.getElementById('iter-log');
el.innerHTML += line + '<br>';
el.scrollTop = el.scrollHeight;
}
function doStep() { if (!newtonStep()) stopRun(); }
function startRun() {
if (running) return;
running = true;
document.getElementById('btn-run').textContent = 'Stop';
intervalId = setInterval(() => { if (!newtonStep()) stopRun(); }, 420);
}
function stopRun() {
running = false;
clearInterval(intervalId);
document.getElementById('btn-run').textContent = 'Run';
}
function doReset() {
stopRun();
pts = [];
document.getElementById('iter-log').innerHTML = '';
setStatus('Click the canvas to pick a starting point.', 'info');
redraw();
}
cnv.addEventListener('click', (e) => {
stopRun();
const r = cnv.getBoundingClientRect();
const [x, y] = fromCanvas(e.clientX - r.left, e.clientY - r.top);
pts = [[x, y]];
document.getElementById('iter-log').innerHTML = '';
setStatus(i18nFmt('Starting at (%x%, %y%) — press Step or Run.', { x: x.toFixed(3), y: y.toFixed(3) }), 'info');
redraw();
});
document.getElementById('btn-step').addEventListener('click', doStep);
document.getElementById('btn-run').addEventListener('click', () => { running ? stopRun() : startRun(); });
document.getElementById('btn-reset').addEventListener('click', doReset);
setStatus('Click the canvas to pick a starting point.', 'info');
redraw();
<!-- Newton-Raphson for a 2D system — solve f1(x,y)=0 and f2(x,y)=0 simultaneously -->
<div class="hint">Click the canvas to set a starting point, then step through Newton-Raphson iterations. Blue = start, orange = intermediate, green = converged root.</div>
<canvas id="cnv" width="340" height="280" title="Newton-Raphson iteration canvas"></canvas>
<div class="controls">
<button id="btn-step" type="button">Step</button>
<button id="btn-run" type="button">Run</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<div id="status" class="status"></div>
<div id="iter-log" class="iter-log"></div>
/* layout and colour scheme */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; background: #fff; }
.hint { font-size: .85rem; color: #444; margin-bottom: .5rem; line-height: 1.5; }
canvas { display: block; border: 1px solid #cdd9e3; border-radius: 8px;
cursor: crosshair; margin-bottom: .5rem; max-width: 100%; }
.controls { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .5rem; }
button { font: 600 13px system-ui; padding: .4rem .85rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.status { font-size: .9rem; font-weight: 600; min-height: 1.3em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.status.info { color: #1d3557; }
.iter-log { font-size: .78rem; color: #555; margin-top: .3rem; max-height: 80px;
overflow-y: auto; line-height: 1.6; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
/* System of two nonlinear equations */
/* f1(x,y) = x^2 + y^2 - 4 (circle r=2) */
/* f2(x,y) = x*y - 1 (hyperbola) */
/* Jacobian of the system */
/* J = [[2x, 2y], [y, x]] */
const W = 340, H = 280;
const SCALE = 55; // pixels per unit
const OX = W / 2, OY = H / 2;
// canvas / math coordinate conversion
const toCanvas = (x, y) => [OX + x * SCALE, OY - y * SCALE];
const fromCanvas = (cx, cy) => [(cx - OX) / SCALE, (OY - cy) / SCALE];
function f1(x, y) { return x * x + y * y - 4; }
function f2(x, y) { return x * y - 1; }
// evaluate the 2x2 Jacobian at (x, y)
function jacobian(x, y) {
return [[2 * x, 2 * y], [y, x]];
}
// solve the 2x2 linear system J * delta = b via Cramer's rule
function solve2x2(J, b) {
const det = J[0][0] * J[1][1] - J[0][1] * J[1][0];
if (Math.abs(det) < 1e-14) return null; // singular or near-singular Jacobian — step cannot be computed
return [
(b[0] * J[1][1] - b[1] * J[0][1]) / det,
(J[0][0] * b[1] - J[1][0] * b[0]) / det
];
}
// iteration state
let pts = []; // list of [x,y] iterates
let running = false;
let intervalId = null;
const cnv = document.getElementById('cnv');
const ctx = cnv.getContext('2d');
// draw axes, grid, and the two curves
function drawBackground() {
ctx.clearRect(0, 0, W, H);
// light grid lines
ctx.strokeStyle = '#e8eef3';
ctx.lineWidth = 1;
for (let gx = -3; gx <= 3; gx++) {
const [cx] = toCanvas(gx, 0);
ctx.beginPath(); ctx.moveTo(cx, 0); ctx.lineTo(cx, H); ctx.stroke();
}
for (let gy = -3; gy <= 3; gy++) {
const [, cy] = toCanvas(0, gy);
ctx.beginPath(); ctx.moveTo(0, cy); ctx.lineTo(W, cy); ctx.stroke();
}
// coordinate axes
ctx.strokeStyle = '#adb1b8';
ctx.lineWidth = 1.2;
ctx.beginPath(); ctx.moveTo(0, OY); ctx.lineTo(W, OY); ctx.stroke();
ctx.beginPath(); ctx.moveTo(OX, 0); ctx.lineTo(OX, H); ctx.stroke();
// blue circle x^2+y^2=4
ctx.strokeStyle = '#3a86ff55';
ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.arc(OX, OY, 2 * SCALE, 0, 2 * Math.PI);
ctx.stroke();
// red hyperbola xy=1
ctx.strokeStyle = '#ff595e55';
ctx.lineWidth = 1.5;
for (let branch = 0; branch < 2; branch++) {
const sign = branch === 0 ? 1 : -1;
ctx.beginPath();
let first = true;
for (let ix = -3; ix <= 3; ix += 0.02) {
const iy = sign / ix;
if (!isFinite(iy) || Math.abs(iy) > 3.5) { first = true; continue; }
const [cx, cy] = toCanvas(ix, iy);
if (first) { ctx.moveTo(cx, cy); first = false; }
else ctx.lineTo(cx, cy);
}
ctx.stroke();
}
}
// draw iteration path with arrows
function drawPath() {
if (pts.length === 0) return;
// draw arrows between consecutive iterates
for (let i = 0; i + 1 < pts.length; i++) {
const [ax, ay] = toCanvas(pts[i][0], pts[i][1]);
const [bx, by] = toCanvas(pts[i + 1][0], pts[i + 1][1]);
ctx.strokeStyle = '#1d3557cc';
ctx.lineWidth = 1.8;
ctx.beginPath(); ctx.moveTo(ax, ay); ctx.lineTo(bx, by); ctx.stroke();
// arrowhead tip
const angle = Math.atan2(by - ay, bx - ax);
const hs = 7;
ctx.fillStyle = '#1d3557cc';
ctx.beginPath();
ctx.moveTo(bx, by);
ctx.lineTo(bx - hs * Math.cos(angle - 0.4), by - hs * Math.sin(angle - 0.4));
ctx.lineTo(bx - hs * Math.cos(angle + 0.4), by - hs * Math.sin(angle + 0.4));
ctx.closePath(); ctx.fill();
}
// colored dots for each iterate
for (let i = 0; i < pts.length; i++) {
const [cx, cy] = toCanvas(pts[i][0], pts[i][1]);
ctx.fillStyle = i === 0 ? '#3a86ff' : i === pts.length - 1 ? '#0a7d33' : '#ff8800';
ctx.beginPath(); ctx.arc(cx, cy, 5, 0, 2 * Math.PI); ctx.fill();
}
}
function redraw() { drawBackground(); drawPath(); }
// perform one Newton-Raphson step; returns true if more steps are possible
function newtonStep() {
if (pts.length === 0) {
setStatus('Click the canvas to pick a starting point.', 'info');
return false;
}
const [x, y] = pts[pts.length - 1];
const F = [f1(x, y), f2(x, y)];
const res = Math.sqrt(F[0] * F[0] + F[1] * F[1]);
if (res < 1e-9) {
setStatus(i18nFmt('Converged! Root ≈ (%x%, %y%)', { x: x.toFixed(6), y: y.toFixed(6) }), 'ok');
return false;
}
const J = jacobian(x, y);
const dx = solve2x2(J, [-F[0], -F[1]]);
if (!dx) {
setStatus('Jacobian is singular here — try a different starting point.', 'bad');
return false;
}
const nx = x + dx[0], ny = y + dx[1];
if (Math.abs(nx) > 6 || Math.abs(ny) > 6) {
setStatus('Iterates left the visible region — try a different starting point.', 'bad');
return false;
}
pts.push([nx, ny]);
const nres = Math.sqrt(f1(nx, ny) ** 2 + f2(nx, ny) ** 2);
appendLog(i18nFmt('#%n%: (%x%, %y%) res=%r%', { n: pts.length - 1, x: nx.toFixed(5), y: ny.toFixed(5), r: nres.toExponential(2) }));
setStatus(i18nFmt('Step %n% · residual %r%', { n: pts.length - 1, r: nres.toExponential(2) }), 'info');
redraw();
if (nres < 1e-9) {
setStatus(i18nFmt('Converged! Root ≈ (%x%, %y%)', { x: nx.toFixed(6), y: ny.toFixed(6) }), 'ok');
return false;
}
return true;
}
function setStatus(t, cls) {
const s = document.getElementById('status');
s.textContent = t; s.className = 'status ' + cls;
}
function appendLog(line) {
const el = document.getElementById('iter-log');
el.innerHTML += line + '<br>';
el.scrollTop = el.scrollHeight;
}
function doStep() { if (!newtonStep()) stopRun(); }
function startRun() {
if (running) return;
running = true;
document.getElementById('btn-run').textContent = 'Stop';
intervalId = setInterval(() => { if (!newtonStep()) stopRun(); }, 420);
}
function stopRun() {
running = false;
clearInterval(intervalId);
document.getElementById('btn-run').textContent = 'Run';
}
function doReset() {
stopRun();
pts = [];
document.getElementById('iter-log').innerHTML = '';
setStatus('Click the canvas to pick a starting point.', 'info');
redraw();
}
cnv.addEventListener('click', (e) => {
stopRun();
const r = cnv.getBoundingClientRect();
const [x, y] = fromCanvas(e.clientX - r.left, e.clientY - r.top);
pts = [[x, y]];
document.getElementById('iter-log').innerHTML = '';
setStatus(i18nFmt('Starting at (%x%, %y%) — press Step or Run.', { x: x.toFixed(3), y: y.toFixed(3) }), 'info');
redraw();
});
document.getElementById('btn-step').addEventListener('click', doStep);
document.getElementById('btn-run').addEventListener('click', () => { running ? stopRun() : startRun(); });
document.getElementById('btn-reset').addEventListener('click', doReset);
setStatus('Click the canvas to pick a starting point.', 'info');
redraw();
Notice how the steps grow short very quickly — that is quadratic convergence at work. The system has four roots (the intersections of a circle of radius 2 and a hyperbola xy=1); the root you reach depends entirely on where you start.
Comments
Loading comments...