I have a background in functional programming and understand recursion in principle, but I can't seem to translate this knowledge into the D3.js environment.
I have a hello world script below which attempts to simply print the contents of a nested data structure. Following advice on other threads, I can use .filter
to return just the nodes, but how do I continue this example to recursively print the nested items?
<!DOCYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="d3.v3.js"></script>
<script>
function draw(data)
{
"use strict";
d3.select("body")
.selectAll("p")
.data(data)
.enter()
.append("p")
.text(function(d) {
if (d instanceof Array) {
return "WHAT DO I PUT HERE?";
}
else {
return d;
};
});
}
</script>
</head>
<body>
Hello world
<script>
draw([1, [2, [1, 2, 3, 4] ], 3, 4, 5]);
</script>
</body>
</html>
You need a root feature and then a recursive function that fills it.
function makeNestedListItems (parentLists) {
var item = parentLists.append('li')
.text(function (d) { return d.txt; });
var children = parentLists.selectAll('ul')
.data(function (d) {
return d.children
})
.enter().append('ul');
if (!children.empty()) {
makeNestedListItems(children);
}
}
var data = {
txt: 'root',
children: [{
txt: "a",
children: [{
txt: "aa",
children: [{
txt: "aaa",
children: []
}, {
txt: "aab",
children: []
}
]
}, {
txt: "ab",
children: []
}
]
}, {
txt: "b",
children: [{
txt: "ba",
children: []
}, {
txt: "bb",
children: []
}, {
txt: "bc",
children: []
}
]
}, {
txt: "c",
children: []
}
]
};
var rootList = d3.select('body').selectAll('ul').data([data])
.enter().append('ul');
makeNestedListItems(rootList);
Which should produce
The easy way to do this is to avoid recursion! The typical D3.js approach is to recurse your data and determine the information you need for layout (for example, total size of children, total depth of nesting, depth of each node) and then flatten the structure and use the computed values for layout.
An excellent example of this can be found in this tree example where the calculation and flattening are taken care of with the built-in function:
var tree = d3.layout.tree()...
That said, if you would really like to try to wrap your head around the kind of selection gymnastics required to do the recursion directly in the layout you can. The key is that you have to make selections and then set their data based on the parent's data.
In the example below I hardcoded maxLevels for convenience, but you could calculate it from your data before entering the loop.
Also note that I was exceedingly lazy about layout, because to do it properly you need a recursive pass on your data first to compute at least how many children each element has before you start. You can play with the fiddle here.
var data = { children: [{
txt: "a", children: [{
txt: "aa", children: [{
txt: "aaa"}, {
txt: "aab"}]}, {
txt: "ab"}]}, {
txt: "b", children: [{
txt: "ba"}, {
txt: "bb"}, {
txt: "bc"}]}, {
txt: "c"}]};
var svg = d3.selectAll("svg");
svg.attr({ width: 500, height: 500});
var recurse = svg.selectAll("g.level0").data([data]).enter()
.append("g").classed("level0", true);
var maxLevels = 4;
for (var level = 0; level < maxLevels; level++) {
var nextLevel = level + 1;
var next = svg.selectAll("g.level" + level).filter(function (d) {
return d.children !== undefined;
});
next.selectAll("g.level" + nextLevel)
.data(function (d) { return d.children; })
.enter().append("g")
.attr("class", function (d) {
return "level" + nextLevel + " " + d.txt;
})
.attr("transform", function (d, i) {
return "translate(" + (nextLevel * 25) + "," + (i * 10 * (5 - level) + 15) + ")";
});
next.selectAll("text.level" + nextLevel)
.data(function (d) { return d.children; })
.enter().append("text")
.classed("level" + level, true)
.attr("x", function (d, i, j) { return nextLevel * 25; })
.attr("y", function (d, i, j) {
return j * (10 * (10 - level)) + (i+1) * 15;
})
.attr("fill", "black")
.text(function (d) { return d.txt; });
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With