Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

d3.js how to generate tree hierarchy from csv or table

I have a csv with the following data:

world,country,state

World,US,CA

World,US,NJ

World,INDIA,OR

World,INDIA,AP

I need to convert in to a tree hierarchy as shown below:

{

"name": "World",
"children": [
  { "name": "US",
      "children": [
       { "name": "CA" },
       { "name": "NJ" }
     ]
  },
  { "name": "INDIA",
      "children": [
      { "name": "OR" },
      { "name": "TN" }
     ]
  }

] };

Using d3.nest I can only get to an json array, not a tree with the name 'children' in it. Anything additional I need to do? Reading API didn't really help, and I can't find a snippet of code that does the conversion anywhere I searched.

like image 813
santoku Avatar asked Apr 26 '18 14:04

santoku


1 Answers

Based on your other question, I'm assuming that you want to use this hierarchical data in a d3 hierarchical layout

Once read by d3.csv/tsv/dsv etc, you get an array with objects like so:

[
  { "world": "World","country": "US","state": "CA" },
  { "world": "World","country": "US","state": "NJ" },
  { "world": "World","country": "INDIA","state": "OR" },
  { "world": "World","country": "INDIA","state": "AP"}
]

We can use d3 nest to get us most of the way to a usable tree for a d3 hierarchy (tree, dendogram etc) layout:

{ 
  "key": "World", "values": [
    {
      "key": "US", "values": [
        { "world": "World", "country": "US", "state": "CA" },
        { "world": "World", "country": "US", "state": "NJ" }
      ]
    },
    {
      "key": "INDIA", "values": [
        { "world": "World", "country": "INDIA", "state": "OR" }, 
        { "world": "World", "country": "INDIA", "state": "AP" }
      ]
    }
  ]
}

This was made with:

var nestedData = d3.nest()
 .key(function(d) { return d.world; })
 .key(function(d) { return d.country; })
 .entries(data);

This produces an array, with each item in the array being a root. To get the json above with d3.nest() we need to get the first element in the array, in this case, nestedData[0];. Also note we generally don't need to nest the lowest level, in this case the state

Now we can feed this data to d3.hierarchy to get a hierarchy to use for visualizations. If you look at the documentation we have an identical data structure to the example, except our children are contained in a property called "values" rather than "children". No worry, d3.hierarchy allows us to set the name of the property that holds the children:

d3.hierarchy(data[, children])

...

The specified children accessor function is invoked for each datum, starting with the root data, and must return an array of data representing the children, or null if the current datum has no children. If children is not specified, it defaults to:

function children(d) { return d.children; }

So, to take the above data created from d3.nest, let's feed it to d3.hierarchy:

var root = d3.hierarchy(nestedData[0],function(d) { return d.values; })

Now we have a dataset that can be fed to any d3 hierarchical layout:

var data = [
 { "world": "World","country": "US","state": "CA" }, 
 { "world": "World","country": "US","state": "NJ" },
 { "world": "World","country": "INDIA","state": "OR" },
 { "world": "World","country": "INDIA","state": "AP"}
];

 
var nestedData = d3.nest()
  .key(function(d) { return d.world; })
  .key(function(d) { return d.country; })
  .entries(data);
	
var root = d3.hierarchy(nestedData[0], function(d) { return d.values; })

// Now draw the tree:

var width = 500;
var height = 400;

margin = {left: 10, top: 10, right: 10, bottom: 10}

var svg = d3.select("body").append("svg")
      .attr("width", width)
      .attr("height", height);
	  
var g = svg.append("g").attr('transform','translate('+ margin.left +','+ margin.right +')');

	  
var tree = d3.tree()
    .size([height-margin.top-margin.bottom,width-margin.left-margin.right]);

 var link = g.selectAll(".link")
    .data(tree(root).links())
    .enter().append("path")
      .attr("class", "link")
      .attr("d", d3.linkHorizontal()
          .x(function(d) { return d.y; })
          .y(function(d) { return d.x; }));

  var node = g.selectAll(".node")
    .data(root.descendants())
    .enter().append("g")
      .attr("class", function(d) { return "node" + (d.children ? " node--internal" : " node--leaf"); })
      .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })

  node.append("circle")
      .attr("r", 2.5);
	  
  node.append("text")
     .text(function(d) { return d.data.key; })
	 .attr('y',-10)
	 .attr('x',-10)
	 .attr('text-anchor','middle');
path {
  fill:none;
  stroke: steelblue;
  stroke-width: 1px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

Here's an example with an actual csv and a few more rows and levels.


If the csv data structure was different, containing parent, child pairs, then it will look something like this once parsed with d3.csv/tsv/dsv:

[
 {parent: "", name: "World"},
 {parent: "World", name:"US"},
 {parent: "World", name:"India"},
 {parent: "India", name:"OR" },
 {parent: "India", name:"AP" },
 {parent: "US", name:"CA" },
 {parent: "US", name:"WA" }
]

With this we can use d3.stratify instead of the combination of d3.nest and d3.hierarchy:

var data = [
 {parent: "", name: "World"},
 {parent: "World", name:"US"},
 {parent: "World", name:"India"},
 {parent: "India", name:"OR" },
 {parent: "India", name:"AP" },
 {parent: "US", name:"CA" },
 {parent: "US", name:"WA" }
];

// manipulate data:

var root = d3.stratify()
  .id(function(d) { return d.name; })
  .parentId(function(d) { return d.parent; })
  (data);
  
// Now draw the tree:

var width = 500;
var height = 400;

margin = {left: 10, top: 10, right: 10, bottom: 10}

var svg = d3.select("body").append("svg")
      .attr("width", width)
      .attr("height", height);
	  
var g = svg.append("g").attr('transform','translate('+ margin.left +','+ margin.right +')');

	  
var tree = d3.tree()
    .size([height-margin.top-margin.bottom,width-margin.left-margin.right]);

 var link = g.selectAll(".link")
    .data(tree(root).links())
    .enter().append("path")
      .attr("class", "link")
      .attr("d", d3.linkHorizontal()
          .x(function(d) { return d.y; })
          .y(function(d) { return d.x; }));

  var node = g.selectAll(".node")
    .data(root.descendants())
    .enter().append("g")
      .attr("class", function(d) { return "node" + (d.children ? " node--internal" : " node--leaf"); })
      .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })

  node.append("circle")
      .attr("r", 2.5);
	  
  node.append("text")
     .text(function(d) { return d.data.name; })
	 .attr('y',-10)
	 .attr('x',-10)
	 .attr('text-anchor','middle');
path {
  fill: none;
  stroke: steelblue;
  stroke-width: 1px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
like image 83
Andrew Reid Avatar answered Sep 27 '22 22:09

Andrew Reid