我在functional programming有一个背景,原则上理解递归,但我似乎无法将这些知识转化为D3.js环境。
我在下面有一个hello world脚本,尝试简单地打印嵌套数据结构的内容。根据其他线程的建议,我可以使用.filter
仅返回节点,但如何继续此示例以递归方式打印嵌套项?
<!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>
答案 0 :(得分:5)
你需要一个root功能,然后是一个填充它的递归函数。
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);
哪个应该产生
答案 1 :(得分:2)
这样做的简单方法是避免递归!典型的D3.js方法是递归数据并确定布局所需的信息(例如,子项的总大小,嵌套的总深度,每个节点的深度),然后展平结构并使用计算值进行布局
在this tree example中可以找到一个很好的例子,其中计算和展平由内置函数处理:
var tree = d3.layout.tree()...
那就是说,如果你真的想尝试围绕那种直接在布局中进行递归所需的选择体操。关键是你必须做出选择,然后根据父母的数据设置他们的数据。
在下面的示例中,为方便起见,我对maxLevels进行了硬编码,但您可以在进入循环之前根据数据进行计算。
另请注意,我对布局非常懒惰,因为要正确执行此操作,首先需要对数据进行递归传递,以便在开始之前计算每个元素至少有多少个子元素。你可以玩小提琴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; });
}