我正在尝试开发一个 d3 圆环图视觉效果,它只需要用户定义的百分比——从 0 到 100% 的任意值。根据这个值,我希望 donut 的分段等于比例。例如,如果是 50%,则将附加一半的圆环图段。同样,如果为 100%,则将绘制整个圆环图;所有段都将被附加。我不知道如何以优雅的方式实现这一点,但我确实找到了一个粗略的解决方法,您可以在下面的代码片段中看到。
var data =
[{'value':0,'interval':6.25},
{'value':6.25,'interval':6.25},
{'value':12.5,'interval':6.25},
{'value':18.75,'interval':6.25},
{'value':25,'interval':6.25},
{'value':31.25,'interval':6.25},
{'value':'none','interval':6.25},
{'value':'none','interval':6.25},
{'value':'none','interval':6.25},
{'value':'none','interval':6.25},
{'value':'none','interval':6.25},
{'value':'none','interval':6.25},
{'value':'none','interval':6.25},
{'value':'none','interval':6.25},
{'value':'none','interval':6.25},
{'value':'none','interval':6.25}];
var width = 960,
height = 500,
radius = Math.min(width, height) / 2;
var color = d3.scale.linear()
.range(["#0005fd","#00fe80"]).domain([0,35]);
var explode = function(x,index) {
var offset = (index==5) ? 80:0;
var angle = (x.startAngle + x.endAngle)/2;
var xOff = Math.sin(angle)*offset;
var yOff = -Math.cos(angle)*offset;
return "translate(" +xOff+","+yOff+ ")";
}
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(radius - 70);
var pie = d3.layout.pie()
.padAngle(.05)
.sort(null)
.value(function(d) { return d.interval; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
//.attr('transform', explode)
.style('stroke','none')
.style("fill", function(d) {
if (d.data.value=='none') {
return 'none'
}
return color(d.data.value); });
g.append("text")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
//.text(function(d) { return d.data.age; });
svg.append('text')
.text('37.5%') // magic number
.attr('font-family','Play')
.attr('font-size','140px')
.attr('text-anchor','middle')
.attr('x',0)
.attr('y',40);
function type(d) {
d.interval = +d.interval;
return d;
}
<!DOCTYPE html>
<meta charset="utf-8">
<style>
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
本质上,所发生的情况是具有值的条目与读为“无”的条目的比例是 37.5%——这是我的神奇数字(6 个值和 10 个无,因此 6/16 = 37.5%)。不用说,这根本不可扩展。
问题
在我的特定时刻,是否有任何内置方法或其他劳动密集型程度较低的解决方案?我只是希望能够将 0 到 100 之间的数字传递给函数,然后绘制出该百分比的 donut 段。在我的特定情况下,我选择了 6.25,因为它看起来最美观。
也许自定义填充透明度来模拟爆炸段的效果?看起来太老套了...
注意:版本是可选的。也就是说我并不反对 d3.v5 解决方案,我只是使用 d3.v3,因为我在 v5 中还没有使用 donuts。
最佳答案
最简单的方法是在其余线段的顶部添加另一个圆弧。该弧可以是任意长度,因此它覆盖了所有不需要显示的线段。这可以通过以下方式完成:
var percentage = .35;
g.append("path")
.attr("d", d3.svg.arc()
.endAngle(Math.PI*2)
.startAngle(percentage * Math.PI*2)
.outerRadius(radius - 10)
.innerRadius(radius - 70)
)
.attr("fill","white")
我们从结束 Angular Math.PI*2 开始,它是一整圈,代表 100% 完成。然后我们以较小的起始 Angular 向后移动,覆盖 100% 和我们拥有的百分比之间的所有内容。
根据您想要的样式,您甚至可以让它稍微透明以显示哪些部分不完整。
这是一个例子:
var data = d3.range(16).map(function(d) {
return { value: d*6.25, interval: 6.25 };
})
var width = 400,
height = 400,
radius = Math.min(width, height) / 2;
var color = d3.scale.linear()
.range(["#0005fd","#00fe80"]).domain([0,35]);
var explode = function(x,index) {
var offset = (index==5) ? 80:0;
var angle = (x.startAngle + x.endAngle)/2;
var xOff = Math.sin(angle)*offset;
var yOff = -Math.cos(angle)*offset;
return "translate(" +xOff+","+yOff+ ")";
}
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(radius - 70);
var pie = d3.layout.pie()
.padAngle(.05)
.sort(null)
.value(function(d) { return d.interval; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
//.attr('transform', explode)
.style('stroke','none')
.style("fill", function(d) {return color(d.data.value); });
g.append("text")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.text(function(d) { return d.data.age; });
// Extra arc:
var percentage = .35;
g.append("path")
.attr("d", d3.svg.arc()
.endAngle(Math.PI*2)
.startAngle(percentage * Math.PI*2)
.outerRadius(radius - 10)
.innerRadius(radius - 70)
)
.attr("fill","white")
svg.append('text')
.text(percentage * 100 + "%") // magic number
.attr('font-family','Play')
.attr('font-size','140px')
.attr('text-anchor','middle')
.attr('x',0)
.attr('y',40);
function type(d) {
d.interval = +d.interval;
return d;
}
<!DOCTYPE html>
<meta charset="utf-8">
<style>
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
这种方法还可以实现简单的动画:
var data = d3.range(16).map(function(d) {
return { value: d*6.25, interval: 6.25 };
})
var width = 400,
height = 400,
radius = Math.min(width, height) / 2;
var color = d3.scale.linear()
.range(["#0005fd","#00fe80"]).domain([0,35]);
var explode = function(x,index) {
var offset = (index==5) ? 80:0;
var angle = (x.startAngle + x.endAngle)/2;
var xOff = Math.sin(angle)*offset;
var yOff = -Math.cos(angle)*offset;
return "translate(" +xOff+","+yOff+ ")";
}
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(radius - 70);
var pie = d3.layout.pie()
.padAngle(.05)
.sort(null)
.value(function(d) { return d.interval; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
//.attr('transform', explode)
.style('stroke','none')
.style("fill", function(d) {return color(d.data.value); });
g.append("text")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.text(function(d) { return d.data.age; });
// Extra arc:
var percentage = .35;
var coverArc = g.append("path")
.attr("fill","white")
var label = svg.append('text')
.text(percentage * 100 + "%") // magic number
.attr('font-family','Play')
.attr('font-size','140px')
.attr('text-anchor','middle')
.attr('x',0)
.attr('y',40);
function transition() {
coverArc
.transition()
.tween("d", function(d) {
var that = d3.select(this),
i = d3.interpolateNumber(0, percentage);
return function(t) {
that.attr("d", d3.svg.arc()
.endAngle(Math.PI*2)
.startAngle(i(t) * Math.PI*2)
.outerRadius(radius - 10)
.innerRadius(radius - 70)
)
label.text(Math.round(i(t) * 100) + "%");
};
})
.duration(1000)
// other way:
.transition()
.tween("d", function(d) {
var that = d3.select(this),
i = d3.interpolateNumber(percentage, 0);
return function(t) {
that.attr("d", d3.svg.arc()
.endAngle(Math.PI*2)
.startAngle(i(t) * Math.PI*2)
.outerRadius(radius - 10)
.innerRadius(radius - 70)
)
label.text(Math.round(i(t) * 100) + "%");
};
})
.duration(1000)
.each("end",transition);
}
transition();
function type(d) {
d.interval = +d.interval;
return d;
}
<!DOCTYPE html>
<meta charset="utf-8">
<style>
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
答案使用 d3v3,d3v4+ 有一些细微的变化,具体与弧有关,d3.svg.arc 现在是 d3.arc,d3.layout.pie 现在是 d3.pie,d3.scale .linear 现在是 d3.scaleLinear,对于第二个片段中使用的动画,transition.each 现在是 transition.on:
var data = d3.range(16).map(function(d) {
return { value: d*6.25, interval: 6.25 };
})
var width = 400,
height = 400,
radius = Math.min(width, height) / 2;
var color = d3.scaleLinear()
.range(["#0005fd","#00fe80"]).domain([0,35]);
var explode = function(x,index) {
var offset = (index==5) ? 80:0;
var angle = (x.startAngle + x.endAngle)/2;
var xOff = Math.sin(angle)*offset;
var yOff = -Math.cos(angle)*offset;
return "translate(" +xOff+","+yOff+ ")";
}
var arc = d3.arc()
.outerRadius(radius - 10)
.innerRadius(radius - 70);
var pie = d3.pie()
.padAngle(.05)
.sort(null)
.value(function(d) { return d.interval; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
//.attr('transform', explode)
.style('stroke','none')
.style("fill", function(d) {return color(d.data.value); });
g.append("text")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.text(function(d) { return d.data.age; });
// Extra arc:
var percentage = .35;
var coverArc = g.append("path")
.attr("fill","white")
var label = svg.append('text')
.text(percentage * 100 + "%") // magic number
.attr('font-family','Play')
.attr('font-size','140px')
.attr('text-anchor','middle')
.attr('x',0)
.attr('y',40);
function transition() {
coverArc
.transition()
.tween("d", function(d) {
var that = d3.select(this),
i = d3.interpolateNumber(0, percentage);
return function(t) {
that.attr("d", d3.arc()
.endAngle(Math.PI*2)
.startAngle(i(t) * Math.PI*2)
.outerRadius(radius - 10)
.innerRadius(radius - 70)
)
label.text(Math.round(i(t) * 100) + "%");
};
})
.duration(1000)
// other way:
.transition()
.tween("d", function(d) {
var that = d3.select(this),
i = d3.interpolateNumber(percentage, 0);
return function(t) {
that.attr("d", d3.arc()
.endAngle(Math.PI*2)
.startAngle(i(t) * Math.PI*2)
.outerRadius(radius - 10)
.innerRadius(radius - 70)
)
label.text(Math.round(i(t) * 100) + "%");
};
})
.duration(1000)
.on("end",transition);
}
transition();
function type(d) {
d.interval = +d.interval;
return d;
}
<!DOCTYPE html>
<meta charset="utf-8">
<style>
</style>
<body>
<script src="http://d3js.org/d3.v5.min.js"></script>
关于javascript - 将圆环图修改为 "progress donut chart",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55459625/