D3 Force Directed Graph ajax update

╄→尐↘猪︶ㄣ 提交于 2019-12-03 11:06:50

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 + ")"); 
        }
david4096

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 call
var 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 not
function 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 not
function 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.

You don't actually need to pass anything back to the server, as long as, server-side, you are able to tell what new nodes and links are being generated. Then, instead of reloading your whole d3 script, you load it once, and in the force.on("tick", function()), you make your 10 sec timeout AJAX call to go get from the server the new data you want to append, be it nodes or links.

For instance, imagine that you initially have this JSON in your server:

[
    {
        "nodes": [
            {
                "name": "MaaS",
                "object_id": 0
            },
            {
                "name": "Convergence",
                "object_id": "531",
                "colour": "#999900"
            }
        ]
    },
    {
        "links": [
            {
                "source": 0,
                "target": "531"
            }
        ]
    }
]

You go get it from your server with AJAX and parse it with json = $.parseJSON(json);.

Then, write your timeout so that instead of running the whole function you have in success, only runs after calculating the layout. Then, again, on success, parse the new JSON you got from the server and add the_new_ nodes and links to the already existing force.nodes and force.links respectively.

Please note that I didn't test this and I'm not sure how it will work and/or perform, but I think the general approach is feasible.

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