HTML
JavaScript
SVG
Sass
vue.js
SLOGANDay 12

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

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

Vueの公式ページにSVGで描かれたグラフがあったのでやってみました。

サンプル

vue-line-graph.png
https://jsfiddle.net/Wave7KN/5th9k53n/

SVGとは

SVGは画像フォーマットの1つでもあり、XMLを使用したマークアップ言語でもあり、なかにはHTMLやCSSと同じような感覚で書けるものもあります。例えば、このように:hoverも使えたりします。※サンプルの点の部分にマウスを乗せてみてください。
vue-line-graph-hover.png
また、HTML同様にJavaScriptで制御できるため、今回はVue.jsを使用して折れ線グラフを描いていきます。

サンプルデータを用意する

0~100の数値を5つずつ適当に用意しました。(ぜひランダム関数使ってください。)

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|2行目~
<svg :width="100 * ratio"
  :height="svgHeight"
  :viewBox="'0 0 ' + 100 * ratio + ' ' + svgHeight"
  class="line-graph">
  <!-- ... -->
</svg>

widthheightは、画面上に表示されるサイズを表します。

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

今回は0~100のデータを扱っていますが、そのままでは小さすぎるので3倍(ratio: 3)にして表示させます。高さは、グラフの点の数に合わせて計算して出したいと思います。

JavaScript|25行目~
svgHeight(){
  return (this.all_data[0].scores.length - 1) * this.row_height
}

all_data1番目のscoresのデータの数を参照して計算し、この :height="svgHeight" :viewBox="'0 0 ' + 100 * ratio + ' ' + svgHeight" 2つの値を入れました。

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

vue-line-graph-group1.png

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を付与し、色を変えています。

間の線と点をグループ化し、繰り返す

vue-line-graph-group2.png

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)をグループ化し、データの数だけ繰り返します。

line要素は、始点と終点の座標を指定することで描画できます。この場合、始点は1つ前の点の位置なので、index - 1でx座標・y座標それぞれの値を求めています。v-forindexを渡すことで、自身が何番目のデータかを知ることができます。
また、1つ目のlineは不要なのでv-if="index != 0"で描画しないようにします。

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

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

まとめ

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