力導(dǎo)向圖(Force-Directed Graph),是繪圖的一種算法。在二維或三維空間里配置節(jié)點(diǎn),節(jié)點(diǎn)之間用線連接,稱為連線。各連線的長(zhǎng)度幾乎相等,且盡可能不相交。節(jié)點(diǎn)和連線都被施加了力的作用,力是根據(jù)節(jié)點(diǎn)和連線的相對(duì)位置計(jì)算的。根據(jù)力的作用,來(lái)計(jì)算節(jié)點(diǎn)和連線的運(yùn)動(dòng)軌跡,并不斷降低它們的能量,最終達(dá)到一種能量很低的安定狀態(tài)。
http://wiki.jikexueyuan.com/project/d3wiki/images/force-1.png" alt="力導(dǎo)向圖" />
力導(dǎo)向圖能表示節(jié)點(diǎn)之間的多對(duì)多的關(guān)系。
初始數(shù)據(jù)如下:
var nodes = [ { name: "桂林" }, { name: "廣州" },
{ name: "廈門(mén)" }, { 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 } ];
節(jié)點(diǎn)(nodes)和連線(edges)的數(shù)組,節(jié)點(diǎn)是一些城市名,連線的兩端是節(jié)點(diǎn)的序號(hào)(序號(hào)從 0 開(kāi)始)。
這些數(shù)據(jù)是不能作圖的,因?yàn)椴恢拦?jié)點(diǎn)和連線的坐標(biāo)。這句話一說(shuō)出來(lái),就請(qǐng)想到布局。本章用到的布局是:d3.layout.force()。
定義一個(gè)力導(dǎo)向圖的布局如下。
var force = d3.layout.force()
.nodes(nodes) //指定節(jié)點(diǎn)數(shù)組
.links(edges) //指定連線數(shù)組
.size([width,height]) //指定作用域范圍
.linkDistance(150) //指定連線長(zhǎng)度
.charge([-400]); //相互之間的作用力
然后,使力學(xué)作用生效:
force.start(); //開(kāi)始作用
如此,數(shù)組 nodes 和 edges 的數(shù)據(jù)都發(fā)生了變化。在控制臺(tái)輸出一下,看看發(fā)生了什么變化。
console.log(nodes);
console.log(edges);
節(jié)點(diǎn)轉(zhuǎn)換前后如下圖。
http://wiki.jikexueyuan.com/project/d3wiki/images/force-2.png" alt="節(jié)點(diǎn)轉(zhuǎn)換前后" />
轉(zhuǎn)換后,節(jié)點(diǎn)對(duì)象里多了一些變量。其意義如下:
再來(lái)看看連線的變化。
http://wiki.jikexueyuan.com/project/d3wiki/images/force-3.png" alt="連線轉(zhuǎn)換前后" />
可以看到,連線的兩個(gè)節(jié)點(diǎn)序號(hào),分別變成了對(duì)應(yīng)的節(jié)點(diǎn)對(duì)象。
有了轉(zhuǎn)換后的數(shù)據(jù),就可以作圖了。分別繪制三種圖形元素:
代碼如下:
//添加連線
var svg_edges = svg.selectAll("line")
.data(edges)
.enter()
.append("line")
.style("stroke","#ccc")
.style("stroke-width",1);
var color = d3.scale.category20();
//添加節(jié)點(diǎn)
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); //使得節(jié)點(diǎn)能夠拖動(dòng)
//添加描述節(jié)點(diǎn)的文字
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;
});
調(diào)用 call( force.drag ) 后節(jié)點(diǎn)可被拖動(dòng)。force.drag() 是一個(gè)函數(shù),將其作為 call() 的參數(shù),相當(dāng)于將當(dāng)前選擇的元素傳到 force.drag() 函數(shù)中。
最后,還有一段最重要的代碼。由于力導(dǎo)向圖是不斷運(yùn)動(dòng)的,每一時(shí)刻都在發(fā)生更新,因此,必須不斷更新節(jié)點(diǎn)和連線的位置。
力導(dǎo)向圖布局 force 有一個(gè)事件 tick,每進(jìn)行到一個(gè)時(shí)刻,都要調(diào)用它,更新的內(nèi)容就寫(xiě)在它的監(jiān)聽(tīng)器里就好。
force.on("tick", function(){ //對(duì)于每一個(gè)時(shí)間間隔
//更新連線坐標(biāo)
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; });
//更新節(jié)點(diǎn)坐標(biāo)
svg_nodes.attr("cx",function(d){ return d.x; })
.attr("cy",function(d){ return d.y; });
//更新文字坐標(biāo)
svg_texts.attr("x", function(d){ return d.x; })
.attr("y", function(d){ return d.y; });
});
tick 的英文意思是鐘表發(fā)出的嘀嗒嘀嗒聲,想到這個(gè)大家應(yīng)該很清楚了吧。每次觸發(fā)時(shí),都會(huì)調(diào)用后面的無(wú)名函數(shù) function。
結(jié)果如圖:
http://wiki.jikexueyuan.com/project/d3wiki/images/force-4.png" alt="結(jié)果" />
下載地址:rm92.zip