9

I have this JSON data structure:

[
    { "dep": "d1", "name": "name1", "size": "size1" },
    { "dep": "d1", "name": "name2", "size": "size2" },
    { "dep": "d2", "name": "name1", "size": "size3" },
    { "dep": "d2", "name": "name1", "size": "size4" }
]

and I want to convert it to a nested structure like this:

{
    "name": "root",
    "children": [
        { "name": "d1",
            "children": [
                { "dep": "d1", "name": "name1", "size": "size1" },
                { "dep": "d1", "name": "name2", "size": "size2" }
            ]
        },
        { "name": "d2",
            "children": [
                { "dep": "d2", "name": "name1", "size": "size3" },
                { "dep": "d2", "name": "name2", "size": "size4" }
            ]
        }
    ]
}

... and further using it to make the Reingold–Tilford Tree. Can anyone point me to the right direction, I'm pretty new to D3!

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
alta
  • 91
  • 1
  • 1
  • 2
  • FYI, while you might receive the data as JSON, when you convert it, you are working with JavaScript objects and arrays. At this point the problem has nothing to do with JSON anymore. – Felix Kling Apr 10 '13 at 00:10
  • http://stackoverflow.com/questions/17847131/generate-multilevel-flare-json-data-format-from-flat-json This should solve your purpose. – synaptikon Jul 25 '13 at 20:08
  • How did you create the initial Json data structure? Did you have a data source and added in the data? Or just hard code? – noc_coder Sep 25 '16 at 23:11

5 Answers5

9

The strategy is to create a new empty data structure corresponding to what you want, and then fill it by going through the whole original dataset. Here is the code:

var data = [
    { "dep": "d1", "name": "name1", "size": "size1" },
    { "dep": "d1", "name": "name2", "size": "size2" },
    { "dep": "d2", "name": "name1", "size": "size3" },
    { "dep": "d2", "name": "name2", "size": "size4" }
]

var newData = {"name": "root", "children": {}}

data.forEach(function (d) {
    if (typeof newData.children[d.dep] !== 'undefined') {
        newData.children[d.dep].children.push(d)
    } else {
        newData.children[d.dep] = {"name": d.dep, "children": [d]}
    }
})
newData.children = Object.keys(newData.children).map(function (key) {
    return newData.children[key];
});

The assignment at the end is to transform the object into an array.

Which gives the desired result for newData:

{
    "name": "root",
    "children": [
        { "name": "d1",
            "children": [
                { "dep": "d1", "name": "name1", "size": "size1" },
                { "dep": "d1", "name": "name2", "size": "size2" }
            ]
        },
        { "name": "d2",
            "children": [
                { "dep": "d2", "name": "name1", "size": "size3" },
                { "dep": "d2", "name": "name2", "size": "size4" }
            ]
        }
    ]
}

jsFiddle: http://jsfiddle.net/chrisJamesC/eB4jF/

Note: This method does not work for nested structures. It will be way harder to do this for nested structures but you can always use a recursive function.


EDIT: As suggested by @imarane in his answer, you can use the d3.nest() which is way better than my hand made solution. You might thus accept his answer. By playing with it, it even was really easy to have multiple levels of nest:

var data = [
    { "dep": "d1", "name": "name1", "size": "size1" },
    { "dep": "d1", "name": "name2", "size": "size2" },
    { "dep": "d2", "name": "name1"},
    { "dep": "d2"}
]

var newData = {
    "key":"root", 
    "children": 
        d3.nest()
            .key(function(d){return d.dep})
            .key(function(d){return d.name})
            .key(function(d){return d.size})
            .entries(data)
}     

Which give:

{"key":"root","children":[
    {"key":"d1","values":[
        {"key":"name2","values":[
            {"dep":"d1","name":"name2","size":"size1"},
            {"dep":"d1","name":"name2","size":"size2"}
        ]}
    ]},
    {"key":"d2","values":[
        {"key":"name1","values":[
            {"dep":"d2","name":"name1"}
        ]},
        {"key":"undefined","values":[
            {"dep":"d2"}
        ]}
    ]}
]}

Which the following data structure (I hope better to understand the whole point):

var data = [
    { "dep": "d1", "name": "name2", "size": "size1" },
    { "dep": "d1", "name": "name2", "size": "size2" },
    { "dep": "d2", "name": "name1"},
    { "dep": "d2"}
]

JsFiddle: http://jsfiddle.net/chrisJamesC/eB4jF/2/

More on Nest: http://bl.ocks.org/phoebebright/raw/3176159/

martineau
  • 119,623
  • 25
  • 170
  • 301
