对Arc的多个开始角度和结束角度进行随机化和动画处理

时间:2017-01-19 18:20:33

标签: animation d3.js tween

我正在尝试创建一个视觉,它是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>

SCRIPT

// 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 个答案:

答案 0 :(得分:2)

这里有一些问题

  1. 你的弧函数将在每次调用时返回随机半径,这不是我想你想要的。您可以将弧从一个内/外半径转换到下一个半径,但为简单起见,假设每个路径最初只获得一个随机半径

  2. 为了从旧的一对开始/结束角度过渡到新角度,您需要将当前角度存储在某处。我们会将其存储在local variable中,该here将绑定到每条路径

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

  4. 这里的工作代码:

        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时,arc.innerRadius中的'this'是正确的。
    4. d3.interpolate可以愉快地处理对象,而不仅仅是数字。
    5. 我有一个类似的例子,如果你想了解更多,我一直在{{3}}上工作。