d3.nest() key and values conversion to name and children
Asked Answered
C

4

16

I am working on creating a Treemap from a csv file. The data in the csv file is hierarchical, as a result I used d3.nest().

However, the resulting JSON is in the form of {key:"United States", values:[...]}. The zoom-able treemap requires hierarchy as {name:"United States", children:[...]}. I have tried replacing name and children to key and values in the example, but it doesn't work.

If anyone has already looked into using key and values on a zoomable treemap, please help. I am new to D3 and I don't know whether d.children means structure or value from the data.

This is the code to convert World Continents, Regions, and Countries from CSV to a hierarchy using d3.

$ d3.csv("../Data/WorldPopulation.csv", function (pop) {
    var treeData= { "key": "World", "values": d3.nest()
   .key(function (d) { return d.Major_Region; })
   .key(function (d) { return d.Region; })
   .key(function (d) { return d.Country; })
   .entries(pop)
};

The first few lines of the result is:

 `[{"key":"AFRICA","values":[{"key":"Eastern Africa","values"
 [{"key":"Burundi","values":[.........`

I can not use the zoomable treemap because it requires name and children labels in json rather than key and values.

Cuba answered 2/7, 2013 at 1:44 Comment(1)
Is there a property named "size" in your converted object? Please check the data structure at mbostock.github.io/d3/talk/20111018/flare.json and You'd better post your entire json code here.Sequence
S
10

The best way to convert a nest to a treemap is specifying children accessor function with Treemap.children().

In the zoomable treemap example , it requires not only "children" but also "name" and "size". You can do either:

1)change the accessor functions of those properties so that keep using "key" and "value".

Let's change the source code.

1.1)line 79 & 86:

 .style("fill", function(d) { return color(d.parent.name); });

 .text(function(d) { return d.name; })

Replace ".name" with ".YOUR_NAME_KEY"(i.e. ".key")

 .style("fill", function(d) { return color(d.parent.YOUR_NAME_KEY); });

 .text(function(d) { return d.YOUR_NAME_KEY; })

1.2)line 47:

var treemap = d3.layout.treemap()
.round(false)
.size([w, h])
.sticky(true)
.value(function(d) { return d.size; });

Append a line to specify children accessor function.(i.e ".values")

var treemap = d3.layout.treemap()
.round(false)
.size([w, h])
.sticky(true)
.value(function(d) { return d.YOUR_SIZE_KEY; })
.children(function(d){return d.YOUR_CHILDREN_KEY});

1.3)line 97 & 51:

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

Replace ".size" with ".YOUR_SIZE_KEY"(you didn't mention in your resulting JSON)

function size(d) {
  return d.YOUR_SIZE_KEY;
}

P.S. Maybe something omitted, you need verify it yourself.

2)convert your JSON structure to fit the example with Array.map().

Sequence answered 2/7, 2013 at 6:57 Comment(3)
Yes i want to know how to do 1) change the zoomable treemap to use key and value instead of name and children. My data is to large and i dont want to parse the structure just to change labels.Cuba
Thankyou so much, this was truly helpful. Though i could not get it to work perfectly jsfiddle.net/bvPUg/7. The names are not showing.Cuba
Because some of your datas doesn't have "key" but their parents does. See jsfiddle.net/e9CebSequence
N
8

I completely agree with @Anderson that the easiest approach to this issue is to use the treemap children(function) and .value(function) methods to specify the names of the properties within the nested dataset.

However, a duplicate question has recently been posted in which the questioner specifically asks for help using an Array.map approach. So here it is:

The array map(function) method creates a new array where each element in the array is the result of running the specified function on each element of the original array. Specifically, the function is run with up to three arguments: the element from the original array, the index of that element, and the original array as a whole. For the purposes of manipulating the property names, we only need the first argument.

The nested data in the original post has three levels, equivalent to the three key functions. Therefore, we're going to need a three-level mapping function:

var treeData = {
    "key": "World",
    "values": d3.nest()
        .key(function(d) {
            return d.Major_Region;
        })
        .key(function(d) {
            return d.Region;
        })
        .key(function(d) {
            return d.Country;
        })
        .entries(pop)
};

