简体   繁体   English

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

[英]How to dynamically update a d3.js force layout graph?

I have a force layout graph that works as expected. 我有一个按预期方式工作的力布局图。 It reads a graph from a JSON on disk and displays it. 它从磁盘上的JSON读取图形并显示它。 I now have the server pushing data to the client page using socket io. 现在,我有服务器使用套接字io将数据推送到客户端页面。 This data is a JSON containing the node and link data. 此数据是包含节点和链接数据的JSON。

How do I have the force layout refresh and update the layout when it receives this new JSON from the server? 当它从服务器接收到这个新的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; });
 });
});

EDIT 编辑

I have updated the code as Guillermo Garcia suggested as follows: 我已经按照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; });
 });
}

However, the graph only works for the first networkData JSON sent from the server. 但是,该图仅适用于从服务器发送的第一个networkData JSON。 I also have to manually refresh the page in order for this data to display. 我还必须手动刷新页面才能显示此数据。

Why is the data not working for the > 2nd data files? 为什么第二个数据文件的数据不起作用? How can I have the page dynamically update? 如何使页面动态更新? (ie not have to manually refresh) (即不必手动刷新)

Have you tried creating a render function and call it in the socket.io event? 您是否尝试过创建渲染函数并在socket.io事件中调用它?

The render function should have all the the code from var link = svg.selectAll(".link") to force.on("tick", function() { 呈现函数应具有从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; });
 });

Good luck! 祝好运!

Maybe this might be a bit harder to implement for your case, but I'm sure that my code can give you at least some ideas. 也许对于您的案例而言,实现起来可能会有些困难,但是我敢肯定,我的代码至少可以给您一些想法。 So these are the functions I basically use to clean up the presentation and add new nodes from the database dynamically. 因此,这些是我基本上用于清理演示文稿并从数据库动态添加新节点的功能。 You can see the result here . 您可以在此处查看结果。 These lines also check if there are duplicates and filtering them out. 这些行还检查是否存在重复项并将其过滤掉。 If you need some more definition or variables, let me know. 如果您需要更多定义或变量,请告诉我。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM