chart = {
const width = 928;
const height = 600;
const color = d3.scaleOrdinal(d3.schemeCategory10);
const simulation = d3
.forceSimulation(nodes)
.force(
"link",
d3.forceLink(links).id((d) => d.id)
)
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2))
.on("tick", draw);
const dpi = devicePixelRatio;
const canvas = d3
.create("canvas")
.attr("width", dpi * width)
.attr("height", dpi * height)
.attr("style", `width: ${width}px; max-width: 100%; height: auto;`)
.node();
const context = canvas.getContext("2d");
context.scale(dpi, dpi);
function draw() {
context.clearRect(0, 0, width, height);
context.save();
context.globalAlpha = 1.0;
context.strokeStyle = "#000000";
context.beginPath();
links.forEach(drawLink);
context.stroke();
context.restore();
// Draw nodes - only draw nodes that have links
const activeNodes = new Set(
links.flatMap((d) => [d.source.id, d.target.id])
);
context.save();
context.strokeStyle = "#fff";
context.globalAlpha = 1;
nodes.forEach((node) => {
if (activeNodes.has(node.id)) {
context.beginPath();
drawNode(node);
context.fillStyle = color(node.group);
context.fill();
context.stroke();
}
});
context.restore();
}
function drawLink(d) {
context.globalAlpha = d.confidence;
context.beginPath(); // Start a new path for each link
if (d.confidence < 1) {
context.setLineDash([5, 5]); // Set the line to be dashed
} else {
context.setLineDash([]); // Set the line to be solid
}
context.moveTo(d.source.x, d.source.y);
context.lineTo(d.target.x, d.target.y);
context.stroke(); // Apply the stroke to this specific link
}
function drawNode(d) {
context.moveTo(d.x + 5, d.y);
context.arc(d.x, d.y, 5, 0, 2 * Math.PI);
}
// Drag behavior setup
d3.select(canvas).call(
d3
.drag()
.subject((event) => {
const [px, py] = d3.pointer(event, canvas);
return d3.least(nodes, ({ x, y }) => {
const dist2 = (x - px) ** 2 + (y - py) ** 2;
if (dist2 < 400) return dist2;
});
})
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
);
// Drag event handlers
function dragstarted(event) {
if (!event.active) simulation.alphaTarget(0.3).restart();
event.subject.fx = event.subject.x;
event.subject.fy = event.subject.y;
}
function dragged(event) {
event.subject.fx = event.x;
event.subject.fy = event.y;
}
function dragended(event) {
if (!event.active) simulation.alphaTarget(0);
event.subject.fx = null;
event.subject.fy = null;
}
// Handle cell reevaluation
invalidation.then(() => simulation.stop());
return canvas;
}