Christopher Chiche
  • 15,075
  • 9
  • 59
  • 98
  • Thanks, I'll give your first option a try... I've played with the .nest() and it's working for data transformation. But the "tree layout" wants "name": and "children" as default and I couldn't get the "key": and "values": to work. – alta Apr 12 '13 at 11:04
  • ...the first option works like a charm! The d3.nest() part is very efficient but the "key" and "values" needs to be worked around. Thanks! – alta Apr 12 '13 at 11:50
  • 1
    Do you have any suggestions: How to map all "key" to "name" and "values" to "children" in your 2nd. suggestion? – alta Apr 12 '13 at 12:12
  • I don't see any other method than going trough all elements recursively and changing names, which requires copying and deleting each field. However you can always use the [source code of nest()](https://github.com/mbostock/d3/blob/master/src/arrays/nest.js) as inspiration. – Christopher Chiche Apr 15 '13 at 04:34
4

another option is to use the nest method built into D3....

var nested = d3.nest()
.key(function(d,i){ return d.dep; })
.entries(data);

which outputs:

 [
  {
    "key": "d1",
    "values": [
      {
        "dep": "d1",
        "name": "name1",
        "size": "size1"
      },
      {
        "dep": "d1",
        "name": "name2",
        "size": "size2"
      }
    ]
  },
  {
    "key": "d2",
    "values": [
      {
        "dep": "d2",
        "name": "name1",
        "size": "size3"
      },
      {
        "dep": "d2",
        "name": "name2",
        "size": "size4"
      }
    ]
  }
]

JsFiddle: http://jsfiddle.net/imrane/bSGrG/1/

imrane
  • 1,542
  • 2
  • 16
  • 29
  • Thanks, I've tried this one. But as I mentioned I can't get mapped the "key" and "values" to "name": and "children": as it's required by the tree layout. I'm sure you have some tricks in your sleeves to map those? – alta Apr 12 '13 at 11:55
  • well - in the tree example you can change the children accessor to d.values by using d3.layout.tree().children(function(d){return d.values})...see here for what they have done.. http://blog.pixelingene.com/2011/07/building-a-tree-diagram-in-d3-js/ – imrane Apr 12 '13 at 13:49
  • here https://groups.google.com/forum/?fromgroups=#!topic/d3-js/L3UeeUnNHO8 and here http://stackoverflow.com/questions/11088303/how-to-convert-to-d3s-json-format ...these are old so I don't know what has changed since. GL. – imrane Apr 12 '13 at 14:17
1

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

var output = {
  "name": "root",
  "children": d3.groups(input, d => d.dep).map(([k, vs]) => ({ "name": k, "children": vs }))
};

For example:

var input = [
  { "dep": "d1", "name": "name1", "size": "size1" },
  { "dep": "d1", "name": "name2", "size": "size2" },
  { "dep": "d2", "name": "name1", "size": "size3" },
  { "dep": "d2", "name": "name1", "size": "size4" }
];

var output = {
  "name": "root",
  "children": d3.groups(input, d => d.dep).map(([k, vs]) => ({ "name": k, "children": vs }))
};

console.log(output);
<script src="https://d3js.org/d3-array.v2.min.js"></script>
Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
0

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

src="underscore-min.js"
src="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

Volker E.
  • 5,911
  • 11
  • 47
  • 64
0

Use this. U can check the output in the console of the browser if u need.

function reSortRoot(root, value_key) {
                //console.log("Calling");
                for ( var key in root) {
                    if (key == "key") {
                        root.name = root.key;
                        delete root.key;
                    }
                    if (key == "values") {
                        root.children = [];
                        for (item in root.values) {
                            root.children.push(reSortRoot(root.values[item],
                                    value_key));
                        }
                        delete root.values;
                    }
                    if (key == value_key) {
                        root.value = parseFloat("1");
                        delete root[value_key];
                    }
                }
                return root;
            } 

            var testdata=[
                          { "dep": "d1", "name": "name1", "size": "size1" },
                          { "dep": "d1", "name": "name2", "size": "size2" },
                          { "dep": "d2", "name": "name1", "size": "size3" },
                          { "dep": "d2", "name": "name1", "size": "size4" }
                      ];

            var testJson = d3.nest()
            .key(function(d)  { return d.dep; })
            .entries(testdata); 
             console.log(testJson);

            var testRoot={};
            testRoot.key = "Information";
            testRoot.values = testJson;
            testRoot = reSortRoot(testRoot, "name");

             console.log(testRoot);
pritesh
  • 533
  • 4
  • 15