学习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.youkuaiyun.com/lzhlzz/article/details/46508041
然后也可以将这些数字放在外面,用连线连接着,大概思路就是将现在文字水平向左或向右平移一段距离(根据弧中心在左边右边来判断)
然后将两点信息保存下来,添加一个polyline 然后传给point就可以了