技術ブログ | 株式会社アイプランニング IPlanning corporation

アイプランニング社員が調査したこと、学んでいることが具体的にどんなものなのかを披露します。 Here is what the IPlanning corp employees surveyed and what they learned.

SVGとVue.jsでグラフコンポーネントを作成する

一年目の若手エンジニアに、データビジュアライゼーションのトレーニングを兼ねて、SVGでグラフを作ってもらいました。

作成物概要

f:id:iplcojp:20180326113827p:plain

総務省統計局の平成29年11月時点の人口データを元に各年齢ごとの人口を男女別にグラフを作成しました。

横軸に年齢(5歳ごとの分類)、縦軸に人数(単位:千人)で男女別で表示しています。
男性を青、女性を赤、合計を灰色に設定しています。

また、縦軸最大値と人口の最大値をあわせたり、グラフの大きさをウィンドウサイズ丁度に表示されるように作成しました。

SVGの基本的な使い方

SVGタグの中にSVG要素を書いていきます。具体的には以下の通りです。

<svg viewbox="0 0 400 500" width=400 height=500>
  <!-- ここに書きたい物を書いていく -->
</svg>

viewboxとは?

描画領域を"x y width height"で指定する事が出来ます。

xとyで描画位置を指定(描画領域の左上部分というイメージ)、widthとheightで長さを決定します。

SVG内に描画出来る要素

  • circle(円)
    • 中心座標(cx,cy)と半径rを指定して作成する
  • ellipse(楕円)
    • 中心座標(cx,cy)とx方向への長さrx、y方向への長さryを指定して作成する
  • rect(四角形)
    • 座標x,yから横にwidth、縦にheight分伸ばして作成する
      • rx,ry(楕円と同じような考え)で指定も可能
  • line(直線)
    • (x1,y1)と(x2,y2)の2つの座標から直線を作成する
  • polyline(連続した直線)
    • pointsを指定して作成。 "x1 y1 x2 y2 ... xn yn"
      • points内の要素が偶数になるよう注意、要素が奇数になると最後のy座標の値がなく、バグの原因になる。
  • polygon(多角形)
    • polylineと同様pointsを指定して作成
      • 始点と終点座標を一緒にする必要はない(始点と終点に直線が描画され多角形になる)
  • text(テキスト)
    • 座標(x,y)にフォント名(font-family)と文字の大きさ(font-size)を指定して作成
      • text-anchorで文字の揃え方(左、右、中央揃え)やdominant-baselineで高さ位置の指定なども可能
  • path(パス、曲線)
    • 始点と終点を設定する。その間に制御点を加えることでベジエ曲線を描く事が可能
      • 大体の図形はpathで描けるが、複雑になりやすいため簡単な図形は上記の要素を書いた方がいい

主要な要素は上記の通りですが、他にも様々な要素を使うことが出来ます。詳しくはMDNのリファレンスをチェックして下さい。

注意事項

  • svg要素内では、html要素は基本的に使えなくなります
    • divでグループ分け出来ないため、g要素でグループ分けを行います。
      • title属性を指定して、ツールチップの表示を試みましたが、不可能でした。
    • HTML要素をどうしても使いたい場合は、foreignObject要素内で利用します。
      • 折り返しテキストを表現する用途でよく使われます。参考サイト

作成物

https://iplanning.heteml.net/2018-svg/

  • 上記ページで閲覧する事ができます。

グラフ描画部の実装ポイント

棒グラフの描画部は下記のようになっています。

<g v-for="(data, index) in datas" :key="index">
  <rect class="totalPopulation" :x="setGraphX(index)" :y="setGraphY(data[0])" :width="rectScaleWidth()" :height="rectScaleHeight(data[0])"></rect>
  <rect class="malePopulation" :x="setGraphX(index)" :y="setGraphY(data[1])" :width="rectScaleWidth()" :height="rectScaleHeight(data[1])"></rect>
  <rect class="femalePopulation" :key="index" :x="setGraphX(index)" :y="setGraphY(data[2])" :width="rectScaleWidth()" :height="rectScaleHeight(data[2])"></rect>
  <text class="axisTextX" v-if="textDataX(index)" :x="setGraphX(index)" :y="graphHeight">{{textDataX(index)}}</text>
</g>

x, y, width, heightを計算する部分は、methods内に定義します。

methods: {
  setGraphX (index) {
    return (index * this.graphWidth / this.datas.length)
  },
  setGraphY (data) {
    return this.graphHeight - data / this.maxData * this.graphHeight
  },
  rectScaleWidth () {
    return this.graphWidth / this.datas.length
  },
  // グラフ横幅
  rectScaleHeight (data) {
    return data / this.maxData * this.graphHeight
  },  
  ...
}

maxDataは表示データの最大値で、これをもとにデータ表示範囲を自動的に調整しています。

おまけ

f:id:iplcojp:20180327173540p:plain

  • 作成物内にポケモンステータスをグラフに示したものも合わせて作成しました。
    • ポケモン種族値から2種類データを選択し、x,y軸に対応させて表示してあります。
    • タイプごとの表示やマウスカーソルを当てたときにデータが表示される機能などを実装しました。
    • 表示データはpokedexの公開データ(MIT)を利用しました。

おまけの実装ポイント

グラフ表示に加え、データの絞込や軸ごとの表示データの選択機能も付けました。 f:id:iplcojp:20180327175330p:plain

グラフ表示部は下記のような実装になっています。

<g v-for="data in datas">
  <g v-for="type in data.type">
    <g v-if="selectType === 'all'|selectType === type|selectType2 === type" @mouseenter="pokemonView(data)">
      <circle :class="type" :cx="setGraphX(data)" :cy="setGraphY(data)" :r="circleScale" :name="data.name" />
    </g>
  </g>
</g>

計算部は下記の通りです。マウスカーソルを重ねた時には、該当するポケモンの名称やパラメータを画面下部に表示します。

methods: {
  setGraphX (data) {
    return data[this.graphStats.x] * this.graphWidth / this.maxX
  },
  setGraphY (data) {
    return this.graphHeight - data[this.graphStats.y] * this.graphHeight / this.maxY
  },
  pokemonView (data) {
    this.viewStr = '名前: ' + data.name + ' ' + this.status[this.graphStats.x] + ' : ' + data[this.graphStats.x] + ' ' + this.status[this.graphStats.y] + ': ' + data[this.graphStats.y]
    return
  }
},

終わりに

特別なグラフライブラリを使用せず、Vue.jsとSVGのみでグラフを構築しました。

絞込UIに関してもVue.jsで実装できるため、UIとグラフの連携もスムーズになるなどの利点があるかと思います。

参考ページ

developer.mozilla.org