简体   繁体   中英

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

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.

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

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM