d3的力導向圖是表現關係型數據比較方便且直觀的方法,可是會遇到節點比較多且層級關係混亂的狀況,這時樹狀佈局就比較方便了,如何不破壞原來結構以最小的代價變換樹狀佈局呢?下面將爲你們介紹。javascript
http://en.jsrun.net/aXiKp/emb...java
首先咱們須要準備關係型數據,數據包括節點數據nodes和連線關係數據links,links的source和target分別表示源和目標的index。node
var nodes = [ {value:"66666666",type:"home",index:"0"}, {value:"11111111111",type:"phone",index:"1"}, {value:"22222222222",type:"phone",index:"2"}, {value:"33333333333",type:"phone",index:"3"}, {value:"44444444444",type:"phone",index:"4"}, {value:"55555555555",type:"phone",index:"5"}, {value:"aaa",type:"weixin",index:"6"}, {value:"bbb",type:"weixin",index:"7"}, {value:"ccc",type:"weixin",index:"8"}, {value:"ddd",type:"weixin",index:"9"}, {value:"eee",type:"weixin",index:"10"}, {value:"fff",type:"weixin",index:"11"}, ]; var links = [ {source:0,target:1}, {source:0,target:2}, {source:0,target:3}, {source:0,target:4}, {source:0,target:5}, {source:2,target:6}, {source:2,target:7}, {source:2,target:8}, {source:3,target:9}, {source:3,target:10}, {source:3,target:11}, ]
//新建畫布 var svg = d3.select("#forceMap").append("svg") .attr("width",width) .attr("height",height) .attr("id","forceSvg"); //建立group,svg的繪製中爲了不混亂及後續事件的添加,建議使用g標籤將畫布分組。 var mapG = svg.append("g") .attr("id","forceGroup"); //使用d3的力學佈局,經過設定的屬性,將數據計算 var force = d3.layout.force() .nodes(nodes) .links(links) .size([width,height]) .linkDistance(100) .charge([-1250]) .gravity(0.5) .friction(0.5); force.start();//開始計算 //繪製線,svg的覆蓋順序是後面標籤覆蓋前面的,因此爲了不線在點上面,要先畫line var linkG = mapG.selectAll(".link") .data(links) .enter() .append("line") .attr("class","link") .attr("stroke","#ccc"); //繪製點 var nodeG = mapG.selectAll(".node") .data(nodes) .enter() .append("circle") .attr("class","node") .attr("r",8) .attr("fill",function(d){ switch(d.type){ case "home": return "red";break; case"phone": return "blue";break; case "weixin": return "green";break; } }); //tick是力導向圖每一次運動須要計算的過程 force.on("tick", function () { linkG.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; }); nodeG.attr("cx", function (d) { return d.x }) .attr("cy", function(d){ return d.y }); });
在tick中,d3自帶的佈局計算幫咱們完成了點和線每一次運動的座標,
咱們只須要在tick中從新賦給頁面中的點和線便可完成運動效果。數組
在這裏,咱們利用d3的tree layout完成點的座標計算。
前提是必須把關係型數據改變爲樹狀層級數據。
用concat拷貝數組,避免影響全局數據。app
function drawTree(){ var middleData = {}; var linksBak = links.concat(); var nodesBak = nodes.concat(); //將數據整理爲樹狀結構 nodesBak.forEach(function(d){ if(d.index == 0){ var temp = { name:d.index, children:[] }; var treeData = toTreeData(linksBak); function toTreeData(data){ var pos={}; var tree=[]; var i=0; while(data.length!=0){ if(data[i].source.index==d.index){ tree.push({ name:data[i].target.index, children:[] }); pos[data[i].target.index]=[tree.length-1]; data.splice(i,1); i--; }else{ var posArr=pos[data[i].source.index]; if(posArr!=undefined){ var obj=tree[posArr[0]]; for(var j=1;j<posArr.length;j++){ obj=obj.children[posArr[j]]; } obj.children.push({ name:data[i].target.index, children:[] }); pos[data[i].target.index]=posArr.concat([obj.children.length-1]); data.splice(i,1); i--; } } i++; if(i>data.length-1){ i=0; } } return tree; } temp.children = treeData; middleData = temp; } }); //使用樹狀佈局計算位置 var tree = d3.layout.tree() .size([450,450]); var tempNodes = tree.nodes(middleData); //重啓佈局以改變位置 force.start(); force.on("tick",function(){ //在運動前強制修改節點座標爲樹狀結構 tempNodes.forEach(function(d,i){ nodes[d.name].x = d.x; nodes[d.name].y = d.y }); linkG.attr("x1", function (d) { return d.source.x; }) .attr("y1", function (d) { return d.source.y+10; }) .attr("x2", function (d) { return d.target.x; }) .attr("y2", function (d) { return d.target.y+10; }); nodeG.attr("cx", function (d) { return d.x }) .attr("cy", function(d){ return d.y+10 }); }) }