2014-09-03 91 views
4

我已經構建了一個節制指向圖,但是,在我的情況下,我也有一個80px * 80px的網格框。我希望圖中的每個節點不僅可以根據現有的重力和力量進行定位,而且可以在最近的網格框中間定位(不固定)。 在d3js中可以這樣做嗎?D3節點位於網格中的節點佈局圖

回答

1

您要申請自定義部隊

force.on("tick", function() { 

    node.attr("cx", function(d) { return d.x; }) 
     .attr("cy", function(d) { return d.y; }); 

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

}); 

所以沒有內置的方式做這樣的事情......

所以你的情況,你必須要找到接近電網盒子中心並且使用節點和盒子中心之間的距離以及一些重力方程來計算x和y值。

在你的情況下

node 
    .attr("cx", function(d) { 
     d.x += f(d).x; 
     return d.x; 
    }) 
    .attr("cy", function(d) { 
     d.y += f(d).y; 
     return d.y; 
    }); 

其中f(d)是你的重力的矢量取決於箱子中心和實際節點d之間的距離。例如

var blackHole = function (d) { 
    var gc = { 
     x: 100, 
     y: 100 
    }; 
    var k = 0.1; 

    var dx = gc.x - d.px; 
    var dy = gc.y - d.py; 

    return { 
     x: k * dx, 
     y: k * dy 
    }; 
}; 

這是很難找到出f(d)其真正起作用由多個重心,所以我建議你閱讀有關這種力量的算法。我嘗試了一些有趣的例子,但是沒有一個能按照你想要的方式工作。 ;-)

至少現在:

var grid = function (d) { 
    var fx = d.px % 100; 
    if (fx < 0) 
     fx += 100; 
    if (fx > 50) 
     fx -= 100; 

    var fy = d.py % 100; 
    if (fy < 0) 
     fy += 100; 
    if (fy > 50) 
     fy -= 100; 
    var k = -1; 

    return { 
     x: k * fx, 
     y: k * fy 
    }; 
}; 

這是一個100px的密集網格非常簡單的力量......但我猜的結果不是你所期望的,節點可以重疊,因爲力佈局只有具有共同鏈接的節點相互排斥,至少這是我的經驗(編輯:這是因爲負電荷)...我認爲使用d3四邊形構建自定義佈局佈局會容易得多...

2

Moritz Stefaner想出了一個辦法來做到這一點

code:https://github.com/moritzstefaner/gridexperiments/

演示:http://moritzstefaner.github.io/gridexperiments/

編輯:

由@altocumulus提到

,這並沒有代碼的副本。通常我只複製個人網站的代碼,因爲它們比github上的東西更容易消失。或者我會在短時間內複製它(少於50個loc?)。無論如何,由於代碼的肉可能會被拉出,我已經複製下面的mortiz的index.html文件。其他引用的js文件可以很容易地在別處找到。 (只是注意,你應該拉每個庫的版本爲12月9日的,2011)

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"> 
<html lang="en"> 
<head> 
    <meta charset="utf-8"> 
    <title>Forces and grids</title> 
    <script type="text/javascript" src="d3.min.js"></script> 
    <script type="text/javascript" src="d3.layout.min.js"></script> 
    <script type="text/javascript" src="d3.geom.min.js"></script> 
    <script type="text/javascript" src="underscore-min.js"></script> 
    <script src="jquery-1.7.2.min.js" charset="utf-8"></script> 
    <style type="text/css" media="screen"> 
     .menu { position:absolute; top :20px; right:20px; } 
    </style>  
