d3js force layout with hide/unhide on node click misplaces nodes after expanding

隐身守侯 提交于 2019-12-10 11:45:42

问题


I am trying to create a graph using d3 and force layout. I have used the following example http://bl.ocks.org/mbostock/1062288 to get started:

I also need images and labels so I looked at this example http://bl.ocks.org/mbostock/950642 to get an idea how I could add them.

My graph will also get bigger depending on the user interactions with the nodes so if a user clicks on a node that doesn't have children, an ajax request will go to the backend service to request more nodes. The graph is meant to be used as a relationship discovery application. I created the following jsfiddle http://jsfiddle.net/R2JGa/7/ to get an idea of what i'm trying to achieve.

It works relatively well but I have one annoying problem: when adding new nodes to the graph the old nodes somehow get misplaced. For instance, I start with 3 nodes, root node is "flare". The other 2 nodes are "animate" and "analytics". In my example the children will always be "x","y","z","t" anytime you click on a node that currently doesn't have children. After expanding a few nodes you will see that "animate" or "analytics" are not linked to the root node "flare" but to some other nodes (x,y,z,t). Or sometimes if you expand x or y or z or t the children nodes have a duplicate x or y or z or t. If you click on "flare" to hide the entire graph and then reopen "flare" you will see that the nodes are correctly linked and named.

I can't seem to see why this is happening. Could somebody shed some light here? I am still new to d3 and find it really interesting but these problems are so annoying...

Here is the code:

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

And here are the 2 json files I'm requesting:

data.json

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

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

And expand.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: i had to sort the nodes array otherwise bad things happened to the graph, i cannot understand why.


回答1:


Here the fiddle to the working solution. I think the problem was with the way you are declaring your id's and sorting them based on array index. You should have let the id's being declared by the flattening code and then sort them based on id's given. Also in your recursive function you might want to declare the parent first and then then children.

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



回答2:


I have managed to find a solution for this starting from Yogesh's answer. Here is the code that needs to be added in the update() function.

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



回答3:


The following should do the trick:

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

That second argument to data() is a callback function that, when called with a datum, returns the key that binds each DOM node to it's corresponding datum. When you don't provide such a function, d3 is left with no option but to use the index to bind datum to DOM nodes.



来源:https://stackoverflow.com/questions/18537905/d3js-force-layout-with-hide-unhide-on-node-click-misplaces-nodes-after-expanding

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!