Updating links on a force directed graph from dynamic json data

天大地大妈咪最大 提交于 2019-11-26 15:09:18

问题


I am new to D3 and working on a force directed graph where the json data is dynamic. I am able to change the force graph upon receiving new data but that happens with a springing effect. The code that creates my force graph is :

<div class="graph"></div>
<script>
var w = 660,
    h = 700,
    r = 10;
var vis = d3.select(".graph")
    .append("svg:svg")
    .attr("width", w)
    .attr("height", h)
    .attr("pointer-events", "all")
    .append('svg:g')
    .call(d3.behavior.zoom().on("zoom", redraw))
    .append('svg:g');
vis.append('svg:rect')
    .attr('width', w)
    .attr('height', h)
    .attr('fill', 'rgba(1,1,1,0)');
function redraw() {
    console.log("here", d3.event.translate, d3.event.scale);
    vis.attr("transform", "translate(" + d3.event.translate + ")" +
                          " scale(" + d3.event.scale + ")");
};  

var force = d3.layout.force()
    .gravity(.05)
    .charge(-200)
    .linkDistance( 260 )
    .size([w, h]);

var svg = d3.select(".text")
    .append("svg")
    .attr("width", w)
    .attr("height", h);

d3.json(graph, function(json) {

    var nodeList = json.nodes;
    var link = vis.selectAll("line")
        .data(json.links)
       .enter()
        .append("line")
        .attr("stroke-opacity", function(d) {
            if(d.label == 'is a') {
                return '0.8';
            } else {
                return '0.2';
            };
        })
        .attr("stroke-width", function(d) {
            if(d.value !== null) {
                return d.value;
            } else {
                return 2;
            };
        })
        .style("stroke", function(d) {
            if(d.color !== null) {
                return d.color;
            };
        })
        .on("mouseover", function() {
            d3.select(this)
                .style("stroke", "#999999")
                .attr("stroke-opacity", "1.0");
        })
        .on("mouseout", function() {
            d3.select(this)
                .style("stroke", function(d) {
                    if(d.color !== null) {
                        return d.color;
                    };
                })
                .attr("stroke-opacity", function(d) {
                    if(d.label == 'is a') {
                        return '0.8';
                    } else {
                        return '0.2';
                    };
                })
            });

    link.append("title")
        .text(function(d) { return d.label } );         

    var node = vis.selectAll("g.node")
        .data(json.nodes)
       .enter()
        .append("svg:g")
        .attr("class","node")
        .call(force.drag);

    node.append("svg:circle")
        .attr("r", function(d) {
            if (d.size > 0) {
                return 10+(d.size*2);
            } else {
                return 10;
            }
        })
        .attr("id", function(d) { return "Node;"+d.id; } )
        .style("fill", function(d) {
            if(d.style == 'filled') {
               return d.color;
            };
        })
        .style("stroke", function(d) {
            if(d.style !== 'filled') {
                return d.color;
            };
        })
        .style("stroke-width", "2")
        .on("mouseover", function() {
            d3.select(this).style("fill", "#999");
            fade(.1);
        })
        .on("mouseout", function(d) {
            if (d.style == 'filled') {
                d3.select(this).style("fill",d.color);fade(1);
            } else {
                d3.select(this).style("stroke",d.color);
                d3.select(this).style("fill","black");
            }
            fade(1);
        });

    node.append("title")
        .text(function(d) { return d.Location; } );         

    force.nodes(json.nodes)
        .links(json.links)
        .on("tick", tick)
        .alpha(1)
        .start();

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

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

});
</script>

I am able to create a new graph when a new json string is received by recalling the whole function again. This creates a new graph in place of the old. I am unable to update the old graph with the new set of values as the values are received; the nodes in my graph do not change, just the relation among them changes.

I did stumble upon an example (http://bl.ocks.org/1095795) where a new node is deleted and recreated, but the implementation is a bit different.

Any pointers or help will be really appreciated.


回答1:


Well I could find the solution browsing through, posting it here for anyone needing help on this topic. The idea is to create an object of the graph and playing around with the nodes and links arrays. The JS code goes as:

var graph;
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++;
    }
    nodes.splice(findNodeIndex(id),1);
    update();
};

this.removeLink = function (source,target){
    for(var i=0;i<links.length;i++)
    {
        if(links[i].source.id == source && links[i].target.id == target)
        {
            links.splice(i,1);
            break;
        }
    }
    update();
};

this.removeallLinks = function(){
    links.splice(0,links.length);
    update();
};

this.removeAllNodes = function(){
    nodes.splice(0,links.length);
    update();
};

this.addLink = function (source, target, value) {
    links.push({"source":findNode(source),"target":findNode(target),"value":value});
    update();
};

var findNode = function(id) {
    for (var i in nodes) {
        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 = 500,
    h = 500;
var vis = d3.select("#svgdiv")
    .append("svg:svg")
    .attr("width", w)
    .attr("height", h)
    .attr("id","svg")
    .attr("pointer-events", "all")
    .attr("viewBox","0 0 "+w+" "+h)
    .attr("perserveAspectRatio","xMinYMid")
    .append('svg:g');

var force = d3.layout.force();

var nodes = force.nodes(),
    links = force.links();

var update = function () {
      var link = vis.selectAll("line")
        .data(links, function(d) {
            return d.source.id + "-" + d.target.id; 
            });

    link.enter().append("line")
        .attr("id",function(d){return d.source.id + "-" + d.target.id;})
        .attr("class","link");
    link.append("title")
    .text(function(d){
        return d.value;
    });
    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("svg:circle")
    .attr("r", 16)
    .attr("id",function(d) { return "Node;"+d.id;})
    .attr("class","nodeStrokeClass");

    nodeEnter.append("svg:text")
    .attr("class","textClass")
    .text( function(d){return d.id;}) ;

    node.exit().remove();
    force.on("tick", function() {

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

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

    // Restart the force layout.
    force
    .gravity(.05)
    .distance(50)
    .linkDistance( 50 )
    .size([w, h])
    .start();
};


// Make it all go
update();
}

function drawGraph()
{
graph = new myGraph("#svgdiv");
graph.addNode('A');
graph.addNode('B');
graph.addNode('C');
graph.addLink('A','B','10');
graph.addLink('A','C','8');
graph.addLink('B','C','15');
}



回答2:


I took Rahuls great example, made some changes, and posted a bl.ock complete with animation over time if anyone is interested in a fully functioning example. Adding/removing links/nodes really should be easier than this, but still pretty cool.

http://bl.ocks.org/ericcoopey/6c602d7cb14b25c179a4




回答3:


In addition to calling drawGraph() in the ready function, you can also embed the posted code inside an inline <script></script>block.

This is how most of the tutorials on the d3 site handle it.



来源:https://stackoverflow.com/questions/11400241/updating-links-on-a-force-directed-graph-from-dynamic-json-data

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