{
const width = 200;
const height = 200;
const svg = d3.create('svg')
.attr('width', width)
.attr('height', height);
const nodesForce = nodes.map(d => ({...d}));
const edgesForce = edges.map(d => ({...d}))
const nodeRadius = 10;
const simulation = d3.forceSimulation(nodesForce)
.force('link', d3.forceLink(edgesForce).id(d => d.id))
.force('center', d3.forceCenter(width / 2, height / 2))
.force('manyBody', d3.forceManyBody().strength(-400))
.force('collision', d3.forceCollide(nodeRadius));
svg.append('defs')
.append('marker')
.attr('id', 'triangle')
.attr('viewBox', '0 0 10 10')
.attr('refX', 1)
.attr('refY', 5)
.attr('markerUnits', 'strokeWidth')
.attr('markerWidth', 7)
.attr('markerHeight', 7)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M 0 0 L 10 5 L 0 10 z')
.attr('fill', 'black')
// add polyline for edges, we don't need to set their positions yet.
// we are using a polyline here instead of a line so that it will
// be easier to place the arrow in the middle of the line
const links = svg.append('g')
.selectAll('polyline')
.data(edgesForce)
.join('polyline')
.attr('stroke', 'black')
.attr('fill', 'none')
.attr('stroke-width', 1)
// put triangle in the middle of the polyline
.attr('marker-mid', 'url(#triangle)')
// add a group for each node, we don't need to set their positions yet
const vertices = svg.append('g')
.selectAll('g')
.data(nodesForce)
.join('g');
// add circle to each group
vertices.append('circle')
.attr('r', nodeRadius)
.attr('fill', 'steelblue');
// add text to each group
vertices.append('text')
.attr('fill', 'white')
.attr('dominant-baseline', 'middle')
.attr('text-anchor', 'middle')
.attr('font-family', 'sans-serif')
.attr('font-size', '16')
.text(d => d.id);
// update the positions of the vertices and edges on each simulation tick
simulation.on('tick', () => {
vertices
.attr('transform', d => `translate(${d.x},${d.y})`);
links.attr('points', d => {
// get the midpoint of the line
// this is where the triangle will go
const midX = (d.source.x + d.target.x) / 2;
const midY = (d.source.y + d.target.y) / 2;
// polylines are specified by space sepatated string of x,y coordinates
return `${d.source.x},${d.source.y} ${midX},${midY} ${d.target.x},${d.target.y}`
});
})
// this line is not needed off of observable
// it stops the current simulation when the cell is re-run
invalidation.then(() => simulation.stop());
return svg.node();
}