6

我有函数式编程的背景,原则上理解递归,但我似乎无法将这些知识转化为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>
4

2 回答 2

7

您需要一个根特征,然后是一个填充它的递归函数。

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

哪个应该产生

    • 一个
        • 啊啊啊
        • aab
      • 抗体
    • b
      • bb
      • 公元前
    • C
于 2014-07-30T04:36:27.143 回答
2

最简单的方法是避免递归!典型的 D3.js 方法是递归数据并确定布局所需的信息(例如,子项的总大小、嵌套的总深度、每个节点的深度),然后展平结构并使用计算的值进行布局.

一个很好的例子可以在这个树示例中找到,其中计算和展平是使用内置函数处理的:

var tree = d3.layout.tree()...

也就是说,如果您真的想尝试直接在布局中进行递归所需的选择体操,您可以。关键是你必须做出选择,然后根据父母的数据设置他们的数据。

在下面的示例中,为了方便起见,我对 maxLevels 进行了硬编码,但您可以在进入循环之前根据数据计算它。

另请注意,我对布局非常懒惰,因为要正确执行此操作,您首先需要对数据进行递归传递,以在开始之前至少计算每个元素有多少个子元素。你可以在这里玩小提琴。

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; });
}
于 2013-01-18T00:15:15.537 回答