Vue.js+d3.jsで折れ線グラフを描く

Vue.js Advent Calendar 2016　11日目
#まえがき
Vue.jsと描画機能とd3.jsの計算機能を合わせて折れ線グラフを描いてみます。
d3.jsで直接描画してもいいのですが、せっかくなのでVue.jsで宣言的に描けたらな、と。

#デモ
[デモ(jsfiddle)](https://jsfiddle.net/huigo/2bz2mxb7/5/)

#コード
##メインのコンポーネント

```html:html
  <svg :width="width" :height="height" :viewBox="viewBox">
    <my-line v-for="(line,index) in lines" 
    :x-scale="xScale"
    :y-scale="yScale"
    :parameters="line"
    :index="index"></my-line>


    <horizontal-grid v-for="y in yTicks"
        :x-scale="xScale"
        :y-scale="yScale"
        :y="y"
        :x_max="100"
    ></horizontal-grid>
    <vertical-grid v-for="x in xTicks"
        :x-scale="xScale"
        :y-scale="yScale"
        :x="x"
        :y_max="100"
    ></vertical-grid>
  </svg>
```
```js:js
new Vue({
  el: '#app',
  data: function(){
    return {
      width:300,
      height:300,
      lines:[]
    }
  },
  computed:{
    viewBox:function(){
	    return '0 0 ' + this.width + ' ' + this.height;
    },
    xTicks:function(){
    	return this.xScale.ticks();
    },
    yTicks:function(){
    	return this.yScale.ticks();
    },
    xScale:function(){
        return d3.scaleLinear().domain([0,100]).range([0, this.width]);
    }, 
    yScale:function(y){
        return d3.scaleLinear().domain([0,100]).range([0, this.height]);
    }
  }
});

```
ポイントは`xScale`と`yScale`でしょうか。
`my-line`などの各コンポーネントに渡してスケールを合わせています。
`xTicks`と`yTicks`は目盛線の値です。

##曲線コンポーネント
```html:html
 <path class="line" :d="d" :style="{stroke: myColor}"></path>
```

```js:js
Vue.component('my-line', {
  template: '#line',
  props:{
  	parameters:Array,
    index:Number,
    xScale:Function,
    yScale:Function
  },
  computed:{
    d:function(){
      var xScale = this.xScale;
      var yScale = this.yScale;
      var line = d3.line()
                 .curve(d3.curveMonotoneX)
                 .x(function(d) {return xScale(d.x); })
                 .y(function(d) { return yScale(d.y); });
                 
      return line(this.parameters);
    },
    myColor:function(){
    	return d3.schemeCategory10[this.index % 10]
    }
  }
});

```
`d`属性がポイントですね。
手で描くとひたすらつらいので、d3.jsの恩恵感じます。

##グリッド線コンポーネント

```html:html
<line :x1="0" :y1="yValue" :x2="max" :y2="yValue" class="grid"></line>
```
```js:js
Vue.component('horizontal-grid', {
  template: '#horizontal-grid',
  props:{
  	x_max:Number,
    y:Number,
    xScale:Function,
    yScale:Function
  },
  computed:{
  	yValue:function(){
    	return this.yScale(this.y);
    },
    max:function(){
    	return this.xScale(this.x_max);
    }
  }
})
```
横線を描くだけのコンポーネントです。
`xScale`と`yScale`を受け取っているので、親のwidth,heightの変更に追従します。
縦線はこれと同じなので省略します。

##Transition
曲線の変更についてだけなら次のCSSでChromeでは動きました。
Firefoxでは残念ながら動きません。というかなぜChromeで動くんだ……？

```css:css
.line{
  fill:none;
  stroke-width: 2px;
  transition: all 1s;
}
```
追加/削除の変更にも対応する時は`transition-group`を使いましょう。
こちらはVueが`-enter`と`-leave`のclassを追加してくれるのでFirefox,Chromeのどちらも動きます。

```html:html
  <transition-group name="line" tag="g">
    <my-line v-for="(line,index) in lines" 
      :key="index"
    :x-scale="xScale"
    :y-scale="yScale"
    :parameters="line"
    :index="index"></my-line>
 </transition-group>
```

```css:css
.line-enter {
  opacity: 0;
  transform: translateX(-100px);
}
.line-leave-active {
  opacity: 0;
  transform: translateX(100px);
}

```

#おわりに
曲線の変更のTransitionをどうしたものかよくわからないな……。

#追記
svgのTransitionに悩んだけど、普通に[ガイド](https://jp.vuejs.org/v2/guide/transitioning-state.html)に書いてあった!
Vueのドキュメントの充実具合すげえや!

##TweenLite.jsを使ったsvgのtransition
[デモ(jsfiddle)](https://jsfiddle.net/2bz2mxb7/6/)

曲線のコンポーネントを次のように書き換えました。

```js:js
Vue.component('my-line', {
  template: '#line',
  props:{
  	parameters:Array,
    index:Number,
    xScale:Function,
    yScale:Function
  },
  data:function(){
    return {
      d:this.generateLineD(this.parameters)
    }
  },
  watch:{
  	parameters:{
      handler:'animateLine',
      deep:true
    },
    xScale:function(){
      this.animateLine(this.parameters);
    },
    yScale:function(){
      this.animateLine(this.parameters);
    }
  },
  computed:{
    myColor:function(){
    	return d3.schemeCategory10[this.index % 10]
    }
  },
  methods:{
    animateLine(parameters){
       TweenLite.to(
          this.$data, 
          1, 
          { d: this.generateLineD(parameters) }
       );
    },
    generateLineD(parameters){
      var xScale = this.xScale;
      var yScale = this.yScale;
    	var line = d3.line()
                 .curve(d3.curveMonotoneX)
                 .x(function(d) {return xScale(d.x); })
                 .y(function(d) { return yScale(d.y); });
                 
      return line(parameters);
    }
  }
});

```
TweetLite.jsはObjetやArrayの値を指定した秒数内で次第に変更してくれるライブラリです。
これを使った`animateLine`メソッドは`vm.$data.d`をじわじわ更新します。
watcherから`animateLine`メソッドを呼び出すことで、
SVG曲線のアニメーションをすることが出来ました。

##おわりに(追記)
ガイドを読もう!