var treeData2 = {
    "name": "World",
    "children": treeData.values.map(function(major) {

        return {
            "name": major.key,
            "children": major.values.map(function(region) {

                return {
                    "name": region.key,
                    "children": region.values.map(function(country) {

                        return {
                            "name": country.key,
                            "children": country.values
                        };

                    }) //end of map(function(country){
                };

            }) //end of map(function(region){
        };

    }) //end of map(function(major){
}; //end of var declaration

You could also probably implement this with a recursive function, which would be especially useful if you had many more levels of nesting.

Nance answered 29/4, 2014 at 15:43 Comment(3)
This doesn't work for me! My browser crashes when I pass treeData2 to my parition's nodes function! Any help would be greatly appreciated .. In firefox I'm getting an Out Of Memory exception!Altagraciaaltaic
Sounds like you've got a loop in your tree structure. Beyond that, I don't have enough information to help you.Nance
I have used this solution and it has worked for me, but I am also doing the count on the leave nodes. Unfortunately, it is labeling count as children as well, whereas I want to have a distinct label for size. I have posted here, could you please help me.. stackoverflow.com/questions/35120044Bobwhite
A
4

Since d3-collection has been deprecated in favor of d3.array, we can use d3.rollups to achieve what used to work with d3.nest:

var input = [
  { "Continent": "Europe", "Country": "Spain", "City": "Madrid", "value": "3" },
  { "Continent": "Europe", "Country": "Spain", "City": "Barcelona", "value": "30" },
  { "Continent": "Europe", "Country": "France", "City": "Paris", "value": "243" },
  { "Continent": "America", "Country": "Argentina", "City": "Buenos Aires", "value": "300" },
  { "Continent": "America", "Country": "Argentina", "City": "Buenos Aires", "value": "250" },
  { "Continent": "America", "Country": "Argentina", "City": "Rosario", "value": "200" }
];

// Nesting:
var rolled_up = d3.rollups(
  input,                           // The array on which to apply the rollup
  vs => d3.sum(vs, v => +v.value), // The reducing function to apply on rollups' leafs
  d => d.Continent,                // A first level of nesting
  d => d.Country                   // A second level of nesting
);

// Formatting:
var output = {
  key: "World",
  values: rolled_up.map(x => ({
    key: x[0], // continent
    values: x[1].map(x => ({
      key: x[0], // country
      values: x[1]
    }))
  }))
}

console.log(output);
<script src="https://d3js.org/d3-array.v2.min.js"></script>

This:

  • Applies a d3.rollups:
    • d3.rollups take 3 parameters: the input array, a reducing function and a variable number of mappers for the different levels of nesting
    • The reducing function (vs => d3.sum(vs, v => +v.value)) takes the values associated to the grouped countries, cast them to int and sum them (using d3.sum)
    • The two levels of nesting group elements first on the Continent and then on the Country
  • Formats each nested part of the rollups' output in order to get the expected output.

Here is the intermediate result produced by d3.rollups (before formatting):

var input = [
  { "Continent": "Europe", "Country": "Spain", "City": "Madrid", "value": "3" },
  { "Continent": "Europe", "Country": "Spain", "City": "Barcelona", "value": "30" },
  { "Continent": "Europe", "Country": "France", "City": "Paris", "value": "243" },
  { "Continent": "America", "Country": "Argentina", "City": "Buenos Aires", "value": "300" },
  { "Continent": "America", "Country": "Argentina", "City": "Buenos Aires", "value": "250" },
  { "Continent": "America", "Country": "Argentina", "City": "Rosario", "value": "200" }
];

var rolled_up = d3.rollups(
  input,
  vs => d3.sum(vs, v => +v.value),
  d => d.Continent,
  d => d.Country
);

console.log(rolled_up);
<script src="https://d3js.org/d3-array.v2.min.js"></script>
Abort answered 16/6, 2019 at 15:50 Comment(0)
T
2

Hey guys I think I found a fairly simple solution. I accomplished a very nice nesting of a large dataset (400,000 rows) for a hierarchical bar chart in a very streamline way. It utilizes the Underscore library and an additional function _.nest. Simply download and include the two libraries necessary

"underscore-min.js" "underscore.nest.js"

Then use the _.nest function to create your structure. Here's my line:

var newdata = _.nest(data, ["Material", "StudyName"]);

"Material" and "StudyName" are the columns I want to group my structure to.

There are other options to use this function if you need to accomplish more things but I will leave it like this

Tarazi answered 22/10, 2014 at 14:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.