力導向圖中每一個節點都受到力的作用而運動,這種是一種非常絢麗的圖表。
力導向圖(Force-Directed Graph),是繪圖的一種算法。在二維或三維空間里配置節點,節點之間用線連接,稱為連線。
各連線的長度幾乎相等,且盡可能不相交。節點和連線都被施加了力的作用,力是根據節點和連線的相對位置計算的。
根據力的作用,來計算節點和連線的運動軌跡,並不斷降低它們的能量,最終達到一種能量很低的安定狀態。
力導向圖能表示節點之間的多對多的關系。
1. 數據
初始數據如下:
var nodes = [ { name: "桂林" }, { name: "廣州" }, { name: "廈門" }, { name: "杭州" }, { name: "上海" }, { name: "青島" }, { name: "天津" } ]; var edges = [ { source : 0 , target: 1 } , { source : 0 , target: 2 } , { source : 0 , target: 3 } , { source : 1 , target: 4 } , { source : 1 , target: 5 } , { source : 1 , target: 6 } ];
節點(nodes)和連線(edges)的數組,節點是一些城市名,連線的兩端是節點的序號(序號從 0 開始)。
這些數據是不能作圖的,因為不知道節點和連線的坐標。這句話一說出來,就請想到布局。這里用到的布局是:d3.layout.force()。
2. 布局(數據轉換)
定義一個力導向圖的布局如下。
var force = d3.layout.force() .nodes(nodes) //指定節點數組 .links(edges) //指定連線數組 .size([width,height]) //指定作用域范圍 .linkDistance(150) //指定連線長度 .charge([-400]); //相互之間的作用力
然后,使力學作用生效:
force.start(); //開始作用
如此,數組 nodes 和 edges 的數據都發生了變化。在控制台輸出一下,看看發生了什么變化。
console.log(nodes);
console.log(edges);
節點轉換前后如下圖。
轉換后,節點對象里多了一些變量。其意義如下:
-
- index:節點的索引號
- px, py:節點上一個時刻的坐標
- x, y:節點的當前坐標
- weight:節點的權重
再來看看連線的變化。
可以看到,連線的兩個節點序號,分別變成了對應的節點對象。
3. 繪制
有了轉換后的數據,就可以作圖了。分別繪制三種圖形元素:
-
- line,線段,表示連線。
- circle,圓,表示節點。
- text,文字,描述節點。
代碼如下:
//添加連線 var svg_edges = svg.selectAll("line") .data(edges) .enter() .append("line") .style("stroke","#ccc") .style("stroke-width",1); var color = d3.scale.category20(); //添加節點 var svg_nodes = svg.selectAll("circle") .data(nodes) .enter() .append("circle") .attr("r",20) .style("fill",function(d,i){ return color(i); }) .call(force.drag); //使得節點能夠拖動 //添加描述節點的文字 var svg_texts = svg.selectAll("text") .data(nodes) .enter() .append("text") .style("fill", "black") .attr("dx", 20) .attr("dy", 8) .text(function(d){ return d.name; });
調用 call( force.drag ) 后節點可被拖動。force.drag() 是一個函數,將其作為 call() 的參數,相當於將當前選擇的元素傳到 force.drag() 函數中。
最后,還有一段最重要的代碼。由於力導向圖是不斷運動的,每一時刻都在發生更新,因此,必須不斷更新節點和連線的位置。
力導向圖布局 force 有一個事件 tick,每進行到一個時刻,都要調用它,更新的內容就寫在它的監聽器里就好。
force.on("tick", function(){ //對於每一個時間間隔 //更新連線坐標 svg_edges.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; }); //更新節點坐標 svg_nodes.attr("cx",function(d){ return d.x; }) .attr("cy",function(d){ return d.y; }); //更新文字坐標 svg_texts.attr("x", function(d){ return d.x; }) .attr("y", function(d){ return d.y; }); });
tick 的英文意思是鍾表發出的嘀嗒嘀嗒聲,想到這個大家應該很清楚了吧。每次觸發時,都會調用后面的無名函數 function。