d3.js attrTween的意外行为

时间:2014-03-08 01:47:27

标签: d3.js

我是D3.js的新手。我将Mike Bostock的this示例改编为this小提琴,其中提供了基于attrTweengetPointAtLength的逐点插值插值示例。

在原始示例中,引入了单个圆形,其遵循简单路径。在改编的示例中,不是引入单个圆,而是生成一束圆,其遵循更复杂的形状,即Inkscape中制作的玩具示例路径。

动画可以进行多次迭代,但过了一会儿,圆圈似乎陷入了循环,最终页面冻结了。但是,如果只生成一个圆圈,例如var RadiusData = [20];(见下面的代码),动画保持循环正常。

可能导致这种情况的原因是什么?是否有一种简单的方法可以避免这种行为?

var w = $(window).width(),
    h = $(window).height();

var svg = d3.select("body").append("svg:svg")
    .attr("width", w)
    .attr("height", h)
  .append("svg:g")
   .attr("transform", "translate(" + 0.25*w + "," + 0 + ")");

//some toy path data   
var dInkScape ="m 360.1639,630.31678 c 1.0609,13.05167 -195.29107,-273.68628 -203.49722,-275.81173 -22.23818,-5.75983 -24.83733,-34.59299 -15.23811,-51.66666 17.17076,-30.54078 59.06286,-32.72422 85.71428,-14.04764 39.11203,27.40863 40.85844,83.86959 12.85717,119.7619 C 202.67874,456.39146 131.20349,457.65152 86.190506,420.21936 29.546262,373.1148 28.796105,286.43841 75.714265,232.36222 132.53844,166.8687 234.51201,166.64035 297.61902,223.07645 c 74.36943,66.50798 74.06939,183.83474 8.09531,255.95237 C 229.54464,562.29148 96.8291,561.45911 15.714334,485.93366 -76.453418,400.11684 -75.086213,251.98848 9.9999617,161.88605 105.45379,60.804734 269.012,62.70845 368.09519,157.36214 478.09632,262.44568 489.74023,530.06221 385.51394,638.12097 z";

var path = svg.append("svg:path")
    .attr("d", dInkScape);

//some random data for the circle radii
var RadiusData = [20,50,25,5,40,22,50,66,72,23];

//introduce a circle for each element, set radius and give it some random color
var circle = svg.selectAll("circle")
    .data(RadiusData).enter()
    .append("svg:circle")
    .attr("r", function(d){return d;})
    .style("fill",function(d,i) {return "hsl(" + 120 + 100 *Math.random() + ",100%,25%)";})
    .attr("transform", "translate(0," + -h / 3 + ")");

//with a 1 second delay introduce a new circle
function transition() {
  circle.transition()
      .duration(5000)
      .delay(function(d,i){return 1000*i;})
      .attrTween("transform", translateAlong(path.node()))
      .each("end", transition);
}

transition();

// Returns an attrTween for translating along the specified path element.
function translateAlong(path) {
  var l = path.getTotalLength();
  return function(d, i, a) {
    return function(t) {
      var p = path.getPointAtLength(t * l); 
      return "translate(" + p.x + "," + p.y + ")";
    };
  };
}

2 个答案:

答案 0 :(得分:2)

问题是,只要任何一个圆圈完成转换,您就会在圆圈上的所有开始新的转换,如果您的初始选择有多个,则会导致重叠转换爆炸元件。

在选择中为每个元素调用transition.each回调。您可能想说d3.select(this)为单个元素创建转换,如chained transitions example

答案 1 :(得分:2)

问题是你在每个圈的移动结束时重新调用转换函数,但该函数为每个圈创建一个新的转换:

//with a 1 second delay introduce a new circle
function transition() {
  circle.transition() //add a transition to every circle
      .duration(5000)
      .delay(function(d,i){return 1000*i;})
      .attrTween("transform", translateAlong(path.node()))
      .each("end", transition);  
            //re-run this function after *each* circle's transition completes
}

修复它的一个选项是让each函数仅重新启动第一个元素的转换,即当i==0!i为真时:

function transition() {
  circle.transition()
      .duration(5000)
      .delay(function(d,i){return 1000*i;})
      .attrTween("transform", translateAlong(path.node()))
      .each("end", function(d,i){if (!i) transition(); });
}

http://jsfiddle.net/A88W3/8/

正如@mbostock刚刚建议的那样,另一个选项是让你的函数只适用于单个元素:

function transitionThis(d,i) { //index is given to the function
  d3.select(this).transition()
      .duration(5000)
      .delay(1000*i) //not a function anymore
      .attrTween("transform", translateAlong(path.node()))
      .each("end", transitionThis); //repeat for this element
}

circle.each(transitionThis); //start transitions for each

http://jsfiddle.net/A88W3/9/

或者,如果您只想应用延迟一次,错开开始时间,然后让所有圆圈均匀移动而不停在路径的开头:

function transitionThis(d,i) { 
  d3.select(this).transition()
      .duration(5000) //no delay once started
      .ease("linear") //move steadily at all points on path
      .attrTween("transform", translateAlong(path.node()))
      .each("end", transitionThis); //repeat for this element
}

circle.transition().duration(0)
      .delay(function(d,i){return 1000*i;}) //stagger starts
      .each(transitionThis); //start transitions for each

http://jsfiddle.net/A88W3/10/

另一件事:至少在调试过程中,以编码方式停止任何无限循环总是一个好主意。我在上面的小提琴中通过向整个svg添加一个点击功能来创建一个新的圆圈过渡,中断无限循环版本:

svg.on("click", function() { //Stop the infinite transitions!
    circle.transition(); //create a new empty transition
});