</head> 
<body> 
    <script type="text/javascript" charset="utf-8"> 
     var w = 700, h = 700; 
     var vis = d3.select("body").append("svg:svg").attr("width", w).attr("height", h); 
     var background = vis.append("g"); 
     var nodes = []; 
     var links = []; 
     var USE_GRID = true; 
     var GRID_SIZE = 60; 
     var GRID_TYPE = "HEXA"; 

     // set up event handlers 
     $(document).ready(function(){ 
      $("#USE_GRID").click(
       function(){ 
        USE_GRID = $(this).is(":checked"); 
        $(this).blur(); 
        force.start(); 
       } 
      ); 

      //$("#CELL_SIZE").rangeinput(); 
      $("#CELL_SIZE").bind("change", 
       function(){ 
        console.log($(this).attr("value")); 
        GRID_SIZE = $(this).attr("value"); 
        grid.init(); 
        force.start(); 
       } 
      ); 

      $("[name=GRID_TYPE]").click( 
       function(){ 
        GRID_TYPE = $(this).attr("value"); 
        grid.init(); 
        force.start(); 
       } 
      ); 
     }); 
     for(var i = 0; i < 30; i++) { 
      var node = { 
       label : "node " + i 
      }; 
      nodes.push(node); 
     }; 
     for(var i = 0; i < nodes.length; i++) { 
      for(var j = 0; j < i; j++) { 
       if(Math.random() > .99-Math.sqrt(i)*.02) 
        links.push({ 
         source : i, 
         target : j, 
         weight :1 
        }); 
      } 
     }; 
     var force = d3.layout.force().size([w, h]).nodes(nodes).links(links).gravity(1).linkDistance(function(d){return (1-d.weight)*100}).charge(-3000).linkStrength(function(x) { 
      return x.weight * 5 
     }); 
     force.start(); 
     var link = vis.selectAll("line.link").data(links).enter().append("svg:line").attr("class", "link").style("stroke-width", 1.5).style("stroke", "#555").style("opacity", function(d){return d.weight*.7}); 
     var node = vis.selectAll("g.node").data(force.nodes()).enter().append("svg:g").attr("class", "node"); 
     node.append("svg:circle").attr("r", 6).style("fill", "#555").style("stroke", "#FFF").style("stroke-width", "4px"); 
     node.call(force.drag); 
     var updateLink = function() { 
      this.attr("x1", function(d) { 
       return d.source.screenX; 
      }).attr("y1", function(d) { 
       return d.source.screenY; 
      }).attr("x2", function(d) { 
       return d.target.screenX; 
      }).attr("y2", function(d) { 
       return d.target.screenY; 
      }); 
     } 
     var updateNode = function() { 
      this.attr("transform", function(d) { 
       if(USE_GRID) { 
        var gridpoint = grid.occupyNearest(d); 
        if(gridpoint) { 
         d.screenX = d.screenX || gridpoint.x;       
         d.screenY = d.screenY || gridpoint.y;              
         d.screenX += (gridpoint.x - d.screenX) * .2; 
         d.screenY += (gridpoint.y - d.screenY) * .2; 

         d.x += (gridpoint.x - d.x) * .05; 
         d.y += (gridpoint.y - d.y) * .05; 
        } 
       } else { 
        d.screenX = d.x; 
        d.screenY = d.y; 
       } 
       return "translate(" + d.screenX + "," + d.screenY + ")"; 
      }); 
     }; 
     var grid = function(width, height) { 
      return { 
       cells : [], 
       init : function() { 
        this.cells = []; 
        for(var i = 0; i < width/GRID_SIZE; i++) { 
         for(var j = 0; j < height/GRID_SIZE; j++) { 
          // HACK: ^should be a better way to determine number of rows and cols 
          var cell; 
          switch (GRID_TYPE) { 
           case "PLAIN": 
            cell = { 
             x : i * GRID_SIZE, 
             y : j * GRID_SIZE 
            }; 
            break; 
           case "SHIFT_ODD_ROWS": 
            cell = { 
             x : i * GRID_SIZE, 
             y : 1.5 * (j * GRID_SIZE + (i % 2) * GRID_SIZE * .5) 
            }; 
            break; 
           case "HEXA": 
            cell = { 
             x : i * GRID_SIZE + (j % 2) * GRID_SIZE * .5, 
             y : j * GRID_SIZE * .85 
            }; 
            break; 
          } 
          this.cells.push(cell); 
         }; 
        }; 
       }, 
       sqdist : function(a, b) { 
        return Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2); 
       }, 
       occupyNearest : function(p) { 
        var minDist = 1000000; 
        var d; 
        var candidate = null; 
        for(var i = 0; i < this.cells.length; i++) { 
         if(!this.cells[i].occupied && (d = this.sqdist(p, this.cells[i])) < minDist) { 
          minDist = d; 
          candidate = this.cells[i]; 
         } 
        } 
        if(candidate) 
         candidate.occupied = true; 
        return candidate; 
       } 
      } 
     }(w, h); 
     force.on("tick", function() { 
      vis.select("g.gridcanvas").remove(); 
      if(USE_GRID) { 
       grid.init(); 
       var gridCanvas = vis.append("svg:g").attr("class", "gridcanvas"); 
       _.each(grid.cells, function(c) { 
        gridCanvas.append("svg:circle").attr("cx", c.x).attr("cy", c.y).attr("r", 2).style("fill", "#555").style("opacity", .3); 
       }); 
      } 
      node.call(updateNode); 
      link.call(updateLink); 
     }); 
    </script> 
    <div class="menu"> 
     <div> 
      <input type="checkbox" id="USE_GRID" checked>use grid</input>  
     </div> 
     <div> 
      <input type="range" min="30" step="10" max="150" id="CELL_SIZE" value="60"></input> 
     </div> 
     <div> 
      <input type="radio" name="GRID_TYPE" value="PLAIN">plain</input> 
      <input type="radio" name="GRID_TYPE" value="SHIFT_ODD_ROWS">Shift odd rows</input> 
      <input type="radio" name="GRID_TYPE" value="HEXA" checked>Hexa</input>        
     </div> 
    </div> 
</body> 

+0

這是一個非常好的例子!但是,由於鏈接唯一的答案被視爲VLQ,因此在SO上皺起眉頭,你是否介意至少分享一些代碼細節和見解? – altocumulus 2017-06-13 10:03:07

+0

@altocumulus - 我從github複製了核心代碼。我已經對它的工作原理做了任何真正的分析,只是它應該與OP所期望的結果緊密匹配。 – viggity 2017-06-13 18:06:18