使用D3.js做複雜資料視覺化(三):理解transition的控制來設計動畫效果

在DOM中,同一個物件在轉換不同css特性時,可以藉由transition來做兩個css特性間轉換的銜接,因此創造出動畫的效果,而d3.js建立在這上面,使用d3.transition的方法來實作動畫特性,d3.transition的設計邏輯是由d3.selection所延伸而來,比較關鍵的不同是transition特性創造出的動畫效果,會有執行時間,也就是衍生了life cycle的問題。

雖然動畫效果在做資料視覺化設計中可能是需要放到最最最後的一個區塊,當然其在某些情況下會有很重要的角色(比如visualization algorithm等),可以藉由下面這個case來理解transition的life cycle特性

下面是stackoverflow上面的苦主問題
How can I make the animation of a circle continous with hovering like this gif in d3.js?

stackoverflowgif

簡單來說,提問者希望可以創造出慢慢往外擴散的圓環,這樣的效果可以利用d3.transition來達到,這樣的效果可以同時有很多方式可以達到,假如使用d3.transition的life cycle,則可以比較單純的完成。

想要更近一步理解d3.transition的話,可以閱讀下面這兩篇文章:

1. Working With Transition
2. A Life of Transition

這邊是原本提問者的代碼:

 var width = document.getElementById('circles').offsetWidth-70;
  var height =(width/2)+100;
  var scale = d3.scale.linear()
    .range(["yellow","orange","red"])
    .domain([0,60]);

  var data = [0,10,20,30,40,50,60];

  var svg = d3.select("#circles").append("svg")
      .attr("width", width+"px")
      .attr("height", height+"px");

  //circle orange
  svg.append("circle")
  .attr("cx",80)
  .attr("cy",80)
  .attr("r",30)
   .attr("fill","orange")
  .style("stroke-width","1")
  .style("stroke","orange")
  .on('mouseover', function(){
    d3.select(this).transition().ease("bounce").duration(500).attr("r",32)
  })
  .on('mouseout', function(){
    d3.select(this).transition().ease("bounce").duration(500).attr("r",30)
  })

  //circle yellow
   svg.append("circle")
  .attr("cx",80)
  .attr("cy",80)
  .attr("r",22)
  .attr("fill","yellow")
  .style("stroke-width","1")
  .style("stroke","yellow")
  .style("opacity","0.9")    

  .on('mouseover', function(){
    d3.select(this).transition().ease("bounce").duration(500).attr("r",24).style("opacity","1")
  })
  .on('mouseout', function(){
    d3.select(this).transition().ease("bounce").duration(500).attr("r",22).style("opacity","0.9")
  })      

     var circles = svg.selectAll("circle")
    .data(data)
    .enter()
    .append("circle")
    .attr("r",function(d) { return d; })
    .attr("transform","translate(80,80)")
    .attr("fill","none")
    .style("stroke-width","1")
    .style("stroke",function(d) { return scale(d) });  

  function transition() {
    // Update data, max d is 60:
    data = data.map(function(d) { return d == 60 ? 0 : d + 10});

    var i = 0;
    // Grow circles
    circles
       .data(data)
       .filter(function(d) { return d > 0 })
       .transition()
       //.ease(d3.easeLinear)
       .ease("linear")//set the ease here
       .attr("r", function(d) { return d; })
       .style("stroke", function(d) { return scale(d) })
       .style("opacity",function(d) { return d == 60 ? 0 : 1 })
       .duration(1000)
       //.on("end",function(){if(++i==circles.size()-1) { transition(); } });
        .each("end", function (d,i) {
          //.on("end", function (d,i) {
           if(++i==circles.size()-1) { transition(); }
        });

    // Reset circles where r == 0
    circles
      .filter(function(d) { return d == 0 })
      .attr("r", 0)
      .style("opacity",1)
      .style("stroke",function(d) { return scale(d); });

  }
  transition()
 

從上面的代碼可以看出,原提問者使用來創造出圓環漸進擴散的效果是利用其data變數裡面陣列資料來控制的,並沒有真正使用到d3.transition的觀念。

下面則是裡用transition life cycle來產生圓環漸進擴散的代碼(這邊是d3.v5版本)。

transition life cycle中主要有三種狀態:start, interrupt, end,在下面的代碼中用transition.on可以指定監聽的狀態,在效果transition完後,將此物件的property改回到原始狀態,如此可以完成一個循環

  const svg = d3.select(DOM.svg(250,250));
  const data = Array.from(Array(CircleNumber).keys());

  function emananting(num){

           svg.selectAll('circle.emanting')
               .each((d,i,g)=>{

                let index = i;
                let number = num;

                d3.select(g[i])
                  .transition()
    		  .duration((d,i)=>{
                     return 5000;
                   })
                  .delay((d,i)=>{
                     return index*1000;
                    })
    		  .attr('opacity',0)
    		  .attr('r',50)
                  .on('end', (d,i,g)=>{

                  d3.select(g[i])
.attr('r',10)
.attr('opacity',1);

                  });
                  // control flow from here
                  if (number { return 'number' + i;})
				.attr('r',10)
				.attr('stroke','orange')
     		    .attr('fill','none')
      		    .attr('opacity',1)
     		   	.attr('cx',125)
				.attr('cy',125);
  //control flow here
  let num = 0;
  emananting(num);
  

 
參考閱讀:
https://beta.observablehq.com/@weitinglin/demo-how-can-i-make-the-animation-of-a-circle-continous-with-h

發表留言