lineIntegralDemo = {
const d3 = await require("d3@7");
const W = 560, H = 450;
const ML = 20, MR = 20, MT = 20, MB = 60;
const innerW = W - ML - MR;
const innerH = H - MT - MB;
const container = d3.create("div").style("max-width", W+"px").style("font-family", "sans-serif").style("margin", "0 auto");
const controls = container.append("div").style("margin-bottom", "15px").style("padding", "10px").style("background", "#f8fafc").style("border-radius", "8px").style("border", "1px solid #e2e8f0").style("display", "flex").style("flex-direction", "column").style("gap", "10px");
const pathDiv = controls.append("div").style("display", "flex").style("gap", "15px").style("align-items", "center");
pathDiv.append("span").style("font-weight", "600").style("font-size", "0.9rem").text("Path C:");
const paths = [
{id: "line", label: "Straight Line", fn: t => [-2 + 4*t, 0], d: t => [4, 0]},
{id: "semi", label: "Semicircle", fn: t => [-2*Math.cos(Math.PI*t), 2*Math.sin(Math.PI*t)], d: t => [2*Math.PI*Math.sin(Math.PI*t), 2*Math.PI*Math.cos(Math.PI*t)]},
{id: "parabola", label: "Parabola", fn: t => [-2 + 4*t, 2 - 2*(2*t - 1)*(2*t - 1)], d: t => [4, -8*(2*t - 1)]}
];
paths.forEach((p, i) => {
const lbl = pathDiv.append("label").style("display", "flex").style("align-items", "center").style("gap", "4px").style("font-size", "0.9rem").style("cursor", "pointer");
const inp = lbl.append("input").attr("type", "radio").attr("name", "path_type").attr("value", p.id).style("margin", "0");
if(i===0) inp.property("checked", true);
lbl.append("span").text(p.label);
inp.on("change", () => { tSlider.property("value", 0); tValSpan.text("0.00"); update(); });
});
const tDiv = controls.append("div").style("display", "flex").style("align-items", "center").style("gap", "10px").style("font-size", "0.9rem");
tDiv.append("span").text("t = ");
const tSlider = tDiv.append("input").attr("type", "range").attr("min", 0).attr("max", 1).attr("step", 0.01).attr("value", 0).style("flex", 1);
const tValSpan = tDiv.append("span").style("width", "35px").style("font-family", "monospace").text("0.00");
const svg = container.append("svg").attr("width", W).attr("height", H).attr("viewBox", `0 0 ${W} ${H}`);
const g = svg.append("g").attr("transform", `translate(${ML},${MT})`);
const xyLim = 2.5;
const xScale = d3.scaleLinear([-xyLim, xyLim], [0, innerW]);
const yScale = d3.scaleLinear([-xyLim, xyLim], [innerH, 0]);
g.append("line").attr("x1", xScale(-xyLim)).attr("y1", yScale(0)).attr("x2", xScale(xyLim)).attr("y2", yScale(0)).attr("stroke", "#cbd5e1");
g.append("line").attr("x1", xScale(0)).attr("y1", yScale(-xyLim)).attr("x2", xScale(0)).attr("y2", yScale(xyLim)).attr("stroke", "#cbd5e1");
const F = (x, y) => [-y, x*x];
const arrowGroup = g.append("g").attr("opacity", 0.3);
const arrowGridSize = 25;
for(let x=0; x<=innerW; x+=arrowGridSize) {
for(let y=0; y<=innerH; y+=arrowGridSize) {
const u = xScale.invert(x);
const v = yScale.invert(y);
const fv = F(u, v);
const speed = Math.sqrt(fv[0]*fv[0] + fv[1]*fv[1]);
if(speed > 0.1) {
const len = Math.min(speed*4, arrowGridSize*0.8);
const dx = (fv[0]/speed)*len;
const dy = -(fv[1]/speed)*len;
g.append("line").attr("x1", x).attr("y1", y).attr("x2", x+dx).attr("y2", y+dy).attr("stroke", "#334155").attr("stroke-width", 1);
g.append("circle").attr("cx", x).attr("cy", y).attr("r", 1.5).attr("fill", "#334155");
}
}
}
const pathCurve = g.append("path").attr("fill", "none").attr("stroke", "#94a3b8").attr("stroke-width", 2.5).attr("stroke-dasharray", "4,4");
const traveledCurve = g.append("path").attr("fill", "none").attr("stroke", "#3b82f6").attr("stroke-width", 3);
const fVec = g.append("g");
const fVecLine = fVec.append("line").attr("stroke", "#ef4444").attr("stroke-width", 2.5);
const fVecHead = fVec.append("polygon").attr("fill", "#ef4444");
const drVec = g.append("g");
const drVecLine = drVec.append("line").attr("stroke", "#10b981").attr("stroke-width", 2.5);
const drVecHead = drVec.append("polygon").attr("fill", "#10b981");
const particle = g.append("circle").attr("r", 6).attr("fill", "#3b82f6");
const plotG = svg.append("g").attr("transform", `translate(${ML},${H - MB + 20})`);
const ptScale = d3.scaleLinear([0, 1], [0, innerW]);
const pyScale = d3.scaleLinear([-15, 15], [30, 0]);
plotG.append("line").attr("x1", 0).attr("x2", innerW).attr("y1", pyScale(0)).attr("y2", pyScale(0)).attr("stroke", "#cbd5e1");
const intArea = plotG.append("path").attr("fill", "rgba(59, 130, 246, 0.2)");
const intLine = plotG.append("path").attr("fill", "none").attr("stroke", "#3b82f6").attr("stroke-width", 2);
const intDot = plotG.append("circle").attr("r", 4).attr("fill", "#2563eb");
const totalLabel = svg.append("text").attr("x", W - MR).attr("y", H - 5).attr("text-anchor", "end").style("font-family", "monospace").style("font-size", "14px").style("font-weight", "bold");
function drawVec(vG, x, y, dx, dy) {
vG.select("line").attr("x1", x).attr("y1", y).attr("x2", x+dx).attr("y2", y+dy);
const angle = Math.atan2(dy, dx);
const r = 6;
vG.select("polygon").attr("points", `${x+dx},${y+dy} ${x+dx - r*Math.cos(angle - 0.5)},${y+dy - r*Math.sin(angle - 0.5)} ${x+dx - r*Math.cos(angle + 0.5)},${y+dy - r*Math.sin(angle + 0.5)}`);
}
function update() {
const selectedId = pathDiv.select('input[name="path_type"]:checked').property("value");
const pdef = paths.find(p => p.id === selectedId);
const t = +tSlider.property("value");
tValSpan.text(t.toFixed(2));
const pts = d3.range(0, 1.01, 0.02).map(tt => pdef.fn(tt));
const lineGen = d3.line().x(d => xScale(d[0])).y(d => yScale(d[1]));
pathCurve.attr("d", lineGen(pts));
const ptsTraveled = d3.range(0, t+0.001, 0.02).map(tt => pdef.fn(tt));
traveledCurve.attr("d", lineGen(ptsTraveled));
const pt = pdef.fn(t);
const px = xScale(pt[0]);
const py = yScale(pt[1]);
particle.attr("cx", px).attr("cy", py);
const fv = F(pt[0], pt[1]);
drawVec(fVec, px, py, fv[0]*15, -fv[1]*15);
const dr = pdef.d(t);
drawVec(drVec, px, py, dr[0]*5, -dr[1]*5);
const intData = d3.range(0, t+0.001, 0.02).map(tt => {
const cpt = pdef.fn(tt);
const cdr = pdef.d(tt);
const cf = F(cpt[0], cpt[1]);
const dot = cf[0]*cdr[0] + cf[1]*cdr[1];
return {t: tt, val: dot};
});
const areaGen = d3.area().x(d => ptScale(d.t)).y0(pyScale(0)).y1(d => pyScale(d.val));
const lineGenPlot = d3.line().x(d => ptScale(d.t)).y(d => pyScale(d.val));
intArea.attr("d", areaGen(intData));
intLine.attr("d", lineGenPlot(intData));
if(intData.length > 0) {
const last = intData[intData.length-1];
intDot.attr("cx", ptScale(last.t)).attr("cy", pyScale(last.val)).attr("visibility", "visible");
} else {
intDot.attr("visibility", "hidden");
}
let sum = 0;
const dt = 0.005;
for(let tt=0; tt<=t; tt+=dt) {
const cpt = pdef.fn(tt);
const cdr = pdef.d(tt);
const cf = F(cpt[0], cpt[1]);
sum += (cf[0]*cdr[0] + cf[1]*cdr[1]) * dt;
}
totalLabel.text(`\u222B F \u00B7 dr \u2248 ${sum.toFixed(2)}`);
}
tSlider.on("input", update);
update();
return container.node();
}