suneet
suneet

Reputation: 400

D3 draw n circles in a tree

enter image description here I have the following data, using which i plotted a d3 tree, where each node is a rectangle. Now, I want to draw n circles on each rectangle(where n is value of 'minor' in each node(see data), but not sure what's the easiest way to achieve this.

// Data
{
"minor": 1,
"critical": 0,
"children": [{
    "minor": 2,
    "critical": 0,
    "children": [{
        "minor": 3,
        "critical": 0,
        },
        {
        "minor": 2,
        "critical": 1,
        },
        {
        "minor": 1,
        "critical": 1,
        }]
    }]
}

// JS
  const nodeEnter = node.enter()
    .append('g')
    .attr('class', 'node')
    .style('font-size', '12px')
    .attr("transform", function (d) {
      return "translate(" + source.y0 + "," + source.x0 + ")";
  })
  .on('click', click);

  // Add Rectangle for the nodes
  nodeEnter.append('rect')
    .attr('class', 'node')
    .attr("width", function (d) { return d.children || d._children ? 150 : 350 })
    .attr("height", 70)
    .attr('stroke', '#ccc')
    .attr('stroke-width', '1');

Upvotes: 0

Views: 309

Answers (1)

Ruben Helsloot
Ruben Helsloot

Reputation: 13129

You can use d3.range to create an array of a specific length. Then you can treat this array just like any other d3 data structure.

const nCircles = 5;
const radius = 8;
const padding = 2;

d3.select('svg')
  .selectAll('circle')
  .data(d3.range(nCircles))
  .enter()
  .append('circle')
  .attr('cx', function(_, i) {
    return radius + padding + i * 2 * (radius + padding);
  })
  .attr('cy', radius + padding)
  .attr('r', radius)
  .attr('fill', 'darkred');
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg></svg>

Alternatively, you can use a fully ES6 solution, but it's unnecessarily complex:

const array = new Array(nCircles).fill(undefined).map(function(_, i) { return i; });

From your data, I gather that each node has the attributes minor and critical. Now, to update the balls of your newly inserted nodes, you can use

nodeEnter
    .selectAll('.minor')
    // Here you take the data from the node and transform it into
    // the data for the circles. This is what makes it generic.
    .data(function(d) { d3.range(d.minor))
    .enter()
    .append('circle')
    .classed('minor', true)
    .attr('cx', function(_, i) {
      return radius + padding + i * 2 * (radius + padding);
    })
    .attr('cy', radius + padding)
    .attr('r', radius)
    .attr('fill', 'darkyellow');

// And for critical messages:
nodeEnter
    .selectAll('.critical')
    .data(function(d) { d3.range(d.critical))
    .enter()
    .append('circle')
    .classed('critical', true)
    .attr('cx', function(_, i) {
      // To position it next to the minor circles, we need to
      // know how many there are
      const nMinorCircles = d3.select(this).select('.minor').size();
      return radius + padding + 2 * (radius + padding) * (i + nMinorCircles);
    })
    .attr('cy', radius + padding)
    .attr('r', radius)
    .attr('fill', 'darkred');

Upvotes: 1

Related Questions