如何编写递归d3.js代码来处理嵌套数据结构?

时间:2013-01-17 13:48:52

标签: javascript recursion d3.js nested

我在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>

2 个答案:

答案 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);

哪个应该产生

    • 一个
      • AA
        • AAA
        • AAB
      • AB
    • b
      • BA
      • BB
      • BC
    • C

答案 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; });
}