1

我正在尝试使用 d3 和强制布局创建图形。我已经使用以下示例http://bl.ocks.org/mbostock/1062288开始:

在此处输入图像描述

我还需要图像和标签,所以我查看了这个示例http://bl.ocks.org/mbostock/950642以了解如何添加它们。

根据用户与节点的交互,我的图表也会变得更大,因此如果用户单击没有子节点的节点,ajax 请求将转到后端服务以请求更多节点。该图旨在用作关系发现应用程序。我创建了以下 jsfiddle http://jsfiddle.net/R2JGa/7/以了解我想要实现的目标。

它工作得相对较好,但我有一个烦人的问题:当向图中添加新节点时,旧节点不知何故放错了位置。例如,我从 3 个节点开始,根节点是“flare”。其他 2 个节点是“动画”和“分析”。在我的示例中,只要您单击当前没有子节点的节点,子节点将始终为“x”、“y”、“z”、“t”。展开几个节点后,您会看到“动画”或“分析”没有链接到根节点“flare”,而是链接到其他一些节点 (x,y,z,t)。或者有时如果您扩展 x 或 y 或 z 或 t,子节点有重复的 x 或 y 或 z 或 t。如果单击“flare”隐藏整个图形,然后重新打开“flare”

我似乎不明白为什么会这样。有人可以在这里阐明一下吗?我还是 d3 的新手,发现它真的很有趣,但是这些问题太烦人了……

这是代码:

var w = 960,
            h = 800,
            node,
            link,
            root;

    var force = d3.layout.force()
            .charge(-1000)
            .size([w, h]);

    var vis = d3.select("#chart").append("svg:svg")
            .attr("width", w)
            .attr("height", h);

    d3.json("data.json", function (json) {
        root = json;
        update();
    });

    function update() {
        var nodes = flatten(root);
        nodes.reverse();
        nodes = nodes.sort(function (a, b) {
            return a.index - b.index;
        });

        var links = d3.layout.tree().links(nodes);

        console.log(nodes);

        // Restart the force layout.
        force
                .nodes(nodes)
                .links(links)
                .linkDistance(55)
                .start();


        var link = vis.selectAll(".link")
                .data(links);

        link.enter().append("line")
                .attr("class", "link");

        link.exit().remove();

        var node = vis.selectAll("g.node")
                .data(nodes)


        var groups = node.enter().append("g")
                .attr("class", "node")
                .attr("id", function (d) {
                    return d.id
                })
                .on('click', click)
                .call(force.drag);


        groups.append("image")
                .attr("xlink:href", "https://github.com/favicon.ico")
                .attr("x", -8)
                .attr("y", -8)
                .attr("width", 16)
                .attr("height", 16);


        groups.append("text")
                .attr("dx", 12)
                .attr("dy", "0.35em")
                .style("font-size", "10px")
                .text(function (d) {
                    console.log(d);
                    return d.name
                });


        node.exit().remove();


        force.on("tick", function () {
            link.attr("x1", function (d) {
                return d.source.x;
            })
                    .attr("y1", function (d) {
                        return d.source.y;
                    })
                    .attr("x2", function (d) {
                        return d.target.x;
                    })
                    .attr("y2", function (d) {
                        return d.target.y;
                    });

            node.attr("transform", function (d) {
                return "translate(" + d.x + "," + d.y + ")";
            });
        });
    }


    // Color leaf nodes orange, and packages white or blue.
    function color(d) {
        return d._children ? "#3182bd" : d.children ? "#c6dbef" : "#fd8d3c";
    }

    // Toggle children on click.
    function click(d) {
        console.log(d);
        if (d.children) {
            d._children = d.children;
            d.children = null;
            update();
        } else if (d._children) {
            d.children = d._children;
            d._children = null;
            update();
        }
        else {
            d3.json("expand.json", function (json) {
                d.children = json.children;
                update();
            })
        }
    }

    // Returns a list of all nodes under the root.
    function flatten(root) {
        var nodes = [], i = 0;

        function recurse(node) {
            if (node.children) node.children.forEach(recurse);
            if (!node.id) node.id = ++i;
            nodes.push(node);
        }

        recurse(root);
        return nodes;
    }

这是我要求的 2 个 json 文件:

数据.json

{
    "name": "flare",
    "id" : "flare",
    "children": [
        {
            "name": "analytics",
            "id": "analytics"

        },
        {
            "name": "animate",
            "id": "animate"
        }
    ]
}

并展开.json

{"children": [
    {
        "name": "x",
        "id": "x",
        "size": 1983
    },
    {
        "name": "y",
        "id": "y",
        "size": 2047
    },
    {
        "name": "z",
        "id": "z",
        "size": 1375
    },
    {
        "name": "t",
        "id": "t",
        "size": 1375
    }
]}

PS:我必须对节点数组进行排序,否则图表会发生坏事,我不明白为什么。

4

3 回答 3

2

这里是工作解决方案的小提琴。我认为问题在于您声明 id 并根据数组索引对它们进行排序的方式。您应该让扁平化代码声明 id,然后根据给定的 id 对它们进行排序。同样在您的递归函数中,您可能希望先声明父级,然后再声明子级。

function recurse(node) {
        if(!node.id) node.id = ++i;
        nodes.push(node);
        if (node.children) node.children.forEach(recurse);
    }
于 2013-08-31T04:33:27.463 回答
0

以下应该可以解决问题:

var i = 0;

...

var link = vis.selectAll(".link")
    .data(links, function (d) {
                return d.id || (d.id = ++i);
            });

...

var node = vis.selectAll("g.node")
.data(nodes, function (d) {
                return d.id || (d.id = ++i);
            });

data() 的第二个参数是一个回调函数,当使用数据调用时,它返回将每个 DOM 节点绑定到其对应数据的键。如果不提供这样的功能,d3 就别无选择,只能使用索引将数据绑定到 DOM 节点。

于 2015-01-08T08:44:13.137 回答
0

从 Yogesh 的回答开始,我设法找到了解决方案。这是需要在 update() 函数中添加的代码。

            var currentNodes = force.nodes();
            var nodes = flatten(root);
            var actualNodes = [];

            var values = currentNodes.map(function(obj) { return obj.name});
            var newNodesValues = nodes.map(function(obj) { return obj.name });


            for(var i = 0; i < currentNodes.length; i++) {
                if(newNodesValues.indexOf(currentNodes[i].name) !== -1) {
                    actualNodes.push(currentNodes[i]);
                }
            }

            for(var i = 0; i < nodes.length; i++) {
                if(values.indexOf(nodes[i].name) == -1) {
                    actualNodes.push(nodes[i]);
                }
            }

            nodes = actualNodes;

            var links = d3.layout.tree().links(nodes);

            // Restart the force layout.
            force
                    .nodes(nodes)
                    .links(links)
                    .linkDistance(55)
                    .start();
于 2013-09-02T16:17:06.267 回答