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

自分が開発しているサービス（Vueを主に使用）で分析系の機能をつくるにあたり
「やっぱりこれ、縦の折れ線グラフが一番わかりやすい」
となり、ググる...
しかし、そのまま使えそうなパッケージは見つからず、自前でつくることを決意。

Vueの公式ページに[SVGで描かれたグラフ](https://jp.vuejs.org/v2/examples/svg.html)があったのでやってみました。

## サンプル
<img width="339" alt="vue-line-graph.png" src="https://qiita-image-store.s3.amazonaws.com/0/204256/f85342ac-6fd7-6ea6-e925-6ea5c424b774.png">
https://jsfiddle.net/Wave7KN/5th9k53n/

## SVGとは
SVGは画像フォーマットの1つでもあり、XMLを使用したマークアップ言語でもあり、なかにはHTMLやCSSと同じような感覚で書けるものもあります。例えば、このように`:hover`も使えたりします。※サンプルの点の部分にマウスを乗せてみてください。
<img width="339" alt="vue-line-graph-hover.png" src="https://qiita-image-store.s3.amazonaws.com/0/204256/95976f80-7448-2d35-2c5a-1c4774a30b7a.png">
また、HTML同様にJavaScriptで制御できるため、今回はVue.jsを使用して折れ線グラフを描いていきます。

## サンプルデータを用意する
0~100の数値を5つずつ適当に用意しました。（ぜひランダム関数使ってください。）

```JavaScript:JavaScript｜3行目~
data: {
  all_data: [
    {
      type: 'a',
      scores: [34, 46, 28, 39, 60]    
    },
    {
      type: 'b',
      scores: [47, 32, 52, 33, 52]
    },
    {
      type: 'c',
      scores: [38, 29, 42, 53, 41]
    },
    {
      type: 'd',
      scores: [52, 57, 69, 48, 46]
    }
  ],
  ratio: 3, //横幅を3倍に拡大するために使う
  row_height: 30,
},
```

## SVGの土台を用意する
```HTML:HTML｜2行目~
<svg :width="100 * ratio"
  :height="svgHeight"
  :viewBox="'0 0 ' + 100 * ratio + ' ' + svgHeight"
  class="line-graph">
  <!-- ... -->
</svg>
```
`width`と`height`は、画面上に表示されるサイズを表します。

`viewBox`はどこからどこまでを切り取って詰め込むかを表し、`viewBox="x y width height"`のように4つの値を指定します。
x: viewBox左上のx座標
y: viewBox左上のy座標
width: viewBoxの幅
height: viewBoxの高さ

今回は0~100のデータを扱っていますが、そのままでは小さすぎるので3倍(`ratio: 3`)にして表示させます。高さは、グラフの点の数に合わせて計算して出したいと思います。
<img width="339" src="https://qiita-image-store.s3.amazonaws.com/0/204256/02cd97c1-6f68-915b-2f06-e9d8538cb905.png">

```JavaScript:JavaScript｜25行目~
svgHeight(){
  return (this.all_data[0].scores.length - 1) * this.row_height
}
```
`all_data`1番目の`scores`のデータの数を参照して計算し、この `:height="svgHeight"` `:viewBox="'0 0 ' + 100 * ratio + ' ' + svgHeight"` 2つの値を入れました。

## 1本の折れ線グラフをグループ化し、繰り返す

<img width="340" alt="vue-line-graph-group1.png" src="https://qiita-image-store.s3.amazonaws.com/0/204256/c231c696-7b8f-9b15-894e-7a0a81ef5953.png">

```HTML:HTML｜6行目~
<g v-for="data in all_data" :class="'type-' + data.type">
  <!-- ... -->
</g>
```
SVGのg要素は子要素を束ねるもので、いわゆる「グループ化」です。画像のような1本の折れ線グラフを1つのグループとして、`v-for`を活用し、4本分繰り返してもらいます。
`:class="'type-' + data.type"`とすることで、1本ずつに`.type-a`、`.type-b`といったそれぞれ異なるclassを付与し、色を変えています。

## 間の線と点をグループ化し、繰り返す
<img width="338" alt="vue-line-graph-group2.png" src="https://qiita-image-store.s3.amazonaws.com/0/204256/77d5ae5e-6c29-4690-891c-8735d17dc1af.png">

```HTML:HTML｜7行目~
<g v-for="(score, index) in data.scores" :key="index">
  <line v-if="index != 0"
    :x1="data.scores[index - 1] * ratio"
    :y1="(index - 1) * row_height"
    :x2="score * ratio"
    :y2="index * row_height"/>
  <circle
    :cx="score * ratio"
    :cy="index * row_height"/>
</g>
```

1本のグラフの中で間を結ぶ線（line）と点（circle）をグループ化し、データの数だけ繰り返します。
<img width="300" src="https://qiita-image-store.s3.amazonaws.com/0/204256/c11c1c9b-617a-f26a-930f-15cfab1d841c.png">
line要素は、始点と終点の座標を指定することで描画できます。この場合、始点は1つ前の点の位置なので、`index - 1`でx座標・y座標それぞれの値を求めています。`v-for`で`index`を渡すことで、自身が何番目のデータかを知ることができます。
また、1つ目のlineは不要なので`v-if="index != 0"`で描画しないようにします。

circle要素は、円の中心の座標と半径を指定することで描画できます。半径rはCSSの方で指定しました。

この場合、「`x2`=`cx`」「`y2`=`cy`」となります。

## まとめ

**「各属性に適切な座標を入れれば描画できる」**という当たり前のことに気がつけたので、大きな収穫です。それほど時間をかけずにグラフを作成することができました。