【 D3.js 高級系列 — 2.0 】 捆圖

捆圖(Bundle)是 D3 中比較奇特的一個佈局,只有兩個函數,並且須要與其它佈局配合使用。本文講述捆圖的製做方法。javascript

201

有關捆圖的例子極少,很容易找到的是:http://bl.ocks.org/mbostock/1044242html


可是此例有些複雜,不太容易弄懂,並且也沒有相應的說明文字(D3官網上的例大多沒有相應文字解說),根本沒法做爲參考。本文提供一個簡單的例子,講解捆圖究竟是什麼,應該怎麼作。java


捆圖只有兩個函數:node


d3.layout.bundle():建立一個捆圖佈局。數組


bundle(links):根據數組links的source和target,計算路徑。app


捆圖的佈局之因此函數少,是由於它常與其它層級佈局一塊兒使用。所謂層級佈局,是指採用嵌套結構(父子節點關係)來描述節點信息,根據層級佈局擴展出來的佈局即:集羣圖、打包圖、分區圖、樹狀圖、矩陣樹圖。最多見的是與集羣圖一塊兒使用,使用集羣圖佈局計算節點的位置,再用捆圖佈局計算連線路徑。也就是說,捆圖佈局只幹一件事:svg


計算連線的路徑。wordpress


下面舉一個例子。中國的高鐵已經在不少城市開通,如北京到上海,北京到桂林等。現要製做一圖來表示通過哪一座城市的高鐵最密集。函數


1. 數據

有9座城市:佈局

var cities = {
     name: "",
     children:[
     	{name: "北京"},{name: "上海"},{name: "杭州"},
     	{name: "廣州"},{name: "桂林"},{name: "昆明"},
     	{name: "成都"},{name: "西安"},{name: "太原"}
     ]
 };


這9座城市所屬的節點有一個公共的父節點,父節點名稱爲空,稍後並不繪製此父節點。另外還有鏈接各城市高鐵的數組,以下:

var railway = [
			{source: "北京", target: "上海"},
			{source: "北京", target: "廣州"},
			{source: "北京", target: "杭州"},
			{source: "北京", target: "西安"},
			{source: "北京", target: "成都"},
			{source: "北京", target: "太原"},
			{source: "北京", target: "桂林"},
			{source: "北京", target: "昆明"},
			{source: "北京", target: "成都"},
			{source: "上海", target: "杭州"},
			{source: "昆明", target: "成都"},
			{source: "西安", target: "太原"}
];	//(該數據爲假設,並無通過調查)


source和target分別表示高鐵的兩端。

2. 佈局(數據轉換)

前面提到,捆圖佈局要和其餘佈局聯合使用,在這裏與集羣圖佈局聯合。分別建立一個集羣圖佈局和一個捆圖佈局。

var cluster = d3.layout.cluster()
				.size([360, width/2 - 50])
				.separation(function(a, b) { 
return (a.parent == b.parent ? 1 : 2) / a.depth; 
});

var bundle = d3.layout.bundle();


從集羣圖佈局的參數能夠看出,接下來節點將要分佈呈圓形。捆圖佈局沒有參數能夠設置,只建立便可,保存在變量bundle中。


先使用集羣圖佈局計算節點:

var nodes = cluster.nodes(cities);
console.log(nodes);


將計算後的節點數組保存在nodes中,並輸出該數組,結果如圖1所示,第一個節點有9個子節點,其餘的節點都有且只有一個父節點,沒有子節點。這是接下來捆圖要使用的節點數組,可是倒是用集羣圖佈局計算而來的。

10-7-2

圖1


下一步是重點,要使用數組railway。因爲railway中存儲的source和target都只有城市名稱,所以先要將其對應成nodes中的節點對象。寫一個函數,按城市名將railway中的source和target替換成節點對象。

function map( nodes, links ){
var hash = [];
	for(var i = 0; i < nodes.length; i++){
		hash[nodes[i].name] = nodes[i];
	}
	var resultLinks = [];
	for(var i = 0; i < links.length; i++){
		resultLinks.push({  	source: hash[ links[i].source ], 
								target: hash[ links[i].target ]
							});
	}
	return resultLinks;
}


使用該函數返回的數組,便可做爲捆圖佈局bundle的參數使用:

