jnobuyukiのブログ

研究していて困ったことやその解決に関するメモ。同じように困ったあなたのために。twitter ID: @j_nobuyuki

transitionを利用したsvgオブジェクトの「連続的」変化

前の投稿で、D3.jsのtransitionを利用したsvgオブジェクトの変化を扱いました。
Flashに頼らずとも、簡単にオブジェクトを移動できる点がすばらしいと思います。

今回は、その続きです。
transitionを利用して、オブジェクトが設定範囲内を文字通り「駆け巡る」ようにします。

ウェブ検索で見つけた方法

transitionを解説したウェブページが幾つかあります。そこで紹介されているのは次のようなやり方です。

まず、前回同様、SVG上に円を一つ描きます。

<script src="http://d3js.org/d3.v3.min.js" charset="utf-8">
var svg = d3.select("body").select("div")
  .append("svg")
  .attr("width",600)
  .attr("height",450)
  .style("background-color", "black");
svg
  .append("circle")
  .attr("r",30)
  .attr("cx",100)
  .attr("cy",100)
  .style("fill","blue");
  
</script>

これを動かすのですが、transitionを繰り返し記述するだけです。ポイントはDelayの設定です。
前回のtransitionが終わってからの時間ではなく、全体を動かし始めてからの通算時間で設定しましょう。
この例では、円の中心座標(100,100)が1秒ごとに,(100,300),(300,300),(100,100)と移動し、その後、円の半径が1pxまで縮小します。

svg
  .select("circle")
  .transition()
  .delay(0)
  .duration(1000)
  .attr("cy", 300);
  
svg
  .select("circle")
  .transition()
  .delay(1000)
  .duration(1000)
  .attr("cx", 300);
    
svg
  .select("circle")
  .transition()
  .delay(2000)
  .duration(1000)
  .attr("cx", 100)
  .attr("cy", 100);
    
svg
  .select("circle")
  .transition()
  .delay(3000)
  .duration(1000)
  .attr("r", 1);




このやり方は、移動回数が少ない時にはいいのですが、円をどんどん動かしたいときには向いていません。
移動のたびにtransitionを書いているからです。
そこで別のやり方を紹介します。

data機能を利用した連続的変化

SVG上に円を一つ描くところまでは同じです。

<script src="http://d3js.org/d3.v3.min.js" charset="utf-8">
var svg = d3.select("body").select("div")
  .append("svg")
  .attr("width",600)
  .attr("height",450)
  .style("background-color", "black");
svg
  .append("circle")
  .attr("r",30)
  .attr("cx",100)
  .attr("cy",100)
  .style("fill","blue");
  
</script>

次に、乱数を生成するコードMath.random()を利用して、円の中心座標を50個ほど決めて、arrという配列に格納します。

var arr = [];

for (var i = 0; i < 50; i++) {
arr.push(new Object({x: (Math.random() * 600), y: (Math.random() * 450)}));
}

このランダムに決めた50個の座標にしたがって、先ほどの円がSVG上をかけるようなアニメーションを作ります。
データ配列をtransitionに利用する場合、data()とenter()を使った方がよさそうに思えます。
でも、そうすると50個の円が生成されてしまい、一つの円の座標のコントロールになりません。

そこでforEach()を使います。forEach()はD3.js独自のコードでforループに相当する操作を行います。
今回はsvgよりも一つ上位のスコープで配列の各要素を操作をします。
これによって一つの円だけを操作できます。
また、delayの使い方も重要です。この例ではi*300とすることで、データの要素順に移動のタイミングが300msずつずれています。

  arr.forEach(function(d,i){
    
    svg
    .select("circle")
    .transition()
    .delay(i * 300)
    .duration(300)
    .attr("cx", d.x)
    .attr("cy", d.y);

  });

ここまでをまとめると以下のようになります。