chart = (nodes, links) => {
const maxLinkWeight = d3.max(links, d => d.weight)
const simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links)
.id(d => d.id)
.strength(d => d.weight / maxLinkWeight)
.distance(200)
)
.force("charge", d3.forceManyBody().strength(-500))
.force("center", d3.forceCenter(width / 2, height / 2));
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);
const link = svg.append("g")
.attr('id', 'links')
.attr("stroke-opacity", 0.25)
.selectAll("line")
.data(links)
.join("line")
.attr("stroke", d => d.source.color)
.attr("stroke-width", d => d.weight)
const node = svg.append("g")
.attr('id', 'nodes')
.selectAll("g")
.data(nodes)
.join("g")
.call(drag(simulation))
node.append('circle')
.attr("r", d => d.r)
.attr("stroke", d => !d.fill ? d.color : '#999')
.attr('fill', d => d.fill ? d.color : '#fff')
.attr("stroke-width", 1.5);
node.append("text")
.attr('text-anchor', 'middle')
.style('font-size', 10)
.style('font-family', 'monospace')
.text(d => d.title);
simulation.on("tick", () => {
_.each(nodes, d => {
d.x = Math.max(d.r, Math.min(width - d.r, d.x))
d.y = Math.max(d.r, Math.min(height - d.r, d.y))
})
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
node.attr('transform', d => `translate(${d.x}, ${d.y})`);
});
invalidation.then(() => simulation.stop());
return svg.node();
}