var oLinks = map(nodes, railway);
	console.log(oLinks);
		
var links = bundle(oLinks);
console.log(links);


map()返回的結果保存在oLinks,bundle()返回的結果保存在links中,輸出結果分別如圖2和圖3所示,即捆圖佈局的連線數組轉換先後的變換。

10-7-1

圖2


如圖2,轉換前,連線數組的每一項都只有兩個變量:source和target,內容是節點對象。對於第一個連線,是從「北京」到「上海」。如圖3,轉換以後,source和target不見了,取而代之的是0、一、2,變成了一個數組。很明顯,該數組的第一項和source的內容同樣,最後一項和target的內容同樣,但中間多出了一項(圖3的紅框內)。多出的這一項是根據source和target公共的父節點計算出來的。因而,該數組表示了一條路徑。


其實,捆圖佈局根據各連線的source和target爲咱們計算了一條條連線路徑,咱們能夠把捆圖佈局的做用簡單地解釋爲:使用這些路徑繪製的線條能更美觀地表示「通過哪座城市的高鐵最多」

10-7-3

圖3


3. 繪製

通過捆圖佈局轉換後的數據很適合用d3.svg.line()和d3.svg.line.radial()來繪製,前者是線段生成器,後者是放射式線段生成器。在line.interpolate()所預約義的插值模式中,有一種就叫作bundle,正是爲捆圖準備的。


因爲本例中用集羣圖佈局計算節點數組使用的使用圓形的,所以要用放射式的線段生成器。先建立一個:

var line = d3.svg.line.radial()
					.interpolate("bundle")
					.tension(.85)
					.radius(function(d) { return d.y; })
					.angle(function(d) { return d.x / 180 * Math.PI; });


此線段生成器是用來獲取連線路徑的。接下來,添加一個分組元素<g>,用來放全部與捆圖相關的元素。

gBundle = svg.append("g")
				.attr("transform", 
"translate(" + (width/2) + "," + (height/2) + ")");

var color = d3.scale.category20c();	//顏色比例尺


而後,先在gBundle中添加連線路徑:

var link = gBundle.selectAll(".link")
			  .data(links)
			  .enter()
			  .append("path")
			  .attr("class", "link")
			  .attr("d", line);	//使用線段生成器


在該連線的樣式中,添加透明度可以在連線匯聚處更能顯示出「捆」的效果。例如樣式設定爲:

.link {
		fill: none;
		stroke: black;
		stroke-opacity: .5;
		stroke-width: 8px;
	}


連線的繪製結果如圖4所示。

10-7-4

圖4


最後,向圖中添加節點。節點用一個圈,裏面寫上城市的名稱來表示。首先,綁定節點數組,並添加與之對應的<g>元素。

var node = gBundle.selectAll(".node")
			  .data( nodes.filter(function(d) { return !d.children; }) )
			  .enter()
			  .append("g")
			  .attr("class", "node")
			  .attr("transform", function(d) {
					return "rotate(" + (d.x- 90) + ")translate(" 
+ d.y + ")" + "rotate("+ (90 - d.x) +")"; 
			  });


要注意,被綁定的數組是通過過濾後的nodes數組。此處的filter是JavaScript數組對象自身的函數,粗體字的部分表示只綁定沒有子節點的節點,也就是說9座城市的公共父節點不繪製。而後只要在該分組元素<g>中分別加入<circle>和<text>便可。

node.append("circle")
			  .attr("r", 20)
			  .style("fill",function(d,i){ return color(i); });
			
	node.append("text")
			.attr("dy",".2em")
			.style("text-anchor", "middle")
			.text(function(d) { return d.name; });


結果如圖5所示。因爲通過北京的高鐵線路最多,連線在北京的圓圈處最密集,就好像將不少條繩子「捆」在這裏同樣。

10-7-5 

圖5

 


當節點和連線變得不少很複雜時,就變成了本文開頭處的例子。就像本文開頭說的同樣,那個例子完成度過高,根本不能提供參考。但願本文的例子能給你們做爲捆圖的參考範例。


完整代碼請點擊如下連接查看:


http://www.ourd3js.com/demo/G-2.0/bundle.html


謝謝閱讀。

文檔信息

相關文章
相關標籤/搜索