SVGとVue.jsでグラフコンポーネントを作成する
一年目の若手エンジニアに、データビジュアライゼーションのトレーニングを兼ねて、SVGでグラフを作ってもらいました。
作成物概要
総務省統計局の平成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(楕円と同じような考え)で指定も可能
- 座標x,yから横にwidth、縦にheight分伸ばして作成する
- line(直線)
- (x1,y1)と(x2,y2)の2つの座標から直線を作成する
- polyline(連続した直線)
- pointsを指定して作成。 "x1 y1 x2 y2 ... xn yn"
- points内の要素が偶数になるよう注意、要素が奇数になると最後のy座標の値がなく、バグの原因になる。
- pointsを指定して作成。 "x1 y1 x2 y2 ... xn yn"
- polygon(多角形)
- polylineと同様pointsを指定して作成
- 始点と終点座標を一緒にする必要はない(始点と終点に直線が描画され多角形になる)
- polylineと同様pointsを指定して作成
- text(テキスト)
- 座標(x,y)にフォント名(font-family)と文字の大きさ(font-size)を指定して作成
- text-anchorで文字の揃え方(左、右、中央揃え)やdominant-baselineで高さ位置の指定なども可能
- 座標(x,y)にフォント名(font-family)と文字の大きさ(font-size)を指定して作成
- path(パス、曲線)
主要な要素は上記の通りですが、他にも様々な要素を使うことが出来ます。詳しくはMDNのリファレンスをチェックして下さい。
注意事項
- svg要素内では、html要素は基本的に使えなくなります
作成物
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は表示データの最大値で、これをもとにデータ表示範囲を自動的に調整しています。
おまけ
- 作成物内にポケモンステータスをグラフに示したものも合わせて作成しました。
おまけの実装ポイント
グラフ表示に加え、データの絞込や軸ごとの表示データの選択機能も付けました。
グラフ表示部は下記のような実装になっています。
<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とグラフの連携もスムーズになるなどの利点があるかと思います。