我希望能够在 D3.js 中将多个(基于数据)弧形图从百分之一(角度)动画化到另一个百分比,并且可以在最初将它们绘制得很好。
然而,经过一番寻找之后,我被动画困住了。下面是执行原始绘图的代码,然后是动画到后续值的两个选项。我为每个图表节点使用组,因为我将向每个图表节点添加多个元素。
要查看每个选项的工作情况,请注释掉另一个选项。
理想情况下,我希望能够创建一个弧函数,可以向其中传递innerRadius、outerRadius,然后传递endAngle。至少对于 endAngle,我希望能够选择传递常量(例如 0)或绑定数据(例如 d.pct)。
index.html
<html lang="en">
<head>
<script src="https://d3js.org/d3.v6.min.js"></script>
</head>
<body>
<div id="vis">
</div>
<script src = 'SOarc.js'></script>
</body>
</html>
SOarc.js
data = [
{x:50, y: 250, pct: 0.25},
{x:200, y: 250, pct: 0.50},
{x:350, y: 250, pct: 0.75}]
radialScale = d3.scaleLinear()
.domain([0, 1])
.range([0, 2 * Math.PI]);
svg = d3.select("#vis")
.append('svg')
.attr('width', 500)
.attr('height', 500)
.attr('opacity', 1)
// Join to the data and create a group for each data point so that various chart items (e.g. multiple arcs) can be added
chartNodes = svg
.selectAll('g.chartGroup')
.data(data)
// Position each using transform/ translate with coordinates specified in data
chartNodesEnter = chartNodes
.enter()
.append('g')
.attr("class", "chartGroup")
.attr('transform', (d) => 'translate('+d.x+','+d.y+')');
// Add arcs to as per data
chartNodesEnter.append('path')
.attr("class", "chart1")
.attr('fill', "red")
.attr('d', d3.arc()
.startAngle(0)
.endAngle((d) => radialScale(d.pct))
.innerRadius(50+2) // This is the size of the donut hole
.outerRadius(50+8));
// Now animate to a different endAngle (90% in this example)
// Option 1 - Standard Interpolation - doesn't work with complex shapes
// --------------------------------------------------------------------
// Animate all arcs to 90% - doesn't animate properly as interpolation not correct for this complex shape
// and also throws Error: <path> attribute d: Expected arc flag ('0' or '1') errors for the same reason
svg.selectAll('.chart1')
.transition().duration(3000).delay(0)
.attr('d', d3.arc()
.startAngle(0)
.endAngle(function(d){ return radialScale(0.9)})
.innerRadius(50+2) // This is the size of the donut hole
.outerRadius(50+8)
)
// Option 2 - Tween Interpolation - Produces error
// -----------------------------------------------
// Code from from Mike Bostock's Arc Tween http://bl.ocks.org/mbostock/5100636
// Errors with <path> attribute d: Expected moveto path command ('M' or 'm'), "function(t) {\n …".
var arc = d3.arc()
.innerRadius(50+2)
.outerRadius(50+8)
.startAngle(0);
// Returns a tween for a transition’s "d" attribute, transitioning any selected
// arcs from their current angle to the specified new angle.
function arcTween(newAngle) {
return function(d) {
var interpolate = d3.interpolate(d.endAngle, newAngle);
return function(t) {
d.endAngle = interpolate(t);
return arc(d);
};
};
}
// Animate to 90%
svg.selectAll('.chart1')
.transition().duration(3000).delay(0)
.attrTween("d", d => arcTween(radialScale(0.9)) );
Error: <path> attribute d: Expected moveto path command ('M' or 'm'), "function(t) {\n …". @ SOarc.js:68
选项 2 是执行此操作的正确方法,但 Bostock 先生的示例对于您更简单的用例来说有点过分了。
让我们检查一下实现您的目标的最简单的代码:
// create a arc generator with start angle of 0
var arc = d3
.arc()
.innerRadius(50 + 2)
.outerRadius(50 + 8)
.startAngle(0)
.endAngle(0);
svg
.selectAll('.chart1')
.transition()
.duration(3000)
.delay(0)
.attrTween('d', function(d,i) {
// for each chart
// create an interpolator between start angle 0
// and end angle of d.pct
var interpolate = d3.interpolate(0, radialScale(d.pct));
// attrTween is expecting a function to call for every iteration of t
// so let's return such a function
return function(t) {
// assign end angle to interpolated value for t
arc.endAngle(interpolate(t));
// call arc and return intermediate `d` value
return arc();
};
});
它正在运行:
<html lang="en">
<head>
<script src="https://d3js.org/d3.v6.min.js"></script>
</head>
<body>
<div id="vis"></div>
<script>
data = [
{ x: 50, y: 250, pct: 0.25 },
{ x: 200, y: 250, pct: 0.5 },
{ x: 350, y: 250, pct: 0.75 },
];
radialScale = d3
.scaleLinear()
.domain([0, 1])
.range([0, 2 * Math.PI]);
svg = d3
.select('#vis')
.append('svg')
.attr('width', 500)
.attr('height', 500)
.attr('opacity', 1);
// Join to the data and create a group for each data point so that various chart items (e.g. multiple arcs) can be added
chartNodes = svg.selectAll('g.chartGroup').data(data);
// Position each using transform/ translate with coordinates specified in data
chartNodesEnter = chartNodes
.enter()
.append('g')
.attr('class', 'chartGroup')
.attr('transform', (d) => 'translate(' + d.x + ',' + d.y + ')');
// Add arcs to as per data
chartNodesEnter
.append('path')
.attr('class', 'chart1')
.attr('fill', 'red')
.attr(
'd',
d3
.arc()
.startAngle(0)
.endAngle((d) => radialScale(d.pct))
.innerRadius(50 + 2) // This is the size of the donut hole
.outerRadius(50 + 8)
);
// Now animate to a different endAngle (90% in this example)
// Option 1 - Standard Interpolation - doesn't work with complex shapes
// --------------------------------------------------------------------
// Animate all arcs to 90% - doesn't animate properly as interpolation not correct for this complex shape
// and also throws Error: <path> attribute d: Expected arc flag ('0' or '1') errors for the same reason
/*
svg
.selectAll('.chart1')
.transition()
.duration(3000)
.delay(0)
.attr(
'd',
d3
.arc()
.startAngle(0)
.endAngle(function (d) {
return radialScale(0.9);
})
.innerRadius(50 + 2) // This is the size of the donut hole
.outerRadius(50 + 8)
);
*/
// Option 2 - Tween Interpolation - Produces error
// -----------------------------------------------
// Code from from Mike Bostock's Arc Tween http://bl.ocks.org/mbostock/5100636
// Errors with <path> attribute d: Expected moveto path command ('M' or 'm'), "function(t) {\n …".
var arc = d3
.arc()
.innerRadius(50 + 2)
.outerRadius(50 + 8)
.startAngle(0)
.endAngle(0);
// Animate to end angle
svg
.selectAll('.chart1')
.transition()
.duration(3000)
.delay(0)
.attrTween('d', function(d,i) {
var interpolate = d3.interpolate(0, radialScale(d.pct));
return function(t) {
arc.endAngle(interpolate(t));
return arc();
};
});
</script>
</body>
</html>
新的评论片段
变量有很多选项
arcs
。我想到的第一件事是将半径添加到数据绑定中并创建像此代码片段中那样的弧。
<html lang="en">
<head>
<script src="https://d3js.org/d3.v6.min.js"></script>
</head>
<body>
<div id="vis"></div>
<script>
data = [
{ x: 50, y: 250, pct: 0.25, inner: 52, outer: 58 },
{ x: 200, y: 250, pct: 0.5, inner: 22, outer: 28 },
{ x: 350, y: 250, pct: 0.75, inner: 82, outer: 88 },
];
radialScale = d3
.scaleLinear()
.domain([0, 1])
.range([0, 2 * Math.PI]);
svg = d3
.select('#vis')
.append('svg')
.attr('width', 500)
.attr('height', 500)
.attr('opacity', 1);
// Join to the data and create a group for each data point so that various chart items (e.g. multiple arcs) can be added
chartNodes = svg.selectAll('g.chartGroup').data(data);
// Position each using transform/ translate with coordinates specified in data
chartNodesEnter = chartNodes
.enter()
.append('g')
.attr('class', 'chartGroup')
.attr('transform', (d) => 'translate(' + d.x + ',' + d.y + ')');
// Add arcs to as per data
chartNodesEnter
.append('path')
.attr('class', 'chart1')
.attr('fill', 'red')
.attr(
'd',
d3
.arc()
.startAngle(0)
.endAngle((d) => radialScale(d.pct))
.innerRadius(50 + 2) // This is the size of the donut hole
.outerRadius(50 + 8)
);
// Now animate to a different endAngle (90% in this example)
// Option 1 - Standard Interpolation - doesn't work with complex shapes
// --------------------------------------------------------------------
// Animate all arcs to 90% - doesn't animate properly as interpolation not correct for this complex shape
// and also throws Error: <path> attribute d: Expected arc flag ('0' or '1') errors for the same reason
/*
svg
.selectAll('.chart1')
.transition()
.duration(3000)
.delay(0)
.attr(
'd',
d3
.arc()
.startAngle(0)
.endAngle(function (d) {
return radialScale(0.9);
})
.innerRadius(50 + 2) // This is the size of the donut hole
.outerRadius(50 + 8)
);
*/
// Option 2 - Tween Interpolation - Produces error
// -----------------------------------------------
// Code from from Mike Bostock's Arc Tween http://bl.ocks.org/mbostock/5100636
// Errors with <path> attribute d: Expected moveto path command ('M' or 'm'), "function(t) {\n …".
// Animate to end angle
svg
.selectAll('.chart1')
.transition()
.duration(3000)
.delay(0)
.attrTween('d', function(d,i) {
var interpolate = d3.interpolate(0, radialScale(d.pct));
var arc = d3
.arc()
.innerRadius(d.inner)
.outerRadius(d.outer)
.startAngle(0)
.endAngle(0);
return function(t) {
arc.endAngle(interpolate(t));
return arc();
};
});
</script>
</body>
</html>
参考Marks Answer,我认为d3更新了一些功能,因为我设法找到的解决方案如下所示:
const arc = d3
.arc()
.cornerRadius(2)
.padAngle(0)
.innerRadius(radius - (arcWidth ?? radius))
.outerRadius(radius)
svg
.select('.plot-area')
.selectAll('path')
.data(formattedData)
.join('path')
...
.transition()
.duration(3000)
.delay(0)
.attrTween('d', function (d) {
const interpolate = d3.interpolate(d.startAngle, d.endAngle)
return function (t) {
d.endAngle = interpolate(t)
return arc(d)
}
})