Vue.js Advent Calendar 2016 11日目
#まえがき
Vue.jsと描画機能とd3.jsの計算機能を合わせて折れ線グラフを描いてみます。
d3.jsで直接描画してもいいのですが、せっかくなのでVue.jsで宣言的に描けたらな、と。
#デモ
デモ(jsfiddle)
#コード
##メインのコンポーネント
<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>
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
は目盛線の値です。
##曲線コンポーネント
<path class="line" :d="d" :style="{stroke: myColor}"></path>
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の恩恵感じます。
##グリッド線コンポーネント
<line :x1="0" :y1="yValue" :x2="max" :y2="yValue" class="grid"></line>
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で動くんだ……?
.line{
fill:none;
stroke-width: 2px;
transition: all 1s;
}
追加/削除の変更にも対応する時はtransition-group
を使いましょう。
こちらはVueが-enter
と-leave
のclassを追加してくれるのでFirefox,Chromeのどちらも動きます。
<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>
.line-enter {
opacity: 0;
transform: translateX(-100px);
}
.line-leave-active {
opacity: 0;
transform: translateX(100px);
}
#おわりに
曲線の変更のTransitionをどうしたものかよくわからないな……。
#追記
svgのTransitionに悩んだけど、普通にガイドに書いてあった!
Vueのドキュメントの充実具合すげえや!
##TweenLite.jsを使ったsvgのtransition
デモ(jsfiddle)
曲線のコンポーネントを次のように書き換えました。
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曲線のアニメーションをすることが出来ました。
##おわりに(追記)
ガイドを読もう!