Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

d3 nesting on several keys with a loop

I am using d3.nest() in order to make a hierarchical object from a CSV file.

Could you please help me understand why the following code does not work. I didn't manage to use the nesting function within a loop, as described below.

I have the following CSV file, taken from the examples on d3 website:

"type1","type2","type3","type4","type5","size"
"flare","analytics","cluster","AgglomerativeCluster","","3938"
"flare","analytics","cluster","CommunityStructure","","3812"
"flare","analytics","cluster","MergeEdge","","743"
"flare","analytics","graph","BetweennessCentrality","","3534"
"flare","analytics","graph","LinkDistance","","5731"

This basic nesting works:

data = data.entries(csv)
        .key(function(d) {return d.type1; })
        .key(function(d) {return d.type2; })
        .key(function(d) {return d.type3; })
        .entries(csv);

I want to use an array of values to specify my keys in order to modify them dynamically.

This works:

    var data = d3.nest();
    var nesting = ["type1","type2","type3"];
    data = data.key(function(d) {return d[nesting[0]]; });
    data = data.key(function(d) {return d[nesting[1]]; });
    data = data.key(function(d) {return d[nesting[2]]; });
    data = data.entries(csv);

But it does not work with a loop...

    var data = d3.nest();
    for(var i=0;i<nesting.length;i++)
    {
        data = data.key(function(d) {return d[nesting[i]]; });
    }
    data = data.entries(csv);

I can't understand why the loop version is not working... Maybe I miss something about the d3.nest() capabilities...

Also, I would like to know if there is a way to "skip" a nesting level if there is nothing filled at this level (ie: the "type5" level on all the lines from the extract above). How could I do that?

Thanks a lot for reading!

like image 497
bobby Avatar asked Dec 03 '22 02:12

bobby


1 Answers

This isn't a problem with the .nest() operator, it's a problem with JavaScript closures. Any time you have this pattern:

for (var x=0; x < y; x++) {
    something.attachCallback(function() {
        // now do something with x
    });
}

You are going to have problems with the closure. The inner anonymous function you're defining doesn't include a copy of the value of x, it includes a reference to the outer variable x, which will update when the outer variable updates. So by the end of your loop, the value of x in every one of your callback functions will be the final value of x in the loop (in the code above, y; in your code, nesting.length).

The D3 .nest() operator uses its .key() arguments as callbacks - they aren't executed until you call .map() or .entries(). So the problem above applies.

There are various ways to fix this; I tend to use .forEach() instead of a for loop. This doesn't work in older browsers, but neither will most of D3, so you're probably safe:

var data = d3.nest();
nesting.forEach(function(key) {
    data.key(function(d) {return d[key]; })
});

Another option is to use a separate function for callback definition. Passing the iterator variable to another function will "fix" its value, as the callback now has a reference to the argument of the creator function, not to the original x variable:

var data = d3.nest();

function addKey(index) {
    data.key(function(d) { return d[nesting[index]]; })
}

for(var i=0;i<nesting.length;i++) {
    addKey(i);
}

There are a few other approaches as well, but in my opinion forEach is the most elegant.

like image 99
nrabinowitz Avatar answered Dec 06 '22 04:12

nrabinowitz