// VIZ 1: Path-dependence explorer
// Three simultaneous paths from A=0 to B=1+i; trace slider; integrand selector;
// running-total bars for Re and Im parts. Key moment: all three match for analytic f.
{
const W = 320, H = 300, SCALE = 110, OX = 40, OY = 260;
const svgNS = "http://www.w3.org/2000/svg";
const STEPS = 400;
const intSelector = Inputs.select(
["f(z) = z (analytic)", "f(z) = z\u0305 (not analytic)", "f(z) = |z|\u00b2 (not analytic)"],
{ value: "f(z) = z (analytic)", label: "Integrand" }
);
const traceSlider = Inputs.range([0, 1], { value: 1, step: 0.005, label: "Trace t" });
function toSx(re) { return OX + re * SCALE; }
function toSy(im) { return OY - im * SCALE; }
// Path parametrisations: t in [0,1], endpoint B = 1+i
function pathStraight(t) { return [t, t]; }
function pathRightAngle(t) {
if (t <= 0.5) return [2 * t, 0];
return [1, 2 * (t - 0.5)];
}
function pathSemicircle(t) {
// Arc from 0 to 1+i via midpoint 0.5 + 0.5i offset by a semicircular bulge
// Use parametric: goes right along x, then curves up, centre at (0.5, 0.5)
const theta = Math.PI * t; // 0..pi maps 0 -> bottom -> 1+i via arc
const cx = 0.5, cy = 0.5, r = Math.sqrt(0.5);
const angle = -Math.PI / 2 + theta; // start at bottom of circle
return [cx + r * Math.cos(angle), cy + r * Math.sin(angle)];
}
function evalF(fname, re, im) {
if (fname.startsWith("f(z) = z ")) return [re, im];
if (fname.startsWith("f(z) = z\u0305")) return [re, -im];
return [re * re + im * im, 0]; // |z|^2
}
function numericalIntegral(pathFn, fname, upToT) {
const steps = Math.max(1, Math.round(upToT * STEPS));
const dt = upToT / steps;
let rA = 0, iA = 0;
for (let k = 0; k < steps; k++) {
const t0 = k * dt, t1 = (k + 1) * dt;
const [r0, i0] = pathFn(t0);
const [r1, i1] = pathFn(t1);
const mr = (r0 + r1) / 2, mi = (i0 + i1) / 2;
const dr = r1 - r0, di = i1 - i0;
const [fr, fi] = evalF(fname, mr, mi);
// f(z) dz = (fr+i*fi)(dr+i*di) = fr*dr - fi*di + i*(fi*dr + fr*di)
rA += fr * dr - fi * di;
iA += fi * dr + fr * di;
}
return [rA, iA];
}
const PATHS = [
{ name: "Straight", fn: pathStraight, color: "#f97316" },
{ name: "Right-angle", fn: pathRightAngle, color: "#3b82f6" },
{ name: "Semicircle", fn: pathSemicircle, color: "#8b5cf6" }
];
function buildSVG(fname, tVal) {
const svg = document.createElementNS(svgNS, "svg");
svg.setAttribute("width", W); svg.setAttribute("height", H);
svg.style.cssText = "background:#fafafa; border:1px solid #e5e7eb; border-radius:6px; display:block;";
// Grid lines
for (let v = 0; v <= 2; v++) {
const gv = document.createElementNS(svgNS, "line");
gv.setAttribute("x1", toSx(v)); gv.setAttribute("y1", toSy(-0.3));
gv.setAttribute("x2", toSx(v)); gv.setAttribute("y2", toSy(1.6));
gv.setAttribute("stroke", "#e5e7eb"); gv.setAttribute("stroke-width", "0.5");
svg.appendChild(gv);
}
for (let u = 0; u <= 2; u++) {
const gh = document.createElementNS(svgNS, "line");
gh.setAttribute("x1", toSx(-0.1)); gh.setAttribute("y1", toSy(u));
gh.setAttribute("x2", toSx(1.6)); gh.setAttribute("y2", toSy(u));
gh.setAttribute("stroke", "#e5e7eb"); gh.setAttribute("stroke-width", "0.5");
svg.appendChild(gh);
}
// Axes
const axH = document.createElementNS(svgNS, "line");
axH.setAttribute("x1", toSx(-0.1)); axH.setAttribute("y1", toSy(0));
axH.setAttribute("x2", toSx(1.6)); axH.setAttribute("y2", toSy(0));
axH.setAttribute("stroke", "#9ca3af"); axH.setAttribute("stroke-width", "1.2");
svg.appendChild(axH);
const axV = document.createElementNS(svgNS, "line");
axV.setAttribute("x1", toSx(0)); axV.setAttribute("y1", toSy(-0.2));
axV.setAttribute("x2", toSx(0)); axV.setAttribute("y2", toSy(1.6));
axV.setAttribute("stroke", "#9ca3af"); axV.setAttribute("stroke-width", "1.2");
svg.appendChild(axV);
// Axis labels
const lblRe = document.createElementNS(svgNS, "text");
lblRe.setAttribute("x", toSx(1.55)); lblRe.setAttribute("y", toSy(0) - 6);
lblRe.setAttribute("font-size", "10"); lblRe.setAttribute("fill", "#6b7280");
lblRe.setAttribute("font-family", "sans-serif"); lblRe.textContent = "Re";
svg.appendChild(lblRe);
const lblIm = document.createElementNS(svgNS, "text");
lblIm.setAttribute("x", toSx(0) + 4); lblIm.setAttribute("y", toSy(1.55));
lblIm.setAttribute("font-size", "10"); lblIm.setAttribute("fill", "#6b7280");
lblIm.setAttribute("font-family", "sans-serif"); lblIm.textContent = "Im";
svg.appendChild(lblIm);
// Draw full ghost paths
for (const p of PATHS) {
let d = "";
for (let k = 0; k <= STEPS; k++) {
const [re, im] = p.fn(k / STEPS);
d += (k === 0 ? "M" : "L") + ` ${toSx(re)} ${toSy(im)}`;
}
const ghost = document.createElementNS(svgNS, "path");
ghost.setAttribute("d", d); ghost.setAttribute("fill", "none");
ghost.setAttribute("stroke", p.color); ghost.setAttribute("stroke-width", "1.5");
ghost.setAttribute("stroke-dasharray", "4,3"); ghost.setAttribute("opacity", "0.35");
svg.appendChild(ghost);
}
// Draw traced portions
for (const p of PATHS) {
const steps = Math.round(tVal * STEPS);
let d = "";
for (let k = 0; k <= steps; k++) {
const [re, im] = p.fn(k / STEPS);
d += (k === 0 ? "M" : "L") + ` ${toSx(re)} ${toSy(im)}`;
}
const trace = document.createElementNS(svgNS, "path");
trace.setAttribute("d", d); trace.setAttribute("fill", "none");
trace.setAttribute("stroke", p.color); trace.setAttribute("stroke-width", "2.5");
svg.appendChild(trace);
// Moving dot
const [cr, ci] = p.fn(tVal);
const dot = document.createElementNS(svgNS, "circle");
dot.setAttribute("cx", toSx(cr)); dot.setAttribute("cy", toSy(ci));
dot.setAttribute("r", "5"); dot.setAttribute("fill", p.color);
dot.setAttribute("stroke", "#fff"); dot.setAttribute("stroke-width", "1.5");
svg.appendChild(dot);
}
// A and B endpoint markers
const A = document.createElementNS(svgNS, "circle");
A.setAttribute("cx", toSx(0)); A.setAttribute("cy", toSy(0));
A.setAttribute("r", "5"); A.setAttribute("fill", "#111827");
A.setAttribute("stroke", "#fff"); A.setAttribute("stroke-width", "1.5");
svg.appendChild(A);
const Albl = document.createElementNS(svgNS, "text");
Albl.setAttribute("x", toSx(0) + 7); Albl.setAttribute("y", toSy(0) - 5);
Albl.setAttribute("font-size", "11"); Albl.setAttribute("font-family", "sans-serif");
Albl.setAttribute("fill", "#111827"); Albl.textContent = "A = 0";
svg.appendChild(Albl);
const B = document.createElementNS(svgNS, "circle");
B.setAttribute("cx", toSx(1)); B.setAttribute("cy", toSy(1));
B.setAttribute("r", "5"); B.setAttribute("fill", "#111827");
B.setAttribute("stroke", "#fff"); B.setAttribute("stroke-width", "1.5");
svg.appendChild(B);
const Blbl = document.createElementNS(svgNS, "text");
Blbl.setAttribute("x", toSx(1) + 7); Blbl.setAttribute("y", toSy(1) - 5);
Blbl.setAttribute("font-size", "11"); Blbl.setAttribute("font-family", "sans-serif");
Blbl.setAttribute("fill", "#111827"); Blbl.textContent = "B = 1+i";
svg.appendChild(Blbl);
return svg;
}
function buildBars(fname, tVal) {
const BAR_W = 260, BAR_H = 200;
const svg = document.createElementNS(svgNS, "svg");
svg.setAttribute("width", BAR_W); svg.setAttribute("height", BAR_H);
svg.style.cssText = "display:block;";
const integrals = PATHS.map(p => numericalIntegral(p.fn, fname, tVal));
const maxVal = Math.max(1e-9, ...integrals.flatMap(([r, i]) => [Math.abs(r), Math.abs(i)]));
const barScale = 70 / maxVal;
const colW = 60, midY = 110, lblY = BAR_H - 10;
// Zero line
const zl = document.createElementNS(svgNS, "line");
zl.setAttribute("x1", 10); zl.setAttribute("y1", midY);
zl.setAttribute("x2", BAR_W - 10); zl.setAttribute("y2", midY);
zl.setAttribute("stroke", "#d1d5db"); zl.setAttribute("stroke-width", "1");
svg.appendChild(zl);
const headRe = document.createElementNS(svgNS, "text");
headRe.setAttribute("x", "10"); headRe.setAttribute("y", "14");
headRe.setAttribute("font-size", "9"); headRe.setAttribute("fill", "#6b7280");
headRe.setAttribute("font-family", "sans-serif"); headRe.textContent = "Re(\u222bf dz)";
svg.appendChild(headRe);
const headIm = document.createElementNS(svgNS, "text");
headIm.setAttribute("x", "135"); headIm.setAttribute("y", "14");
headIm.setAttribute("font-size", "9"); headIm.setAttribute("fill", "#6b7280");
headIm.setAttribute("font-family", "sans-serif"); headIm.textContent = "Im(\u222bf dz)";
svg.appendChild(headIm);
for (let i = 0; i < PATHS.length; i++) {
const [rv, iv] = integrals[i];
const col = PATHS[i].color;
const name = PATHS[i].name;
const xR = 14 + i * 38, xI = 140 + i * 38;
// Re bar
const rh = rv * barScale;
const reBar = document.createElementNS(svgNS, "rect");
reBar.setAttribute("x", xR); reBar.setAttribute("y", rh >= 0 ? midY - rh : midY);
reBar.setAttribute("width", 30); reBar.setAttribute("height", Math.max(2, Math.abs(rh)));
reBar.setAttribute("fill", col); reBar.setAttribute("rx", "2");
svg.appendChild(reBar);
const reVal = document.createElementNS(svgNS, "text");
reVal.setAttribute("x", xR + 15); reVal.setAttribute("y", rh >= 0 ? midY - rh - 3 : midY + Math.abs(rh) + 11);
reVal.setAttribute("font-size", "8"); reVal.setAttribute("text-anchor", "middle");
reVal.setAttribute("fill", col); reVal.setAttribute("font-family", "monospace");
reVal.textContent = rv.toFixed(3);
svg.appendChild(reVal);
// Im bar
const ih = iv * barScale;
const imBar = document.createElementNS(svgNS, "rect");
imBar.setAttribute("x", xI); imBar.setAttribute("y", ih >= 0 ? midY - ih : midY);
imBar.setAttribute("width", 30); imBar.setAttribute("height", Math.max(2, Math.abs(ih)));
imBar.setAttribute("fill", col); imBar.setAttribute("rx", "2");
svg.appendChild(imBar);
const imVal = document.createElementNS(svgNS, "text");
imVal.setAttribute("x", xI + 15); imVal.setAttribute("y", ih >= 0 ? midY - ih - 3 : midY + Math.abs(ih) + 11);
imVal.setAttribute("font-size", "8"); imVal.setAttribute("text-anchor", "middle");
imVal.setAttribute("fill", col); imVal.setAttribute("font-family", "monospace");
imVal.textContent = iv.toFixed(3) + "i";
svg.appendChild(imVal);
// Path label
const lbl = document.createElementNS(svgNS, "text");
lbl.setAttribute("x", (xR + xI) / 2 - 6); lbl.setAttribute("y", lblY);
lbl.setAttribute("font-size", "8"); lbl.setAttribute("text-anchor", "middle");
lbl.setAttribute("fill", col); lbl.setAttribute("font-family", "sans-serif");
lbl.textContent = name;
svg.appendChild(lbl);
}
// Agreement indicator
const vals = integrals;
const reClose = Math.abs(vals[0][0] - vals[1][0]) < 0.01 && Math.abs(vals[1][0] - vals[2][0]) < 0.01;
const imClose = Math.abs(vals[0][1] - vals[1][1]) < 0.01 && Math.abs(vals[1][1] - vals[2][1]) < 0.01;
const agree = reClose && imClose;
const annot = document.createElementNS(svgNS, "text");
annot.setAttribute("x", BAR_W / 2); annot.setAttribute("y", BAR_H - 26);
annot.setAttribute("font-size", "9"); annot.setAttribute("text-anchor", "middle");
annot.setAttribute("font-weight", "bold");
annot.setAttribute("fill", agree ? "#059669" : "#dc2626");
annot.setAttribute("font-family", "sans-serif");
annot.textContent = agree ? "all three agree \u2014 path-independent" : "values differ \u2014 path-dependent";
svg.appendChild(annot);
return svg;
}
const container = document.createElement("div");
container.style.cssText = "border:1px solid #e5e7eb; border-radius:8px; padding:1rem; margin:1rem 0; font-family:sans-serif;";
const title = document.createElement("div");
title.style.cssText = "font-weight:600; margin-bottom:0.5rem;";
title.textContent = "Path-dependence explorer: three paths from A to B";
container.appendChild(title);
container.appendChild(intSelector);
container.appendChild(traceSlider);
const panels = document.createElement("div");
panels.style.cssText = "display:flex; gap:1rem; align-items:flex-start; flex-wrap:wrap; margin-top:0.5rem;";
const svgDiv = document.createElement("div");
const barDiv = document.createElement("div");
panels.appendChild(svgDiv);
panels.appendChild(barDiv);
container.appendChild(panels);
// Legend
const legend = document.createElement("div");
legend.style.cssText = "display:flex; gap:1rem; margin-top:0.5rem; font-size:0.82em;";
for (const p of PATHS) {
const item = document.createElement("span");
item.style.cssText = `color:${p.color};`;
item.textContent = "\u25ac " + p.name;
legend.appendChild(item);
}
container.appendChild(legend);
const note = document.createElement("div");
note.style.cssText = "margin-top:0.5rem; font-size:0.82em; color:#6b7280; font-style:italic;";
note.textContent = "Drag the trace slider to advance all three paths simultaneously. For f(z) = z (analytic) every path gives the same result — path independence. Switch to z\u0305 or |z|\u00b2 and the bars diverge.";
container.appendChild(note);
function update() {
const fname = intSelector.value;
const tVal = traceSlider.value;
svgDiv.innerHTML = ""; svgDiv.appendChild(buildSVG(fname, tVal));
barDiv.innerHTML = ""; barDiv.appendChild(buildBars(fname, tVal));
}
intSelector.addEventListener("input", update);
traceSlider.addEventListener("input", update);
update();
return container;
}