Reputation: 500
I have a nested array of data that I am using to create a grouped bar chart. I am having trouble assigning a key to the datajoin of the nested array.
Below is the input data, the working datajoin for the top level, and two partial implementations of what I am trying to accomplish. I am so close!
// input data
var data = [
{"key":"cat1","value":[{"key":"subcatA","value":100},{"key":"subcatB","value":200}]},
{"key":"cat2","value":[{"key":"subcatA","value":150},{"key":"subcatB","value":250}]}
];
To access the top level and build the main categories, I do this:
// top level (all good)
var plot = d3.select('#plot');
plot.selectAll(".category")
.data(data,function(d) {return d.key;}) // <-- return key on datajoin
.enter().append("g")
.attr("class", "category")
Returning d.key
on the datajoin keeps track of each category such that enter() and exit() is not a giant mess of animation. See Mike Bostock's General Update Pattern, II for reference on adding a key to a datajoin.
To plot the second level, I do this:
// second level (plots but no key is assigned)
plot.selectAll(".category").selectAll(".bar")
.data(function (d) {return d.value; }) // <-- is this right?
.enter().append("rect")
.attr("class","bar")
There is no key assigned so d3 handles updates rather randomly. I can get what I want for only the first iteration if I do this:
// second level (key is assigned but first iteration only)
plot.selectAll(".g-category").selectAll(".bar")
.data(data[0].value, function (d) {return d.key; }) // <-- first category only
.enter().append("rect")
.attr("class","bar")
Perfection except I need to iterate over data
, not just data[0]
. My problem is as soon as I try, I get into a conflict of interest with d3.selectAll
and make a general mess of things.
The answer to this question may be simple, but I am missing it: How do I go about properly selecting and assigning keys to nested arrays in d3?
Upvotes: 3
Views: 376
Reputation: 102198
The data method in your second level has just one argument:
.data(function (d) {
return d.value;
});
For assigning the key function you have to pass a second argument. For instance:
.data(function(d) {
return d.value
}, function(d) {
return d.key
});
You can better see it in one line:
.data(function(d) { return d.value }, function(d) { return d.key });
//2nd arg after the comma ----------^
However, to facilitate human reading, I'd advise you to use different property names for level 1 and level 2. Right now everything is value
and key
, which can be hard to understand (not for the machine, though). For instance, in the above snippet, value
refers to the first level array, while key
refers to the second level array, not the first one... do you see the mess?
Here is a demo with your data:
var data = [{
"key": "cat1",
"value": [{
"key": "subcatA",
"value": 100
}, {
"key": "subcatB",
"value": 200
}]
},
{
"key": "cat2",
"value": [{
"key": "subcatA",
"value": 150
}, {
"key": "subcatB",
"value": 250
}]
}
];
var body = d3.select("body");
var outer = body.selectAll(null)
.data(data, function(d) {
return d.key
})
.enter()
.append("div")
.attr("class", "outer");
var inner = outer.selectAll(null)
.data(function(d) {
return d.value
}, function(d) {
console.log("the key is: " + d.key)
return d.key
})
.enter()
.append("div")
.html(function(d) {
return d.key + " - " + d.value
});
<script src="https://d3js.org/d3.v4.min.js"></script>
Upvotes: 2