{
// C-R approach-direction demo
// Left panel: z-plane with z₀ (draggable via sliders), two approach arrows
// Right: "derivative plane" showing the two difference quotients as Δ→0
const W = 320, H = 300, SCALE = 70;
const CX = W/2, CY = H/2;
const svgNS = "http://www.w3.org/2000/svg";
const funcSel = Inputs.select(
["f(z) = z²", "f(z) = eᶻ", "f(z) = z̄", "f(z) = |z|²"],
{ value: "f(z) = z²", label: "Function" }
);
const z0xSlider = Inputs.range([-2, 2], { value: 0.7, step: 0.1, label: "Re(z₀)" });
const z0ySlider = Inputs.range([-2, 2], { value: 0.5, step: 0.1, label: "Im(z₀)" });
const deltaSlider = Inputs.range([0.02, 1.0], { value: 0.5, step: 0.02, label: "Δ (step size)" });
function evalF(name, re, im) {
switch (name) {
case "f(z) = z²": return [re*re - im*im, 2*re*im];
case "f(z) = eᶻ": { const ex=Math.exp(re); return [ex*Math.cos(im), ex*Math.sin(im)]; }
case "f(z) = z̄": return [re, -im];
case "f(z) = |z|²": return [re*re + im*im, 0];
default: return [re, im];
}
}
function toScreen(re, im) { return [CX + re*SCALE, CY - im*SCALE]; }
function fromScreen(px, py) { return [(px-CX)/SCALE, -(py-CY)/SCALE]; }
function makePanel(w, h, title) {
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
for (let i=-3; i<=3; i++) {
const xl = document.createElementNS(svgNS,"line");
const [lx]=toScreen(i,0); const [,ly1]=toScreen(0,-3); const [,ly2]=toScreen(0,3);
xl.setAttribute("x1",lx); xl.setAttribute("y1",ly1); xl.setAttribute("x2",lx); xl.setAttribute("y2",ly2);
xl.setAttribute("stroke","#e5e7eb"); xl.setAttribute("stroke-width","0.5"); svg.appendChild(xl);
const yl = document.createElementNS(svgNS,"line");
const [lx1]=toScreen(-3,0); const [lx2]=toScreen(3,0); const [,ly]=toScreen(0,i);
yl.setAttribute("x1",lx1); yl.setAttribute("y1",ly); yl.setAttribute("x2",lx2); yl.setAttribute("y2",ly);
yl.setAttribute("stroke","#e5e7eb"); yl.setAttribute("stroke-width","0.5"); svg.appendChild(yl);
}
// Axes
const [ox,oy]=toScreen(0,0);
function axLine(x1,y1,x2,y2) {
const l=document.createElementNS(svgNS,"line");
l.setAttribute("x1",x1); l.setAttribute("y1",y1); l.setAttribute("x2",x2); l.setAttribute("y2",y2);
l.setAttribute("stroke","#9ca3af"); l.setAttribute("stroke-width","1"); svg.appendChild(l);
}
const [ax1]=toScreen(-2.8,0); const [ax2]=toScreen(2.8,0);
axLine(ax1,oy,ax2,oy);
const [,ay1]=toScreen(0,-2.3); const [,ay2]=toScreen(0,2.3);
axLine(ox,ay1,ox,ay2);
// Panel label
const tl = document.createElementNS(svgNS,"text");
tl.setAttribute("x","6"); tl.setAttribute("y","16");
tl.setAttribute("fill","#6b7280"); tl.setAttribute("font-size","10");
tl.setAttribute("font-family","sans-serif"); tl.textContent = title;
svg.appendChild(tl);
return { svg, ox, oy };
}
function arrowHead(svg, x2, y2, dx, dy, color) {
const len = Math.sqrt(dx*dx+dy*dy);
if (len < 0.001) return;
const ux=dx/len, uy=dy/len;
const size=7;
const lx=x2-ux*size+uy*size*0.4, ly=y2-uy*size-ux*size*0.4;
const rx=x2-ux*size-uy*size*0.4, ry=y2-uy*size+ux*size*0.4;
const tri=document.createElementNS(svgNS,"polygon");
tri.setAttribute("points",`${x2},${y2} ${lx},${ly} ${rx},${ry}`);
tri.setAttribute("fill",color); svg.appendChild(tri);
}
function addArrow(svg, x1, y1, x2, y2, color, dash) {
const l=document.createElementNS(svgNS,"line");
l.setAttribute("x1",x1); l.setAttribute("y1",y1); l.setAttribute("x2",x2); l.setAttribute("y2",y2);
l.setAttribute("stroke",color); l.setAttribute("stroke-width","2");
if (dash) l.setAttribute("stroke-dasharray",dash);
svg.appendChild(l);
arrowHead(svg, x2, y2, x2-x1, y2-y1, color);
}
function addDot(svg, cx, cy, r, fill, stroke) {
const c=document.createElementNS(svgNS,"circle");
c.setAttribute("cx",cx); c.setAttribute("cy",cy); c.setAttribute("r",r);
c.setAttribute("fill",fill); c.setAttribute("stroke",stroke||"#fff"); c.setAttribute("stroke-width","1.5");
svg.appendChild(c);
}
function addLabel(svg, x, y, txt, color, size) {
const t=document.createElementNS(svgNS,"text");
t.setAttribute("x",x); t.setAttribute("y",y);
t.setAttribute("fill",color); t.setAttribute("font-size",size||"11");
t.setAttribute("font-family","sans-serif"); t.textContent=txt;
svg.appendChild(t);
}
function buildCRPanel(funcName, z0x, z0y, delta) {
const { svg: svgL } = makePanel(W, H, "z-plane");
// C-R background wash: pixel-test each cell
const WASH_STEP = 12, H_EPS = 0.01;
for (let px=0; px<W; px+=WASH_STEP) {
for (let py=0; py<H; py+=WASH_STEP) {
const [re,im] = fromScreen(px+WASH_STEP/2, py+WASH_STEP/2);
const [u0,v0]=evalF(funcName,re,im);
const [udx,vdx]=evalF(funcName,re+H_EPS,im);
const [udy,vdy]=evalF(funcName,re,im+H_EPS);
const ux=(udx-u0)/H_EPS, vx=(vdx-v0)/H_EPS;
const uy=(udy-u0)/H_EPS, vy=(vdy-v0)/H_EPS;
const err = Math.abs(ux-vy) + Math.abs(uy+vx);
const ok = err < 0.05;
const rect=document.createElementNS(svgNS,"rect");
rect.setAttribute("x",px); rect.setAttribute("y",py);
rect.setAttribute("width",WASH_STEP); rect.setAttribute("height",WASH_STEP);
rect.setAttribute("fill", ok ? "#059669" : "#dc2626");
rect.setAttribute("opacity","0.12");
svgL.appendChild(rect);
}
}
// z₀ point
const [z0sx, z0sy] = toScreen(z0x, z0y);
addDot(svgL, z0sx, z0sy, 7, "#f97316", "#fff");
addLabel(svgL, z0sx+9, z0sy-6, "z₀", "#f97316", "12");
// Horizontal approach arrow: z₀ + Δ → z₀
const [hsx] = toScreen(z0x + delta, z0y);
addArrow(svgL, hsx, z0sy, z0sx, z0sy, "#f97316", "");
addLabel(svgL, hsx+4, z0sy-4, "→ z₀", "#f97316", "10");
// Vertical approach arrow: z₀ + iΔ → z₀
const [,vsy] = toScreen(z0x, z0y + delta);
addArrow(svgL, z0sx, vsy, z0sx, z0sy, "#3b82f6", "");
addLabel(svgL, z0sx+4, vsy-4, "↑ z₀", "#3b82f6", "10");
// Panel labels
addLabel(svgL, 6, H-8, "Green = C-R holds Red = fails", "#6b7280", "9");
// Right panel: derivative plane — two quotient vectors
const { svg: svgR } = makePanel(W, H, "difference quotient");
const f0 = evalF(funcName, z0x, z0y);
const fHoriz = evalF(funcName, z0x + delta, z0y);
const fVert = evalF(funcName, z0x, z0y + delta);
// Horizontal quotient: (f(z₀+Δ) − f(z₀)) / Δ (real Δ)
const hqr = (fHoriz[0]-f0[0])/delta, hqi = (fHoriz[1]-f0[1])/delta;
// Vertical quotient: (f(z₀+iΔ) − f(z₀)) / (iΔ) = −i·(Δf)/Δ
const vqr = (fVert[1]-f0[1])/delta, vqi = -(fVert[0]-f0[0])/delta;
const [origx, origy] = toScreen(0,0);
const [hqsx, hqsy] = toScreen(hqr, hqi);
const [vqsx, vqsy] = toScreen(vqr, vqi);
addArrow(svgR, origx, origy, hqsx, hqsy, "#f97316", "");
addLabel(svgR, hqsx+6, hqsy, "horiz", "#f97316", "10");
addArrow(svgR, origx, origy, vqsx, vqsy, "#3b82f6", "");
addLabel(svgR, vqsx+6, vqsy, "vert", "#3b82f6", "10");
addDot(svgR, origx, origy, 4, "#6b7280", "#fff");
// Check agreement
const agree = Math.abs(hqr-vqr) + Math.abs(hqi-vqi);
const color = agree < 0.15 ? "#059669" : "#dc2626";
const msg = agree < 0.15 ? "Quotients agree — f differentiable here" : "Quotients differ — C-R fails here";
addLabel(svgR, 6, H-20, msg, color, "10");
addLabel(svgR, 6, H-8, `|error| = ${agree.toFixed(3)} (small Δ → more precise)`, "#9ca3af", "9");
const wrap = document.createElement("div");
wrap.style.cssText = "display:flex; gap:0.75rem; flex-wrap:wrap; margin:0.5rem 0;";
wrap.appendChild(svgL);
wrap.appendChild(svgR);
return wrap;
}
const container = document.createElement("div");
container.style.cssText = "border:1px solid #e5e7eb; border-radius:8px; padding:1rem; margin:1rem 0;";
const titleEl = document.createElement("div");
titleEl.style.cssText = "font-weight:600; margin-bottom:0.5rem; font-family:sans-serif;";
titleEl.textContent = "Cauchy-Riemann: approach-direction explorer";
container.appendChild(titleEl);
container.appendChild(funcSel);
container.appendChild(z0xSlider);
container.appendChild(z0ySlider);
container.appendChild(deltaSlider);
const vizDiv = document.createElement("div");
container.appendChild(vizDiv);
const noteEl = document.createElement("div");
noteEl.style.cssText = "margin-top:0.5rem; font-size:0.82em; color:#6b7280; font-style:italic;";
noteEl.textContent = "Left: z-plane with orange (horizontal) and blue (vertical) approach arrows toward z₀. Background green where C-R holds, red where it fails. Right: the two difference quotients as vectors — they must point to the same place for f to be differentiable at z₀. Reduce Δ to see the quotients converge (or not). For f = z̄ the entire left panel is red and the quotient vectors never align.";
container.appendChild(noteEl);
function update() {
vizDiv.innerHTML = "";
vizDiv.appendChild(buildCRPanel(funcSel.value, z0xSlider.value, z0ySlider.value, deltaSlider.value));
}
funcSel.addEventListener("input", update);
z0xSlider.addEventListener("input", update);
z0ySlider.addEventListener("input", update);
deltaSlider.addEventListener("input", update);
update();
return container;
}