chart = {
const width = 640;
const height = 640;
const margin = { top: 40, right: 40, bottom: 60, left: 60 };
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
// 3. 修复方案:使用 DOM.svg 或直接使用 d3.select(html`...`)
// 在 OJS 中,d3.create 有时会因为环境隔离失效,改用 html 模板字符串是更稳妥的做法
const svg = d3.select(DOM.svg(width, height))
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto; background: #fcfbf9;");
const g = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
// 4. 定义坐标轴范围和比例尺
const xDomain = [-2.5, 2.5];
const yDomain = [-2.5, 2.5];
const xScale = d3.scaleLinear().domain(xDomain).range([0, innerWidth]);
const yScale = d3.scaleLinear().domain(yDomain).range([innerHeight, 0]);
// 5. 定义箭头标记
const defs = svg.append("defs");
defs.append("marker")
.attr("id", "arrowhead-field")
.attr("viewBox", "0 -5 10 10")
.attr("refX", 8)
.attr("refY", 0)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5")
.attr("fill", "#b6daff");
defs.append("marker")
.attr("id", "arrowhead-path")
.attr("viewBox", "0 -5 10 10")
.attr("refX", 5)
.attr("refY", 0)
.attr("markerWidth", 8)
.attr("markerHeight", 8)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5")
.attr("fill", "#ff6600");
// 6. 生成矢量场数据
const vectors = [];
const gridSize = 20;
const xStep = (xDomain[1] - xDomain[0]) / gridSize;
const yStep = (yDomain[1] - yDomain[0]) / gridSize;
for (let i = 0; i <= gridSize; i++) {
for (let j = 0; j <= gridSize; j++) {
const x = xDomain[0] + i * xStep;
const y = yDomain[0] + j * yStep;
const r2 = x * x + y * y;
if (r2 < 0.1) continue;
let u = -y / r2;
let v = x / r2;
const magnitude = Math.sqrt(u * u + v * v);
const visualScaleFactor = 0.15;
let visualLen = Math.min(magnitude * visualScaleFactor, 0.8);
vectors.push({
x, y,
uVis: (u / magnitude) * visualLen,
vVis: (v / magnitude) * visualLen
});
}
}
// 7. 绘制矢量场
g.append("g")
.selectAll("line")
.data(vectors)
.join("line")
.attr("x1", d => xScale(d.x))
.attr("y1", d => yScale(d.y))
.attr("x2", d => xScale(d.x + d.uVis))
.attr("y2", d => yScale(d.y + d.vVis))
.attr("stroke", "#b6daff")
.attr("stroke-width", 1.5)
.attr("marker-end", "url(#arrowhead-field)");
// 8. 绘制坐标轴
g.append("g")
.attr("transform", `translate(0,${yScale(0)})`)
.call(d3.axisBottom(xScale).ticks(5));
g.append("g")
.attr("transform", `translate(${xScale(0)},0)`)
.call(d3.axisLeft(yScale).ticks(5));
// 9. 绘制闭合路径 C (单位圆)
const svgRadius = xScale(1) - xScale(0);
g.append("circle")
.attr("cx", xScale(0))
.attr("cy", yScale(0))
.attr("r", svgRadius)
.attr("fill", "none")
.attr("stroke", "#ff6600")
.attr("stroke-width", 3)
.attr("stroke-dasharray", "8, 4");
// 10. 路径方向箭头与标注
const pathArrows = [
{x: 1, y: 0, u: 0, v: 0.4}, {x: 0, y: 1, u: -0.4, v: 0},
{x: -1, y: 0, u: 0, v: -0.4}, {x: 0, y: -1, u: 0.4, v: 0}
];
g.selectAll(".path-arrow")
.data(pathArrows)
.join("line")
.attr("x1", d => xScale(d.x))
.attr("y1", d => yScale(d.y))
.attr("x2", d => xScale(d.x + d.u))
.attr("y2", d => yScale(d.y + d.v))
.attr("stroke", "#ff6600")
.attr("stroke-width", 3)
.attr("marker-end", "url(#arrowhead-path)");
// 11. 标记原点的“洞”
const originG = g.append("g").attr("transform", `translate(${xScale(0)},${yScale(0)})`);
originG.append("line").attr("x1", -10).attr("y1", -10).attr("x2", 10).attr("y2", 10).attr("stroke", "red").attr("stroke-width", 3);
originG.append("line").attr("x1", 10).attr("y1", -10).attr("x2", -10).attr("y2", 10).attr("stroke", "red").attr("stroke-width", 3);
return svg.node();
}