Joe1314
Joe1314

Reputation: 35

D3 force layou/ avoid overlapping node ( forcecollide, radius)

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?

enter image description here

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

Answers (1)

Guillermo
Guillermo

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

Related Questions