如何动态更新d3.js强制布局图?

时间:2015-06-30 11:46:09

标签: javascript node.js d3.js force-layout

我有一个按预期工作的力布局图。它从磁盘上的JSON读取图形并显示它。我现在让服务器使用socket io将数据推送到客户端页面。此数据是包含节点和链接数据的JSON。

如何从服务器接收新的JSON时,强制布局刷新并更新布局?

var width = 1900, 
height = 1100;

var color = d3.scale.category20();

var force = d3.layout.force() 
    .charge(-120)         
    .linkDistance(30)
    .size([width, height]);

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);


var networkData = {};
var socket = io.connect('http://localhost:3000');

socket.on("networkData", function (data) {
    networkData = data; //This is the data I want to use
});


d3.json("data.json", function(error, graph) { //"data.json" is the data 
    if (error) throw error;                   // currently being used
                                              // want to use "networkData"
force
  .nodes(graph.nodes)
  .links(graph.links)
  .start();

 var link = svg.selectAll(".link")
  .data(graph.links)        
  .enter().append("line")
  .attr("class", "link")
  .style("stroke-width", function(d) { return Math.sqrt(d.value); });

 var node = svg.selectAll(".node") 
  .data(graph.nodes)
  .enter().append("circle")
  .attr("class", "node")
  .attr("r", 5)
  .style("fill", "orange")
  .call(force.drag);

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

 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("cx", function(d) { return d.x; })
    .attr("cy", function(d) { return d.y; });
 });
});

修改

我更新了代码,Guillermo Garcia建议如下:

socket.on("networkData", function (data) {
    force.stop()
    render(data);
    force
        .nodes(data.nodes)
        .links(data.links)
        .start();             
});

function render(data) {

    var link = svg.selectAll(".link") 
      .data(data.links)     
      .enter().append("line")
      .attr("class", "link")
      .style("stroke-width", function(d) { return Math.sqrt(d.value); });

   var node = svg.selectAll(".node") 
      .data(data.nodes)
      .enter().append("circle")
      .attr("class", "node")
      .attr("r", 5)
      .style("fill", "orange")
      .call(force.drag);

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

 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("cx", function(d) { return d.x; })
     .attr("cy", function(d) { return d.y; });
 });
}

但是,该图仅适用于从服务器发送的第一个networkData JSON。我还必须手动刷新页面才能显示这些数据。

为什么数据不适用于>第二个数据文件?如何让页面动态更新? (即不必手动刷新)

2 个答案:

答案 0 :(得分:1)

您是否尝试过创建渲染函数并在socket.io事件中调用它?

渲染函数应包含从var link = svg.selectAll(".link")force.on("tick", function() {

的所有代码
 var link = svg.selectAll(".link")
 .data(graph.links)    

 ...
 ...

 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("cx", function(d) { return d.x; })
     .attr("cy", function(d) { return d.y; });
 });
祝你好运!

答案 1 :(得分:0)

对你的情况来说,这可能有点难以实现,但我确信我的代码至少可以给你一些想法。所以这些是我基本上用来清理演示文稿和动态添加新节点的功能。您可以看到结果here。这些行还会检查是否存在重复项并将其过滤掉。如果您需要更多定义或变量,请告诉我。

        cleanPresentation: function () {
            svg.remove();
            nodeCircles = {};
            alreadyThere = false;
        },
        getAlreadyThere: function () {
            return alreadyThere;
        },
        createGraph: function (newJSON) {
            if (alreadyThere) {
                svg.remove();
                nodeCircles = {};
            }
            this.updateForceUsingNewNodes(this.generateObjects(newJSON));
            currentJSON = newJSON;
            if (alreadyThere == false) {
                this.setbasiczoom();
            }
            alreadyThere = true;
        },
        updateGraph: function (newJSON) {
            svg.remove();
            this.findDuplicatesAndSetEmpty(newJSON);
            this.deleteEmptyObjectsInJSON(newJSON);
            currentJSON = currentJSON.concat(newJSON);
            this.updateForceUsingNewNodes(this.generateObjects(currentJSON));
        },
        findDuplicatesAndSetEmpty: function (newJSON) {
            for (var i = 0; i < currentJSON.length; i++) {
                for (var o = 0; o < newJSON.length; o++) {
                    if ((currentJSON[i].source.ID == newJSON[o].source) && (currentJSON[i].target.ID == newJSON[o].target)
                        || (currentJSON[i].source.ID == newJSON[o].target) && (currentJSON[i].target.ID == newJSON[o].source)) {
                        newJSON[o] = {};
                    }
                }
            }
        },
        deleteEmptyObjectsInJSON: function (json) {
            for (var i = 0; i < json.length; i++) {
                var y = json[i].source;
                if (y === "null" || y === null || y === "" || typeof y === "undefined") {
                    json.splice(i, 1);
                    i--;
                }
            }
        },
        updateGraphByRemoveElement: function (clickedNode, index) {
            svg.remove();
            var json4Splicing = currentJSON;
            for (var i = 0; i < json4Splicing.length; i++) {
                if (json4Splicing[i].source.ID == clickedNode.ID) {
                    json4Splicing[i] = {};
                } else if (json4Splicing[i].target.ID == clickedNode.ID) {
                    json4Splicing[i] = {};
                }
            }
            familytree.deleteEmptyObjectsInJSON(json4Splicing);
            familytree.deleteNode(force.nodes(), clickedNode);
            currentJSON = json4Splicing;
            familytree.updateForceRemoveElement(familytree.generateObjects(currentJSON));
        },
        deleteNode: function (allNodes, clickedNode) {
            allNodes.forEach(function (node) {
                if (node == clickedNode) {
                    force.links().forEach(function (link) {
                        if (node.ID == link.source.ID) {
                            link.target.linkCount--;
                        }
                        if (node.ID == link.target.ID) {
                            link.source.linkCount--;
                        }
                    });
                    node.linkCount = 0;
                }
            });
        },
        generateObjects: function (json) {
            json.forEach(function (link) {
                if (typeof(link.source) == "string") {
                    link.source = nodeCircles[link.source] || (nodeCircles[link.source] = {name: link.sourceName, significance: link.sourceSign, uniquename: link.sourceUName, ID: link.source, class: link.sourceClass, relation: link.relation, race: link.sourceRace, linkCount: 0});
                    link.source.linkCount++;
                }
                if (typeof(link.target) == "string") {
                    link.target = nodeCircles[link.target] || (nodeCircles[link.target] = {name: link.targetName, significance: link.targetSign, uniquename: link.targetUName, ID: link.target, class: link.targetClass, relation: link.relation, race: link.targetRace, linkCount: 0});
                    link.target.linkCount++;
                }
            });
            return json;
        },
        updateForceRemoveElement: function (links) {
            force.nodes(d3.values(nodeCircles).filter(function (d) {
                return d.linkCount;
            }));
            force.links(d3.values(links));
            familytree.initializeGraph();
        },
        updateForceUsingNewNodes: function (links) {
            force.nodes(d3.values(nodeCircles).filter(function (d) {
                return d.linkCount;
            }));
            force.links(d3.values(links));
            this.initializeGraph();
        }