skip to Main Content

I am using the d3.js pack() method to pack the circles. But I want to leave an empty circle in the center like a gap, and then pack the circles around it. I tried using forceSimulation without luck. I want to achieve something like shown in the picture using pack().

Circles packed with a hollow at the center

2

Answers


  1. Chosen as BEST ANSWER
    <!DOCTYPE html>
    <html>
    
    <body>
      <div id="chart"></div>
    
      <script type="module">
        import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
    
        const width = 954;
        const height = 954;
        const size = Math.max(width, height);
        const color = d3.scaleSequential([0, 2 * Math.PI], d3.interpolateRainbow);
        const circles = d3
          .packSiblings([
            {
              r: 100,
              x: 0,
              y: 0,
              angle: 0
            },
            ...d3
              .range(100)
              .map(d3.randomUniform(8, 26))
              .map((r) => ({ r }))
              .sort((a, b) => b.r - a.r)
          ])
        //.filter((d) => -500 < d.x && d.x < 500 && -500 < d.y && d.y < 500);
    
        const svg = d3.select('#chart').append("svg")
          .attr("viewBox", [-width / 2, -height / 2, width, height])
          .style("background", "#333")
          .attr("stroke", "currentColor")
          .attr("stroke-width", 1.5);
    
        svg.selectAll("circle")
          .data(circles)
          .join("circle")
          .attr("fill", (d, i) => i === 0 ? 'none' : color(d.angle = Math.atan2(d.y, d.x)))
          .attr("stroke", (d, i) => i === 0 ? 'none' : 'black')
          .attr("cx", d => Math.cos(d.angle) * (size / Math.SQRT2 + 30))
          .attr("cy", d => Math.sin(d.angle) * (size / Math.SQRT2 + 30))
          .attr("r", d => d.r - 1)
          .transition()
          .ease(d3.easeCubicOut)
          .delay(d => Math.sqrt(d.x * d.x + d.y * d.y) * 10)
          .duration(1000)
          .attr("cx", d => d.x)
          .attr("cy", d => d.y);
      </script>
    
    </body>
    
    </html>
    

  2. You can add a first Big circle that you don’t display

    like I did here :

    https://observablehq.com/d/220e0ac384e98fd0

    circles = d3
      .packSiblings([
        {
          r: 100,
          x: 0,
          y: 0,
          angle: 0
        },
        ...d3
          .range(2000)
          .map(d3.randomUniform(8, 26))
          .map((r) => ({ r }))
      ])
      .filter((d) => -500 < d.x && d.x < 500 && -500 < d.y && d.y < 500)
    

    Not displaying first circle

    ...
      .join("circle")
      .attr("fill", (d,i) => i === 0 ? 'none' : color(d.angle = Math.atan2(d.y, d.x)))
      .attr("stroke", (d,i) => i === 0 ? 'none':'black')
    ...
    

    EDIT:
    To sort bubbles to have bigger one at center:

    circles = d3
      .packSiblings([
        {
          r: 100,
          x: 0,
          y: 0,
          angle: 0
        },
        ...d3
          .range(2000)
          .map(d3.randomUniform(8, 26))
          .map((r) => ({ r }))
          .sort((a, b) => b.r - a.r)
      ])
      .filter((d) => -500 < d.x && d.x < 500 && -500 < d.y && d.y < 500)
    

    I also updated my observable to add a sort toggle

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search