1. 程式人生 > >D3.js之餅圖動畫

D3.js之餅圖動畫

學習D3差不多一個月了。最近看了剛看了echart,發現他裡面的餅圖初始化的時候是從無到有的開啟一圈。所以想要模仿這個動畫效果。

如果想要學習D3.js餅圖佈局可以去看這個網站:http://www.ourd3js.com

開始的想法是這樣的:先給出餅圖從無到有展開花費的所有時間,再設計餅圖每個弧所佔的比例,乘以動畫總時間,就每個弧所要執行的時間。

在用一個delaytime變數儲存每個弧之前所有弧需要展開的時間總和。這是用來延遲這個弧動畫時間,從而每個弧的無縫連線動畫。

<html>

	<head>
		<meta charset="utf-8">
		<title>餅狀圖</title>
	</head>

	<style>

	</style>

	<body>
		<script src="../js/d3.js" charset="utf-8"></script>
		<script>
			var width = 400;
			var height = 400;
			var dataset = [30, 10, 43, 55, 13];
			
			var svg = d3.select("body")
				.append("svg")
				.attr("width", width)
				.attr("height", height);
			var pie = d3.layout.pie().sort(null);	//定義餅圖的佈局
			var piedata = pie(dataset);	//將資料傳給pie,就可以得到繪圖的資料
			var outerRadius = 150; //外半徑
			var innerRadius = 0; //內半徑,為0則中間沒有空白
			var sum=0;
			piedata.forEach(function(d,i){
				d._endAngle=d.endAngle;//儲存這個值,後面動畫要用到。
				d.endAngle=d.startAngle;//讓每個弧的弧度都是0
				d.duration=2001*(d.data/d3.sum(dataset));
				d.delaytime=sum;
				sum+=d.duration;
			})
			var arc = d3.svg.arc() //弧生成器
				.innerRadius(innerRadius) //設定內半徑
				.outerRadius(outerRadius); //設定外半徑

			var color = d3.scale.category10();
			var arcs = svg.selectAll("g")		//先新增五個分組元素,用來存放一段弧的相關元素
				.data(piedata)
				.sort(d3.ascending)
				.enter()
				.append("g")
				.attr("transform", function(d,i){
					return "translate(" + (width / 2) + "," + (width / 2) + ")";
				});
			arcs.append("path")		//給每個分組元素g新增一個路徑
				.attr("fill", function(d, i) {
					return color(i);
				})
				.attr("d", function(d, i) {
					return arc(d);		
				})
				.transition()
				.duration(function(d,i){
					return d.duration;
				})
				.ease("linear")
				.delay(function(d,i){
					console.log(d.delaytime)
					return d.delaytime;
				})
				.attr("d", function(d, i) {
					d.endAngle=d._endAngle;
					// d.endAngle=d._endAngle;
//					arc.outerRadius(outerRadius+i*10);每個邊的長度不一樣。
					// console.log(d);     d此時是一個物件包括data、endAngle padAngle StartAngle value	
					console.log(d);
					return arc(d);		//通過之前定義的弧生成器來轉化資料
				})
				;	
		</script>
	</body>
</html>

上面就是我實現的程式碼。但是非常醜陋,並不是那種平滑展開那種效果。

然後最近看了一個API。

transition.attrTween - 在兩個屬性值之間平滑地過渡。(本人英語不是太好,感謝前人翻譯的中文API)

