Wednesday, 27 January 2021

D3.js v5 - Swarm Tree - How to iteratively center swarms around tree nodes

The task is to append a swarm of small circles around the nodes of a tree (where I have placed bigger circles). The depth of the tree is tiny -- just two, However, there is a slight degree of complexity when considering I'm appending several trees iteratively.

To keep it simple, I didn't even try to append swarms to all trees. Instead, I set myself with a more modest goal of appending the swarm to just one node of one tree. Comments and snippet below:

var margins = {top:100, bottom:300, left:100, right:100};

var height = 600;
var width = 900;

var totalWidth = width+margins.left+margins.right;
var totalHeight = height+margins.top+margins.bottom;

var svg = d3.select('body')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight);

var graphGroup = svg.append('g')
.attr('transform', "translate("+margins.left+","+margins.top+")");

  var data = [
{name:"China", tree: {
  "name": "Actors",
  "children": [
        {
          "name": "Jackie Chan",
          "movies":d3.range(50).map(function(v) {return v.toString()+'_movie'})
         },
         {
           "name": "Jet Li",
           "movies":d3.range(38).map(function(v) {return v.toString()+'_movie'})
          },
          {
            "name": "Chow Yun Fat",
            "movies":d3.range(46).map(function(v) {return v.toString()+'_movie'})
           }
      ]
}},
{name:"USA", tree: {
  "name": "Actors",
  "children": [
    {
      "name": "Tom Cruise",
      "movies":d3.range(7).map(function(v) {return v.toString()+'_movie'})
     },
     {
       "name": "Johnny Depp",
       "movies":d3.range(4).map(function(v) {return v.toString()+'_movie'})
      },
      {
        "name": "Harrison Ford",
        "movies":d3.range(20).map(function(v) {return v.toString()+'_movie'})
       },
       {
         "name": "Ryan Gosling",
         "movies":d3.range(4).map(function(v) {return v.toString()+'_movie'})
        }
      ]
}},
{name:"UK", tree: {
  "name": "Actors",
  "children": [
    {
      "name": "Daniel Day-Lewis",
      "movies":d3.range(36).map(function(v) {return v.toString()+'_movie'})
     },
     {
       "name": "Christoper Lee",
       "movies":d3.range(50).map(function(v) {return v.toString()+'_movie'})
      },
      ]
}}

  ];

  //var formatComma = d3.format(",");

  var columns = 3;
  var spacing = 200;
  var vSpacing = 180;

  var regionG = graphGroup.selectAll('.region')
.data(data)
.enter()
.append('g')
.attr('class', 'region')
.attr('id', (d, i) => 'region' + i)
.attr('transform', (d, k) => {
  var horSpace = (k % columns) * spacing;
  var vertSpace = ~~((k / columns)) * vSpacing;
  return "translate(" + horSpace + "," + vertSpace + ")";
});

var miniTree = d3.tree()
.size([150, 75]);

regionG.append('rect')
.attr('x',0)
.attr('y',0)
.attr('width',100)
.attr('height',25)
.style('fill',"#003366");

regionG.append('text')
.attr('x',50)
.attr('y',-10)
.attr('text-anchor','middle')
.text(function(d) {return d.name});


regionG.selectAll(null)
.data( function(d) {
    return miniTree(d3.hierarchy(d.tree)).descendants().slice(1)

    })
  .enter().append("path")
.attr("transform", "translate(-25,20)") // extra positioning.
.attr("class", "link")
.attr("d", function(d) {
   return "M" + d.x + "," + d.y
     + "C" + d.x + "," + (d.y + d.parent.y) / 2
     + " " + d.parent.x + "," +  (d.y + d.parent.y) / 2
     + " " + d.parent.x + "," + d.parent.y;
   });

regionG.selectAll(null)
.data( function(d) {return miniTree(d3.hierarchy(d.tree)).descendants() })
  .enter().append("g")
.attr("class", function(d) {
  return "node" +
    (d.children ? " node--internal" : " node--leaf"); })
.attr("transform", function(d) {
  return "translate(" + (d.x - 25) + "," + (d.y+20) + ")"; }) // with extra positioning.
  .append("circle")
  .attr('r',10)
  .attr('cx',0)
  .attr('cy',0)
  .style('fill',"#003366");



//start simulation for one node
  var simulation = d3.forceSimulation(data[0].tree.children[0].movies)
  .force("x", d3.forceX(function(d) {
  return 0;
  }).strength(0.03))
  .force("y", d3.forceY(function(d) {
  return 95;
  }).strength(0.1))
  .force("collide", d3.forceCollide(04).iterations(1))
  .stop();

  simulation.tick(75);
//append circles at (0,95) -- the center coorinates of the bottom left tree node
  var circles = graphGroup.selectAll(null)
  .data(data[0].tree.children[0].movies)
  .enter()
  .append("circle")
  .attr("r", 3)
  .attr("cx", function(d) { return d.x;})
  .attr("cy", function(d) { return d.y;})
  .style('fill', function(d) {return "#4f81b9"; });
<script src="https://d3js.org/d3.v5.min.js"></script>

This has given a huge swath of errors that I'm having trouble debugging:

Cannot create property 'vx' on string "0_movie" ... Cannot create property 'vx' on string "1_movie" ...

The desired result should be circles appended around the node. The idea is that:

  • The top shows the region: China
  • The big circles on the tree nodes show the actors: Jackie Chan
  • The smaller swarm circles are to show the number of movies he/she acted in

The end result will have swarm circles around each node, but for the purposes of this question, we can just focus on the bottom-left node.

Question

How can I append swarm clusters at each node at the bottom-most depth of my tree?



from D3.js v5 - Swarm Tree - How to iteratively center swarms around tree nodes

No comments:

Post a Comment