随机化和动画化弧的多个起始角和结束角

Randomize and animate multiple Start and End Angles of Arc

我正在尝试创建一个视觉效果,它是 Arc Tween 演示的修改版。在其中,我想要一个数据数组来定义每个弧的颜色和一般半径,并且在一个间隔上,开始和结束角度都应该缓慢地动画。但我认为我定义每个弧线的方式会导致动画过度。

HTML

<!DOCTYPE html>

<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <title>Arcs</title>
</head>
<body>
  <svg width="960" height="500"></svg>
</body>
</html>

剧本

// Modified from Arc Tween
// https://bl.ocks.org/mbostock/5100636

var tau = 2 * Math.PI; // http://tauday.com/tau-manifesto
var overlap = 50

var jsonArcs = [
  { "base_radius": 370, "color" : "red"},
  { "base_radius": 330, "color" : "orange"},
  { "base_radius": 290, "color" : "yellow"},
  { "base_radius": 250, "color" : "green"},
  { "base_radius": 210, "color" : "blue" },
  { "base_radius": 170, "color" : "purple"},
  { "base_radius": 130, "color" : "black"},
  { "base_radius": 90, "color" : "red"}
];


var arc = d3.arc()
    .startAngle(function(d) { return Math.random() * tau; })
    .endAngle(function(d) { return Math.random() * tau; })
    .innerRadius(function(d) { return d.base_radius - overlap * Math.random(); })
    .outerRadius(function(d) { return d.base_radius + overlap * Math.random(); });

var center_def = d3.arc()
    .innerRadius(0)
    .outerRadius(60)
    .startAngle(0);


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

  var path = g.selectAll("path")
      .data(jsonArcs) 
    .enter().append("path")
      .attr("fill", function(d, i) { return d.color; })
      .attr("d", arc);


var center = g.append("path")
    .datum({endAngle: tau})
    .style("fill", "black")
    .attr("d", center_def);


d3.interval(function() {

  path.transition()
    .duration(750)
    .attrTween("d", arcTween(Math.random() * tau, arc));
}, 2500);

function arcTween(newAngle, obj) {

  return function(d) {

    var interpolate = d3.interpolate(d.endAngle, newAngle);

    return function(t) {

      d.endAngle = interpolate(t);

      return obj(d);
    };
  };
}

不是每个圆弧从起始角度平滑地动画到新角度,整个视觉跳转多次到新状态。

如何配置此功能,使每个圆弧的起点和终点角度从旧角度平滑过渡到新角度?

这里有一些问题

  1. 你的 arc 函数每次调用都会 return 随机半径,这不是我认为你想要的。您可以将圆弧从一个 inner/outer 半径过渡到下一个 inner/outer 半径,但为简单起见,假设每条路径最初仅获得随机半径

  2. 为了从旧的一对 start/end 角度过渡到新的一对,您需要将当前角度存储在某处。我们将它存储在一个 local variable 中,它将绑定到每个路径

  3. 因为每条路径都有不同的 inner/outer 半径,我们也需要为每个线段设置不同的弧函数。

工作代码在这里:

    var tau = 2 * Math.PI; // http://tauday.com/tau-manifesto
    var overlap = 50;
    var currentSegment = d3.local();
    var segmentRadius = d3.local();

    var jsonArcs = [
      { "base_radius": 370, "color" : "red"},
      { "base_radius": 330, "color" : "orange"},
      { "base_radius": 290, "color" : "yellow"},
      { "base_radius": 250, "color" : "green"},
      { "base_radius": 210, "color" : "blue" },
      { "base_radius": 170, "color" : "purple"},
      { "base_radius": 130, "color" : "black"},
      { "base_radius": 90, "color" : "red"}
    ];

    var arc = d3.arc()
        .innerRadius(function() { return segmentRadius.get(this).innerRadius })
        .outerRadius(function() { return segmentRadius.get(this).outerRadius });

    var center_def = d3.arc()
        .innerRadius(0)
        .outerRadius(60)
        .startAngle(0);

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

    var path = g.selectAll("path")
        .data(jsonArcs)
        .enter().append("path")
        .attr("fill", function(d, i) { return d.color; })
        .each(function(d) {
          var angles = randomAngles();
          d.startAngle = angles.startAngle;
          d.endAngle = angles.endAngle;
          segmentRadius.set(this, {
            innerRadius: d.base_radius - overlap * Math.random(),
            outerRadius: d.base_radius + overlap * Math.random()
          });
        })
        .attr("d", arc)
        .each(function(d) {currentSegment.set(this, d)});

    var center = g.append("path")
        .datum({endAngle: tau})
        .style("fill", "black")
        .attr("d", center_def);

    d3.interval(function() {
      path.transition()
          .duration(750)
          .attrTween("d", arcTween);
    }, 2500);

    function arcTween() {
      var thisPath = this;
      var interpolate = d3.interpolate(currentSegment.get(this), randomAngles());
      currentSegment.set(this, interpolate(0));

      return function(t) {
        return arc.call(thisPath, interpolate(t));
      };
    }
    function randomAngles() {
      var angles = [Math.random() * tau, Math.random() * tau].sort();
      return {startAngle: angles[0], endAngle: angles[1]};
    }

请注意有关更改代码的一些事项:

  1. 我在设置 "d" 属性之前在路径上的每个调用中设置了随机初始角度
  2. 我将段半径存储在同一条链末端的 segmentRadius d3.local 变量中,并且在调用转换时设置了每个插值之后
  3. 在过渡函数中,我需要调用 arc 来保留路径的 'this',以便在检索 segmentRadius 时 'this' 在 arc.innerRadius 中是正确的。
  4. d3.interpolate 可以愉快地处理对象而不仅仅是数字。

我有一个类似的例子,如果你想了解更多,我一直在研究 here