添加新的节点到强制指导布局

关于Stack Overflow的第一个问题,请耐心等待! 我是d3.js的新手,但是一直都很惊讶于别人能够用它来完成什么……而且我几乎同样惊讶于我自己能够取得的进展。 很显然,我不是在寻找什么,所以我希望这里的善良的灵魂能让我看到光明。

我的意图是做一个可重用的JavaScript函数,它只是做以下几点:

  • 在指定的DOM元素中创build一个空白的强制方向图
  • 允许您添加和删除带标记的图像承载节点到该图表,指定它们之间的连接

我以http://bl.ocks.org/950642为起点,因为这实际上是我想要创build的布局types:

在这里输入图像描述

这是我的代码看起来像:

<!DOCTYPE html> <html> <head> <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript" src="underscore-min.js"></script> <script type="text/javascript" src="d3.v2.min.js"></script> <style type="text/css"> .link { stroke: #ccc; } .nodetext { pointer-events: none; font: 10px sans-serif; } body { width:100%; height:100%; margin:none; padding:none; } #graph { width:500px;height:500px; border:3px solid black;border-radius:12px; margin:auto; } </style> </head> <body> <div id="graph"></div> </body> <script type="text/javascript"> function myGraph(el) { // Initialise the graph object var graph = this.graph = { "nodes":[{"name":"Cause"},{"name":"Effect"}], "links":[{"source":0,"target":1}] }; // Add and remove elements on the graph object this.addNode = function (name) { graph["nodes"].push({"name":name}); update(); } this.removeNode = function (name) { graph["nodes"] = _.filter(graph["nodes"], function(node) {return (node["name"] != name)}); graph["links"] = _.filter(graph["links"], function(link) {return ((link["source"]["name"] != name)&&(link["target"]["name"] != name))}); update(); } var findNode = function (name) { for (var i in graph["nodes"]) if (graph["nodes"][i]["name"] === name) return graph["nodes"][i]; } this.addLink = function (source, target) { graph["links"].push({"source":findNode(source),"target":findNode(target)}); update(); } // set up the D3 visualisation in the specified element var w = $(el).innerWidth(), h = $(el).innerHeight(); var vis = d3.select(el).append("svg:svg") .attr("width", w) .attr("height", h); var force = d3.layout.force() .nodes(graph.nodes) .links(graph.links) .gravity(.05) .distance(100) .charge(-100) .size([w, h]); var update = function () { var link = vis.selectAll("line.link") .data(graph.links); link.enter().insert("line") .attr("class", "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; }); link.exit().remove(); var node = vis.selectAll("g.node") .data(graph.nodes); node.enter().append("g") .attr("class", "node") .call(force.drag); node.append("image") .attr("class", "circle") .attr("xlink:href", "https://d3nwyuy0nl342s.cloudfront.nethttp://img.dovov.comicons/public.png") .attr("x", "-8px") .attr("y", "-8px") .attr("width", "16px") .attr("height", "16px"); node.append("text") .attr("class", "nodetext") .attr("dx", 12) .attr("dy", ".35em") .text(function(d) { return d.name }); node.exit().remove(); 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("transform", function(d) { return "translate(" + dx + "," + dy + ")"; }); }); // Restart the force layout. force .nodes(graph.nodes) .links(graph.links) .start(); } // Make it all go update(); } graph = new myGraph("#graph"); // These are the sort of commands I want to be able to give the object. graph.addNode("A"); graph.addNode("B"); graph.addLink("A", "B"); </script> </html> 

每次添加新节点时,都会重新标记所有现有的节点; 这些堆在彼此的顶部,事情开始变得丑陋。 我明白这是为什么:因为当我添加一个新节点时调用update()函数函数,它会为整个数据集执行一个node.append(...) 。 我不知道如何做到这一点, 只有我添加的节点 …我只能显然使用node.enter()创build一个新的元素,所以这不适用于额外的元素我需要绑定到节点。 我该如何解决这个问题?

感谢您提供任何有关此问题的指导!

编辑是因为我很快修复了之前提到的其他一些错误的来源

经过很长时间的无法得到这个工作,我终于偶然发现一个演示,我不认为是链接的任何文档: http : //bl.ocks.org/1095795 :

在这里输入图像描述

这个演示包含了最终帮助我解决问题的关键。

enter()上添加多个对象可以通过将enter()赋值给一个variables,然后追加。 这是有道理的。 第二个关键部分是节点和链接数组必须基于force() – 否则随着节点被删除和添加,图和模型将不同步。

这是因为如果构build一个新的数组,它将缺less以下属性 :

  • 索引 – 节点数组内节点的从零开始的索引。
  • x – 当前节点位置的x坐标。
  • y – 当前节点位置的y坐标。
  • px – 前一个节点位置的x坐标。
  • py – 前一个节点位置的y坐标。
  • 固定 – 指示节点位置是否locking的布尔值。
  • 重量 – 节点重量; 关联链接的数量。

这些属性对于force.nodes()的调用并不是非常必要的,但是如果这些属性不存在,那么它们将在第一次调用时被force.start() 随机初始化。

如果有人好奇,工作代码看起来像这样:

 <script type="text/javascript"> 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++; } var index = findNodeIndex(id); if(index !== undefined) { nodes.splice(index, 1); update(); } } this.addLink = function (sourceId, targetId) { var sourceNode = findNode(sourceId); var targetNode = findNode(targetId); if((sourceNode !== undefined) && (targetNode !== undefined)) { links.push({"source": sourceNode, "target": targetNode}); update(); } } var findNode = function (id) { for (var i=0; i < nodes.length; i++) { 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 = $(el).innerWidth(), h = $(el).innerHeight(); var vis = this.vis = d3.select(el).append("svg:svg") .attr("width", w) .attr("height", h); var force = d3.layout.force() .gravity(.05) .distance(100) .charge(-100) .size([w, h]); var nodes = force.nodes(), links = force.links(); var update = function () { var link = vis.selectAll("line.link") .data(links, function(d) { return d.source.id + "-" + d.target.id; }); link.enter().insert("line") .attr("class", "link"); 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("image") .attr("class", "circle") .attr("xlink:href", "https://d3nwyuy0nl342s.cloudfront.nethttp://img.dovov.comicons/public.png") .attr("x", "-8px") .attr("y", "-8px") .attr("width", "16px") .attr("height", "16px"); nodeEnter.append("text") .attr("class", "nodetext") .attr("dx", 12) .attr("dy", ".35em") .text(function(d) {return d.id}); node.exit().remove(); 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("transform", function(d) { return "translate(" + dx + "," + dy + ")"; }); }); // Restart the force layout. force.start(); } // Make it all go update(); } graph = new myGraph("#graph"); // You can do this from the console as much as you like... graph.addNode("Cause"); graph.addNode("Effect"); graph.addLink("Cause", "Effect"); graph.addNode("A"); graph.addNode("B"); graph.addLink("A", "B"); </script>