D3 Force Directed Graph ajax update D3 Force Directed Graph ajax update ajax ajax

D3 Force Directed Graph ajax update


I have managed to find a solution to the problem using a mixture of all the advice above, below is the code I have used

    var width = $(document).width();    var height = $(document).height();    var outer = d3.select("#chart")        .append("svg:svg")            .attr("width", width)            .attr("height", height)            .attr("pointer-events", "all");    var vis = outer        .append('svg:g')            .call(d3.behavior.zoom().on("zoom", rescale))            .on("dblclick.zoom", null)        .append('svg:g')        vis.append('svg:rect')            .attr('width', width)            .attr('height', height)            .attr('fill', 'white');        var force = d3.layout.force()            .size([width, height])            .nodes([]) // initialize with a single node            .linkDistance(1)            .charge(-500)            .on("tick", tick);        nodes = force.nodes(),            links = force.links();        var node = vis.selectAll(".node"),            link = vis.selectAll(".link");       redraw();       setInterval(function(){           $.ajax({                url: "<?php echo $url;?>",                type: "post",                async: false,                datatype: "json",                success: function(json, textStatus, XMLHttpRequest)                 {                    var current_nodes = [];                    var delete_nodes = [];                    var json = $.parseJSON(json);                    $.each(json.nodes, function (i,data){                        result = $.grep(nodes, function(e){ return e.object_id == data.object_id; });                        if (!result.length)                        {                            nodes.push(data);                        }                        else                        {                            pos = nodes.map(function(e) { return e.object_id; }).indexOf(data.object_id);                            nodes[pos].colour = data.colour;                        }                        current_nodes.push(data.object_id);                                 });                    $.each(nodes,function(i,data){                        if(current_nodes.indexOf(data.object_id) == -1)                        {                            delete_nodes.push(data.index);                        }                           });                    $.each(delete_nodes,function(i,data){                        nodes.splice(data,1);                     });                    var nodeMap = {};                    nodes.forEach(function(x) { nodeMap[x.object_id] = x; });                    links = json.links.map(function(x) {                        return {                            source: nodeMap[x.source],                            target: nodeMap[x.target],                            colour: x.colour,                        };                    });                    redraw();                }            });       },2000);       function redraw()       {           node = node.data(nodes,function(d){ return d.object_id;});           node.enter().insert("circle")                .attr("r", 5)           node.attr("fill", function(d){return d.colour})           node.exit().remove();           link = link.data(links);           link.enter().append("line")               .attr("stroke-width",1)           link.attr('stroke',function(d){return d.colour});           link.exit().remove();           force.start();       }       function tick() {          link.attr("x1", function(d) { return Math.round(d.source.x); })              .attr("y1", function(d) { return Math.round(d.source.y); })              .attr("x2", function(d) { return Math.round(d.target.x); })              .attr("y2", function(d) { return Math.round(d.target.y); });          node.attr("cx", function(d) { return Math.round(d.x); })              .attr("cy", function(d) { return Math.round(d.y); });        }       function rescale() {            trans=d3.event.translate;            scale=d3.event.scale;            vis.attr("transform",                "translate(" + trans + ")"                + " scale(" + scale + ")");         }


Check out this answer. You need a unique identifier for your nodes, which it appears you have.

Updating links on a force directed graph from dynamic json data


I recently tried to do the same thing, here is the solution I came up with. What I do is load a first batch of data with links.php and then update them with newlinks.php, both return a JSON with a list of objects with attributes sender and receiver. In this example newlinks returns a new sender each time and I set the receiver to be a randomly selected old node.

$.post("links.php", function(data) {// Functions as an "initializer", loads the first data// Then newlinks.php will add more data to this first batch (see below)var w = 1400,    h = 1400;var svg = d3.select("#networkviz")            .append("svg")            .attr("width", w)            .attr("height", h);var links = [];var nodes = [];var force = d3.layout.force()                     .nodes(nodes)                     .links(links)                     .size([w, h])                     .linkDistance(50)                     .charge(-50)                     .on("tick", tick);svg.append("g").attr("class", "links");svg.append("g").attr("class", "nodes");var linkSVG = svg.select(".links").selectAll(".link"),    nodeSVG = svg.select(".nodes").selectAll(".node");handleData(data);update();// This is the server callvar interval = 5; // set the frequency of server calls (in seconds)setInterval(function() {    var currentDate = new Date();    var beforeDate = new Date(currentDate.setSeconds(currentDate.getSeconds()-interval));    $.post("newlinks.php", {begin: beforeDate, end: new Date()}, function(newlinks) {        // newlinks.php returns a JSON file with my new transactions (the one that happened between now and 5 seconds ago)        if (newlinks.length != 0) { // If nothing happened, then I don't need to do anything, the graph will stay as it was            // here I decide to add any new node and never remove any of the old ones            // so eventually my graph will grow extra large, but that's up to you to decide what you want to do with your nodes            newlinks = JSON.parse(newlinks);            // Adds a node to a randomly selected node (completely useless, but a good example)            var r = getRandomInt(0, nodes.length-1);            newlinks[0].receiver = nodes[r].id;            handleData(newlinks);            update();        }    });}, interval*1000);function update() {    // enter, update and exit    force.start();    linkSVG = linkSVG.data(force.links(), function(d) { return d.source.id+"-"+d.target.id; });    linkSVG.enter().append("line").attr("class", "link").attr("stroke", "#ccc").attr("stroke-width", 2);    linkSVG.exit().remove();    var r = d3.scale.sqrt().domain(d3.extent(force.nodes(), function(d) {return d.weight; })).range([5, 20]);    var c = d3.scale.sqrt().domain(d3.extent(force.nodes(), function(d) {return d.weight; })).range([0, 270]);    nodeSVG = nodeSVG.data(force.nodes(), function(d) { return d.id; });    nodeSVG.enter()           .append("circle")           .attr("class", "node")    // Color of the nodes depends on their weight    nodeSVG.attr("r", function(d) { return r(d.weight); })           .attr("fill", function(d) {               return "hsl("+c(d.weight)+", 83%, 60%)";           });    nodeSVG.exit().remove();    }function handleData(data) {    // This is where you create nodes and links from the data you receive    // In my implementation I have a list of transactions with a sender and a receiver that I use as id    // You'll have to customize that part depending on your data    for (var i = 0, c = data.length; i<c; i++) {        var sender = {id: data[i].sender};        var receiver = {id: data[i].receiver};        sender = addNode(sender);        receiver = addNode(receiver);        addLink({source: sender, target: receiver});    }}// Checks whether node already exists in nodes or notfunction addNode(node) {    var i = nodes.map(function(d) { return d.id; }).indexOf(node.id);    if (i == -1) {        nodes.push(node);        return node;    } else {        return nodes[i];    }}// Checks whether link already exists in links or notfunction addLink(link) {    if (links.map(function(d) { return d.source.id+"-"+d.target.id; }).indexOf(link.source.id+"-"+link.target.id) == -1        && links.map(function(d) { return d.target.id+"-"+d.source.id; }).indexOf(link.source.id+"-"+link.target.id) == -1)        links.push(link);}function tick() {    linkSVG.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;});    nodeSVG.attr("cx", function(d) {return d.x})            .attr("cy", function(d) {return d.y});}function getRandomInt(min, max) {    return Math.floor(Math.random() * (max - min + 1)) + min;}}, "json");

This is a very specific implementation so you should fill the holes where necessary depending on your server output. But I believe the D3 backbone is correct and what you are looking for :)Here is a JSFiddle to toy with it : http://jsfiddle.net/bTyh5/2/

This code was really useful and inspired some of the parts introduced here.