<html>

	<head>
		<meta charset="utf-8">
		<title>餅狀圖</title>
	</head>

	<style>

	</style>

	<body>
		<script src="../js/d3.js" charset="utf-8"></script>
		<script>
			var width = 400;
			var height = 400;
			var dataset = [30, 10, 43, 55, 13];
			
			// dataset.sort(function(a,b){return b-a;});
			var svg = d3.select("body")
				.append("svg")
				.attr("width", width)
				.attr("height", height);

			var pie = d3.layout.pie().sort(null);	//定義餅圖的佈局

			var piedata = pie(dataset);	//將資料傳給pie,就可以得到繪圖的資料
			var outerRadius = 150; //外半徑
			var innerRadius = 0; //內半徑,為0則中間沒有空白
			var sum=0;
			piedata.forEach(function(d,i){
				d._endAngle=d.endAngle;
				d.endAngle=d.startAngle;
				d.duration=2000*(d.data/d3.sum(dataset));//動畫時長2秒,計算每一個弧形所用動畫時間
				d.delaytime=sum;
				sum+=d.duration;
			})
			var arc = d3.svg.arc() //弧生成器
				.innerRadius(innerRadius) //設定內半徑
				.outerRadius(outerRadius); //設定外半徑

			var color = d3.scale.category10();
			var arcs = svg.selectAll("g")		//先新增五個分組元素,用來存放一段弧的相關元素
				.data(piedata)
				.enter()
				.append("g")
				.attr("transform", function(d,i){
					return "translate(" + (width / 2) + "," + (width / 2) + ")";
				});

			arcs.append("path")		//給每個分組元素g新增一個路徑
				.attr("fill", function(d, i) {
					return color(i);
				})
				.attr("d", function(d, i) {
					console.log(d);
					return arc(d);		
				})
				.transition()
				.duration(function(d,i){
					return d.duration;
				})
				.ease("linear")
				.delay(function(d,i){
					return d.delaytime;
				})
				.attrTween("d", tweenArc(function(d, i) {
					return {
						startAngle: d.startAngle,	
						endAngle: d._endAngle
					};
				}))
				;	
			 function tweenArc(b) {
			 	return function(a, i) {
			 		var d = b.call(this, a, i),
			 			i = d3.interpolate(a, d);
			 			//d儲存轉換之後的資訊
			 			//插值模式,從d.endAnle=d.startAngle到d.endAngle=d._endAngle轉換
			 		return function(t) {
			 			return arc(i(t));
			 		};
			 	};
			 }
		</script>

	</body>

</html>

效果如下:


上面看起來的效果就差不多了。

然後填餅圖新增文字。因為文字是直接顯示上面的,即使弧沒有出來文字也在上,感覺不好看,可以跟上面一樣給text設定延遲出來的動畫。

arcs.append("text")
				.attr("transform", function(d) {
					//arc.centroid計算出每個弧的中心位置
					return "translate(" + arc.centroid(d) + ")";
				})
				.attr("text-anchor", "middle")
				.text(function(d) {
					return d.data;
				})


也可以給餅圖之間新增一些空隙
			d3.selectAll("path").attr("transform",function(d,i){
					var midAngle=(d.startAngle+d.endAngle)/2;
					return "translate("+(1*Math.sin(midAngle))+","+(-1*Math.cos(midAngle))+")";
				})


也可以給新增餅圖新增滑鼠移入移出事件

這邊我實現的是往外移動一點點,也可以實現移入的時候讓它變大,echart的餅圖就是這樣的效果

arcs.select("path").on("mouseover",function(d,i){
					d3.select(this).transition()
						.duration(500)
						.ease("linear")
						.attr("transform", function(d, i) {
							var midAngle = (d.startAngle + d.endAngle) / 2;
							return "translate(" + (20 * Math.sin(midAngle)) + "," + (-20 * Math.cos(midAngle)) + ")";
						})
				})
				.on("mouseout",function(d,i){
					d3.select(this).transition()
						.duration(500)
						.ease("linear")
						.attr("transform", function(d, i) {
							var midAngle=(d.startAngle+d.endAngle)/2;
					return "translate("+(1*Math.sin(midAngle))+","+(-1*Math.cos(midAngle))+")";
							return "translate(0)";
						})
				})

還有給餅圖新增一些資訊,可以檢視這位大神的部落格:http://blog.csdn.net/lzhlzz/article/details/46508041

然後也可以將這些數字放在外面,用連線連線著,大概思路就是將現在文字水平向左或向右平移一段距離(根據弧中心在左邊右邊來判斷)

然後將兩點資訊儲存下來,新增一個polyline 然後傳給point就可以了