I'm trying to create a d3 force graph where each of the nodes is two concentric circles. I can do it without any issues with only one circle. I undestand I have to group each of the two circles, but I'm not sure what I'm doing wrong.
This is what my code looks like:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>D3 Force Graph</title>
<script src="d3.v5.9.7.js"></script>
</head>
<body>
<div id="viz"></div>
<script>
// Utils
function createLinksArray(agentNodes) {
const n = agentNodes.length;
let linksArray = [];
agentNodes.map((agent, i) => {
linksArray.push({ source: 0, target: i 1, weight: agent.packetsIn agent.packetsOut, ...agent })
})
return linksArray;
}
// Constants ---
const forceManyBody = -1000;
const distance = 140;
const styles = {
switch: {
size: 60,
fill: '#bbb',
strokeColor: 'green',
strokeWidth: 4
},
agent: {
size: 25,
fill: 'white',
strokeColor: '#bbb',
strokeWidth: 2
}
}
const linkStyles = {
onlineColor: 'green',
offlineColor: 'gray',
strokeWidth: 2,
}
// Chart container dimensions ---
let svgWidth = 600, svgHeight = 600;
// Main SVG container ---
let svg = d3.select('#viz')
.append('svg')
.attr('width', svgWidth)
.attr('height', svgHeight)
.append('g');
// Data binding ---
d3.json('data.json').then(data => {
// Format data structure ---
const switchNode = {
d3id: 0,
type: 'switch',
name: data.selectedSwitch.sdmcSwitch.instanceName
};
const agentsNodes = data.agentsState.rates.agentsRates.map((x, i) => ({ d3id: i 1, type: 'agent', name: x.agentName, ...x }));
let NODES = [switchNode, ...agentsNodes];
let LINKS = createLinksArray(agentsNodes);
// Scale domain and range
const linkWeightScale = d3.scaleLinear()
.domain([0, d3.max(LINKS.map(link => link.weight))])
.range([1, 10]);
// SVGs creation ---
const linkSvg = svg
.selectAll('path')
.data(LINKS)
.enter()
.append('path')
.attr('stroke', linkStyles.onlineColor)
.attr('stroke-width', d => linkWeightScale(d.weight));
let nodeSvg2 = svg
.selectAll('g')
.data(NODES)
.enter()
.append('g');
nodeSvg2
.append('circle')
.attr('r', d => styles[d.type].size)
.style('fill', d => styles[d.type].fill);
nodeSvg2
.append('circle')
.attr('r', d => styles[d.type].size*0.9)
.attr('fill', 'green')
.attr('stroke', 'black');
const textSvg = svg
.selectAll("text")
.data(NODES)
.enter()
.append("text")
.attr("text-anchor", "middle")
.attr("alignment-baseline", "middle")
.style('font-size', 20)
.style('font-family', 'sans-serif')
.text((node) => node.name);
// Set up graph simulation ---
const simulation = d3.forceSimulation(NODES)
.force("charge", d3.forceManyBody().strength(forceManyBody))
.force("link", d3.forceLink(LINKS).id(d => d.d3id).distance(distance))
.force("center", d3.forceCenter(svgWidth / 2, svgHeight / 2));
simulation.on("tick", () => {
nodeSvg2
.attr("cx", d => d.x).attr("cy", d => d.y);
linkSvg
.attr("d", d => d3.line()([
[d.source.x, d.source.y],
[d.target.x, d.target.y]
]));
textSvg
.attr('x', d => d.x).attr('y', d => d.y);
})
});
</script>
</body>
</html>
And the screenshot shows what's happening, all circles are piled at (0,0) What's happening?
CodePudding user response:
nodeSvg2
is a group so you need to supply a transform/translate.
nodeSvg2
.attr('transform', d => 'translate(' d.x ',' d.y ')')
The cx
and cy
attributes won't work on the group element.