2012-03-02 68 views
84

關於Stack Overflow的第一個問題,請耐心等待!我對d3.js是新手,但一直對其他人能夠完成的事情感到驚訝......我幾乎同樣驚訝於我自己能夠取得的進展。很顯然,我並不是在追求什麼,所以我希望這裏的善良的靈魂能夠讓我看到光明。將新節點添加到Force-directed佈局

我的本意是做一個可重用的JavaScript功能,只需執行以下操作:

  • 創建在指定的DOM元素的空力向圖
  • 允許您添加和刪除標記,圖像 - 承載節點操作圖,指定

我已經採取http://bl.ocks.org/950642爲起點它們之間的連接,因爲這是基本我希望能夠創建什麼樣的佈局:

enter image description here

這裏是我的代碼如下所示:

<!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.net/images/icons/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(" + d.x + "," + d.y + ")"; }); 
     }); 

     // 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()來創建一個新的元素,所以這不適用於我需要綁定的額外元素到節點。我怎樣才能解決這個問題?

非常感謝您提供任何有關此問題的指導!

編輯,因爲我迅速修復了以前提到

回答

147

很長的時間了無法得到這個工作後,其他幾個錯誤的根源,我終於跨過我不認爲這是一個偶然的演示任何鏈接的文檔:http://bl.ocks.org/1095795

enter image description here

該演示包含哪些終於幫我破解的難題的鑰匙。

enter()上添加多個對象可以通過將enter()分配給一個變量,然後附加到該變量來完成。這是有道理的。第二個關鍵部分是節點和鏈接陣列必須基於force() - 否則隨着節點被刪除和添加,圖形和模型將不同步。

這是因爲如果一個新的數組被構造,相反,它會缺乏以下attributes

  • 索引 - 節點陣列中的節點的索引(從零開始)。
  • x - 當前節點位置的x座標。
  • y - 當前節點位置的y座標。
  • px - 前一個節點位置的x座標。
  • py - 前一個節點位置的y座標。
  • fixed - 指示節點位置是否鎖定的布爾值。
  • 權重 - 節點權重;關聯鏈接的數量。

這些屬性沒有嚴格需要調用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.net/images/icons/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(" + d.x + "," + d.y + ")"; }); 
     }); 

     // 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> 
+2

是的,先生。順便說一句,這是一個非常好的答案! – Maziyar 2014-03-21 04:16:59

+1

添加新數據時使用'force.start()'而不是'force.resume()'是關鍵。非常感謝! – Mouagip 2014-06-10 09:05:01

+0

這太棒了。如果它自動縮放縮放級別(可能會減少收費,直到所有東西都適合?),那麼要冷靜一些,因此所有東西都按照它正在繪製的盒子的大小裝配。 – 2014-07-31 12:43:11