{
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')
const links = svg.append('g')
.selectAll('polyline')
.data(edgesForce)
.join('polyline')
.attr('stroke', 'black')
.attr('fill', 'none')
.attr('stroke-width', 1)
.attr('marker-mid', 'url(#triangle)')
const vertices = svg.append('g')
.selectAll('g')
.data(nodesForce)
.join('g');
vertices.append('circle')
.attr('r', nodeRadius)
.attr('fill', 'steelblue');
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);
simulation.on('tick', () => {
vertices
.attr('transform', d => `translate(${d.x},${d.y})`);
links.attr('points', d => {
const midX = (d.source.x + d.target.x) / 2;
const midY = (d.source.y + d.target.y) / 2;
return `${d.source.x},${d.source.y} ${midX},${midY} ${d.target.x},${d.target.y}`
});
})
invalidation.then(() => simulation.stop());
return svg.node();
}