Adding new nodes to Force-directed layout

前端 未结 1 1674
一向
一向 2020-11-28 18:16

First question on Stack Overflow, so bear with me! I am new to d3.js, but have been consistently amazed by what others are able to accomplish with it... and almost as amazed

相关标签:
1条回答
  • 2020-11-28 19:00

    After many long hours of being unable to get this working, I finally stumbled across a demo that I don't think is linked any of the documentation: http://bl.ocks.org/1095795:

    enter image description here

    This demo contained the keys which finally helped me crack the problem.

    Adding multiple objects on an enter() can be done by assigning the enter() to a variable, and then appending to that. This makes sense. The second critical part is that the node and link arrays must be based on the force() -- otherwise the graph and model will go out of synch as nodes are deleted and added.

    This is because if a new array is constructed instead, it will lack the following attributes:

    • index - the zero-based index of the node within the nodes array.
    • x - the x-coordinate of the current node position.
    • y - the y-coordinate of the current node position.
    • px - the x-coordinate of the previous node position.
    • py - the y-coordinate of the previous node position.
    • fixed - a boolean indicating whether node position is locked.
    • weight - the node weight; the number of associated links.

    These attributes are not strictly needed for the call to force.nodes(), but if these are not present, then they would be randomly initialised by force.start() on the first call.

    If anybody is curious, the working code looks like this:

    <script type="text/javascript">
    
    function myGraph(el) {
    
        // Add and remove elements on the graph object
        this.addNode = function (id) {
            nodes.push({"id":id});
            update();
        }
    
        this.removeNode = function (id) {
            var i = 0;
            var n = findNode(id);
            while (i < links.length) {
                if ((links[i]['source'] === n)||(links[i]['target'] == n)) links.splice(i,1);
                else i++;
            }
            var index = findNodeIndex(id);
            if(index !== undefined) {
                nodes.splice(index, 1);
                update();
            }
        }
    
        this.addLink = function (sourceId, targetId) {
            var sourceNode = findNode(sourceId);
            var targetNode = findNode(targetId);
    
            if((sourceNode !== undefined) && (targetNode !== undefined)) {
                links.push({"source": sourceNode, "target": targetNode});
                update();
            }
        }
    
        var findNode = function (id) {
            for (var i=0; i < nodes.length; i++) {
                if (nodes[i].id === id)
                    return nodes[i]
            };
        }
    
        var findNodeIndex = function (id) {
            for (var i=0; i < nodes.length; i++) {
                if (nodes[i].id === id)
                    return i
            };
        }
    
        // set up the D3 visualisation in the specified element
        var w = $(el).innerWidth(),
            h = $(el).innerHeight();
    
        var vis = this.vis = d3.select(el).append("svg:svg")
            .attr("width", w)
            .attr("height", h);
    
        var force = d3.layout.force()
            .gravity(.05)
            .distance(100)
            .charge(-100)
            .size([w, h]);
    
        var nodes = force.nodes(),
            links = force.links();
    
        var update = function () {
    
            var link = vis.selectAll("line.link")
                .data(links, function(d) { return d.source.id + "-" + d.target.id; });
    
            link.enter().insert("line")
                .attr("class", "link");
    
            link.exit().remove();
    
            var node = vis.selectAll("g.node")
                .data(nodes, function(d) { return d.id;});
    
            var nodeEnter = node.enter().append("g")
                .attr("class", "node")
                .call(force.drag);
    
            nodeEnter.append("image")
                .attr("class", "circle")
                .attr("xlink:href", "https://d3nwyuy0nl342s.cloudfront.net/images/icons/public.png")
                .attr("x", "-8px")
                .attr("y", "-8px")
                .attr("width", "16px")
                .attr("height", "16px");
    
            nodeEnter.append("text")
                .attr("class", "nodetext")
                .attr("dx", 12)
                .attr("dy", ".35em")
                .text(function(d) {return d.id});
    
            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 + ")"; });
            });
    
            // Restart the force layout.
            force.start();
        }
    
        // Make it all go
        update();
    }
    
    graph = new myGraph("#graph");
    
    // You can do this from the console as much as you like...
    graph.addNode("Cause");
    graph.addNode("Effect");
    graph.addLink("Cause", "Effect");
    graph.addNode("A");
    graph.addNode("B");
    graph.addLink("A", "B");
    
    </script>
    
    0 讨论(0)
提交回复
热议问题