Reputation: 35
I tried to create cluster bubble by using d3 force layout. After referring to several code, I could create the following chart. However the problem is that circles is overlapping each others. I referred to the several Q&A for this problem,Still I could not solve the porblem.
I tried to make chart like the following site.(https://blockbuilder.org/ericsoco/d2d49d95d2f75552ac64f0125440b35e) I've already add forcecollide, however it seems not working..
Could anyone help this problem?
The code for this chart is as follows.
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="my_dataviz"></div>
<script src="https://d3js.org/d3.v5.js"></script>
<script>
// set the dimensions and margins of the graph
var width = 450
var height = 450
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", 450)
.attr("height", 450),
color = d3.scaleOrdinal(d3.schemeAccent);
// create dummy data -> just one element per circle
var data = [{ "name": "A" , value:10},
{ "name": "B", value:20 },
{ "name": "C" , value:20 },
{ "name": "D" , value:60 },
{ "name": "E" , value:20 },
{ "name": "F", value:20 },
{ "name": "G", value:50 },
{ "name": "H" , value:100 }]
// Initialize the circle: all located at the center of the svg area
var node = svg.append("g")
.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("r", d => d.value)
.attr("cx", width / 2)
.attr("cy", height / 2)
.style("fill", d=>color(d.name))
.style("fill-opacity", 0.9)
// .attr("stroke", "#b3a2c8")
.style("stroke-width", 4)
.call(d3.drag() // call specific function when circle is dragged
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
var transitionTime = 3000;
var t = d3.timer(function (elapsed) {
var dt = elapsed / transitionTime;
simulation.force('collide').strength(Math.pow(dt, 2) * 0.7);
if (dt >= 1.0) t.stop();
});
// Features of the forces applied to the nodes:
var simulation = d3.forceSimulation()
.force("center", d3.forceCenter().x(width / 2).y(height / 2)) // Attraction to the center of the svg area
.force("collide", d3.forceCollide().strength(1).radius(30).iterations(1)) // Force that avoids circle overlapping
.force('attract', d3.forceRadial(0, width / 2, height / 2).strength(0.07))
// Apply these forces to the nodes and update their positions.
// Once the force algorithm is happy with positions ('alpha' value is low enough), simulations will stop.
simulation
.nodes(data)
.on("tick", function(d){
node
.attr("cx", function(d){ return d.x; })
.attr("cy", function(d){ return d.y; })
});
// What happens when a circle is dragged?
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(.03).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(.03);
d.fx = null;
d.fy = null;
}
</script>
</body>
</html>
Upvotes: 0
Views: 1666
Reputation: 1044
In the collide
force, you have a constant of 30
for the radius
, if you pass a callback function to return the node value (your nodes radii) the problem should bbe solved. Here is how I did it:
.force("collide", d3.forceCollide().strength(1).radius( (d) => d.value ).iterations(1))
Upvotes: 2