Public
Edited
May 6, 2023
Insert cell
Insert cell
Insert cell
forceGraph = (data) => {
const w2 = width / 2,
h2 = height / 2,
nodeRadius = 5;

const ctx = DOM.context2d(width, height);
const canvas = ctx.canvas;

const simulation = forceSimulation(width, height);
let transform = d3.zoomIdentity;

// The simulation will alter the input data objects so make
// copies to protect the originals.
const nodes = data.nodes.map(d => Object.assign({}, d));
const edges = data.links.map(d => Object.assign({}, d));

d3.select(canvas)
.call(d3.drag()
// Must set this in order to drag nodes. New in v5?
.container(canvas)
.subject(dragSubject)
.on('start', dragStarted)
.on('drag', dragged)
.on('end', dragEnded))
.call(d3.zoom()
.scaleExtent([1 / 10, 8])
.on('zoom', zoomed));

simulation.nodes(nodes)
.on("tick",simulationUpdate);
simulation.force("link")
.links(edges);

function zoomed() {
transform = d3.event.transform;
simulationUpdate();
}
/** Find the node that was clicked, if any, and return it. */
function dragSubject() {
const x = transform.invertX(d3.event.x),
y = transform.invertY(d3.event.y);
const node = findNode(nodes, x, y, nodeRadius);
if (node) {
node.x = transform.applyX(node.x);
node.y = transform.applyY(node.y);
}
// else: No node selected, drag container
return node;
}

function dragStarted() {
if (!d3.event.active) {
simulation.alphaTarget(0.3).restart();
}
d3.event.subject.fx = transform.invertX(d3.event.x);
d3.event.subject.fy = transform.invertY(d3.event.y);
}

function dragged() {
d3.event.subject.fx = transform.invertX(d3.event.x);
d3.event.subject.fy = transform.invertY(d3.event.y);
}

function dragEnded() {
if (!d3.event.active) {
simulation.alphaTarget(0);
}
d3.event.subject.fx = null;
d3.event.subject.fy = null;
}

function simulationUpdate() {
ctx.save();
ctx.clearRect(0, 0, width, height);
ctx.translate(transform.x, transform.y);
ctx.scale(transform.k, transform.k);
// Draw edges
edges.forEach(function(d) {
ctx.beginPath();
ctx.moveTo(d.source.x, d.source.y);
ctx.lineTo(d.target.x, d.target.y);
ctx.lineWidth = Math.sqrt(d.value);
ctx.strokeStyle = '#aaa';
ctx.stroke();
});
// Draw nodes
nodes.forEach(function(d, i) {
ctx.beginPath();
// Node fill
ctx.moveTo(d.x + sizes[i], d.y);
ctx.arc(d.x, d.y, sizes[i], 0, 2 * Math.PI);
ctx.fillStyle = color(d);
ctx.fill();
// Node outline
ctx.strokeStyle = '#fff'
ctx.lineWidth = '1.5'
ctx.stroke()
});
ctx.restore();
}

return canvas;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more