Reputation: 1369
I am using d3.nest()
to convert flat JSON files into hierarchical trees. This works just fine if you know in advance the levels that found in your data: d3.nest()
creates a nesting-level for each key function you pass it.
However, I would like to know if there is an elegant way to dynamically pass key functions. This might be an elementary question, but my Javascript skills are also elementary.
This is a static example working with key names stored in an array:
var levels = ['first', 'second', 'third']
var root = {
"key":"root",
"values":
d3.nest()
.key(function(d){ return d[levels[0]] })
.key(function(d){ return d[levels[1]] })
.key(function(d){ return d[levels[2]] })
.entries(data)
}
My solution now is an if-else loop that pseudo-dynamically creates different nestings:
// build the nested tree pseudo-dynamically
if (levels.length === 1) {
var nest = d3.nest()
.key(function(d){return d[levels[0]]})
.entries(data);
} else if (levels.length === 2) {
var nest = d3.nest()
.key(function(d){return d[levels[0]]})
.key(function(d){return d[levels[1]]})
.entries(data);
} else if (levels.length === 3) {
var nest = d3.nest()
.key(function(d){return d[levels[0]]})
.key(function(d){return d[levels[1]]})
.key(function(d){return d[levels[2]]})
.entries(data);
}
[...]
Is there an elegant way to dynamically pass keys to d3.nest()
? What I would like is something in principle like the following example (which DOES NOT WORK):
var root = {
"key":"root",
"values":
d3.nest()
for (var i = 0; i < levels.length; i++) {
.key(function(d){return d[levels[i]]})
}
.entries(data)
}
Thanks for your time!
Upvotes: 2
Views: 4766
Reputation: 127
A simpler solution, following AmeliaBR one:
let levels = ['first', 'second', 'third']
// create the nest
let nest = d3.nest()
// for each value in 'levels' array
// add the key() function
levels.forEach(function(level){
// re-assign to the nest key
nest = nest.key(d => d[level])
});
// compute the nest
nest = nest.entries(data)
// add the root
let root = {
"key":"root",
"values": nest
}
Upvotes: 1
Reputation: 1369
Found another possible solution after posting the question. It uses forEach and it is described here.
Upvotes: 1
Reputation: 27544
It's not as easy as you hypothetical elegant code, but you can certainly simplify it compared to your if-else method.
A first instinct is to do something like this:
var levels = ['first', 'second', 'third']
var nest = d3.nest();
for (var i = 0; i < levels.length; i++) {
nest = nest.key(function(d){return d[levels[i]]});
//create a new nesting function that has one more key function added
//and save it in the variable
}
var root = {
"key":"root",
"values": nest.entries(data) //compute the nest
}
However, this doesn't work because your nesting function won't be used until after you create all your nesting functions, so by the time the nest is actually run your i
variable equals 3 and levels[i]
returns undefined, and therefore d[levels[i]]
returns undefined. Example here.
You need to create a separate function enclosure so that the correct value from levels
is locked in:
function createNestingFunction(propertyName){
return function(d){
return d[propertyName];
};
}
var levels = ['first', 'second', 'third']
var nest = d3.nest();
for (var i = 0; i < levels.length; i++) {
nest = nest.key( createNestingFunction(levels[i]) );
//create a new nesting function that has one more key function added
//and save it in the variable
//the function `createNestingFunction` is called *immediately*
//with a parameter based on the current value of `i`
//the returned function will always use that parameter,
//regardless of how many times createNestingFunction is called
}
var root = {
"key":"root",
"values": nest.entries(data) //compute the nest
}
The working version of the example: http://fiddle.jshell.net/brVLH/1/
Upvotes: 5