svg要素の基本的な使い方まとめ

Written by defghi1977@xboxLIVE.この文書は全てテキストエディタで作成しています.えーと,そりゃもうゴリゴリと.
Powered by D3.js.

20.D3.jsによるグラフの描画

svgを利用するにあたって,まず思い当たることとしてはグラフの描画への応用であろう.簡単なものであればsvgdomを直接操作するだけで十分対応できるが,ここではD3.js.を用いた簡単なサンプルを紹介する.D3.jsは元来データ指向のドキュメント生成フレームワークであるが,svgの出力にも対応しており,グラフの生成を簡潔に行える.なお本ドキュメントではバージョンはv2.9.2を使用した.

D3.jsについて

D3.js(Data-Driven Documents)はその名の通りデータ駆動のドキュメント生成を行うjavascriptフレームワークであり,グラフ等によるデータの視覚化(データビジュアライゼーション)を簡潔に実現することが出来る.データの視覚化とは,そのままでは意味を見出しにくいデータの塊を,何らかの方法で目に見える形に変換し,その中に秘められた重要点や問題点を直感的に理解できるようにする事を指す.等高線を例に取れば,線の密度に着目することで,単なる標高値の羅列ではイメージしにくい「勾配」の情報を読み取ることができるだろう.このようにデータにはその目的により適切な視覚化手法が存在する

棒グラフや円グラフは古典的ながら有効な数値の視覚化手法の一つであるし,天気図(気圧),音楽の五線譜(音程),フローチャート(手続き),マインドマップ(思考),UML(相関)なども広義の視覚化と言える.また,これから生まれる新しいデータ形式には,新たな視覚化手法が編み出されることであろう.D3.jsはこのデータの視覚化を強力にサポートするメソッド・テンプレートを多数提供しており,単なるグラフに留まらない,非常に柔軟なチャートデザインを実現することが出来る.

公式サイトのサンプルを見ると判るように,一見htmlだけでは実現できそうにないような非常に高度な処理を行える一方,使いこなすためには広大なapiやjavascriptの文法に深く精通しておく必要がある.またsvgをサポートしていると言っても,使用するにはsvgそのものの知識が問われるため,他のライブラリと比較してかなりハードルは高い.

従ってここではD3.jsの入門として,実際に簡単な棒グラフを作ってみることとし,それを手がかりにD3.jsで実装可能なグラフのサンプルを提示するのみに留める.D3.jsの入口に過ぎないが,これだけでも相当役に立つはずだ.表の自動生成やアニメーションを利用したインタラクティブなグラフの作成等,もっとD3.jsの機能を知りたい場合は公式サイトのチュートリアルを参考にして欲しい(但しチュートリアルで使用しているモジュールのバージョンが低い場合もあるので注意が必要だ).

※なお筆者も広大なD3.jsの世界につい先程足を踏み入れた初心者なので,間違いがあったらゴメンナサイ.

D3.jsことはじめ・svgによる図形の描画

D3.jsはhtml文書で利用されることを前提としている.まずはじめに公式サイトからd3.jsを入手し,head要素にて次の宣言をしておこう.
<script type="text/javascript" src="d3.v2.min.js"></script>
これで,D3.jsを利用する準備は整った.

D3.jsでsvgを描画するには,まずsvg要素に相当するオブジェクトを取得することとなる.例を示す.

d3はD3.jsの名前空間を表すオブジェクトである.ここでd3.selectメソッドによりsvg要素を配置するdiv要素を検索する(※body要素でも構わない).div要素に相当するオブジェクトが取得できたら,今度はappendメソッドによりsvg要素を追加する.

D3.jsはjQueryの影響を受けているため,要素の検索文字列やメソッドチェーン等にその面影を見ることが出来る.が,attrメソッドのように要素の属性値は一つづつ設定する必要があるなど微妙な点で使い勝手が異なる点に注意する(この理由は後ほど判る).また,jQueryに存在したdocument.readyイベントに相当する機能が存在しないため,window.onload等を起点にスクリプトを実行することとなる.

さて,ここで得られたsvgのオブジェクトに更にappendメソッドを実行してcircle要素を追加してみよう.名前空間の指定はd3.jsが勝手に行なってくれるので,考慮する必要はない.

circle要素のみならず,その他の図形要素もこのやり方で生成することが出来る.このように,svgdomを直接叩くよりも簡潔にコードを記述することが出来た.これだけでも頑張ればグラフを記述することができるが,まだこれはD3.jsの表層の部分に過ぎない.ここからデータと図形の描画処理とを結びつけるのがD3.jsの醍醐味である.だがその前に,scaleオブジェクトについて説明することとする.

scaleオブジェクトによる値の変換

scaleオブジェクトはスケール変換を表すもので,このオブジェクトを用いるとある値の範囲を別の範囲にマッピングする事が出来る.例を示す.ここではscale.linearオブジェクトで区間[0,100](domain:定義域)を区間[10,180](range:値域)にマッピングしている.これを図に表してみよう.なお,y座標は上から下に向かって値が大きくなる点に注意する.

circle要素の中心のx座標を75とし,y座標をscale(75)とすると,丁度直線上に中心が配置された.即ちscale.linearは線形な値の変換を行うオブジェクト(関数)であることが分かった.

ここで,棒グラフを考えてみよう.棒グラフではグラフの値を描画範囲の高さに変換することで図形の描画を行う.つまり,scale.linearをデータ値と高さの変換に利用出来るはずだ.実際にやってみよう.矩形の最大の高さを180として,ちょっとずつ値を変えて並べてみよう.描画の基準点を下から10pxの部分となるようにy属性とheight属性の値を算出する.

このように期待通り階段状のグラフィックが現れた.ここまでで大分グラフに近い形となってきた.後はここにデータを挿入する事が出来れば本当のグラフになるはずだ.

attrメソッドの動作

さて,ここまで黙ってきたが,selectメソッドやselectAllメソッド,appendメソッドで得られたオブジェクトをselectionオブジェクトと呼ぶ.selectメソッドでは条件に合致した最初の,selectAllでは条件に合致した全てのdomオブジェクトを内部に保持する.このオブジェクトのattrメソッドを使って属性値を設定するのだが,実はこのメソッドには関数を指定することが出来る.

また,selectAllを用いた場合は,dataメソッドでデータの配列をselectionオブジェクトに格納して,データの一括設定が可能だ.dataメソッドにデータの配列を渡すと,attrメソッドに指定した関数の引数としてデータの中身を取得できる.下の例では事前に2つのrectを定義して,その属性値を一括設定している.

これを更に変形しよう.selectionオブジェクトにはデータの配列の数だけ図形要素を自動的に生成するやり方がある.まず,selectAllメソッドを使って空のselectionオブジェクトを生成しよう.このオブジェクトにdataを設定し,enterメソッドを実行すると,後続のappendメソッド(insertメソッド)で指定した要素がdataのサイズの分だけ要素が追加されるのだ.後続のattrメソッドの動作は先程の例と同じく,データの配列を参照して属性値を一括設定する.

ここで,データの定義と図形の生成ロジックに着目すると,dataメソッドを境界としてキレイに分離されていることが判る.つまり,データの構造のみでグラフィックが描かれていることとなる.これがD3.jsがデータ駆動文書ライブラリたるゆえんだ.(ループ構文を使ってもロジックとデータの分離ができるという意見はもっともだが,重要なのはループ構造が配列処理の中に隠れて極めてフラットな構造となっている点である.)

総括:棒グラフの作成

さて,ここまで辿りつけばグラフを作るのはそれほど難しくない.早速作ってみることとしよう.なおd3.maxメソッドはデータ配列から最大値を取得するユーティリティ関数である.

グラフの描画位置を調整するために縦方向と横方向のscaleオブジェクトを用いた.後はこのスケールをデータオブジェクトのscore値に適用し,棒の高さを得るだけだ.また,見栄えを良くするため見出しをtext要素で挿入した.テキストはtextメソッドで挿入するが,attrメソッドと同様にデータを参照することが出来る.

D3.jsでグラフを作るのは如何だったろうか?広大なapiを読み解くのは確かに面倒ではあるが,幸いなことに他のjavascriptライブラリと比較しても非常にドキュメントが充実しており,英語を読むことに抵抗がなければ非常に心強い.また,サンプルを弄ってみるのも,ループ構造が少ないためやりやすい.と言ったふうに最初の壁・動作するプログラムを作るところまで行き着いてしまえば,割と何とかなるので是非試してみて欲しい.

グラフのバリエーション

D3.jsの基本機能を使ったグラフのバリエーションを示す.もっと良い実装方法があるかもしれないので,あくまでもサンプルとして扱って欲しい.気がついた点としては変換元のデータの形状がグラフの描画処理の難易度を大きく左右することだ.グラフを描画する上で,データの形状として連想配列が良いのか,配列が良いのか検討すると良い.

なおここではデータを変数から取得しているが,D3.jsではjQueryと同様のd3.jsonメソッドをサポートしているので,外部からデータを取得することもできる.

この他にもアイディア次第で様々なグラフが描けるだろう.しかし,その都度グラフのプログラムを構築するというのは面倒であるから,適宜モジュール化しておくと良い.データとロジックが分離されているため,モジュール化は比較的容易だと思われる.

グラフのデータ軸と目盛の描画

いくら見栄えの良い図形が描けても,データを読み取るための目盛とデータ軸が存在しなければグラフとは呼べない.グラフのデータ軸を設定する場合,d3.svg.axisを用いると簡単に軸を描画することが出来る.

またグラフに目盛をつける場合は,d3.rangeメソッドを使うと便利だ.このメソッドは数値の配列を簡単に作ることが出来る.

D3.jsが提供するレイアウト

ここまでD3.jsの基本機能のみを用いてグラフを描いてきた.これだけでも十分利用価値があると理解できたであろう.だがD3.jsが真価を発揮するのは,以下に示すレイアウトテンプレート機能を利用した場合であろう.

レイアウトオブジェクトは様々なデータ構造を分析し,その形を配列に変換する.配列となったデータをselection.dataメソッドに渡して,先ほどのグラフと同様に図形要素を生成する.この時生成したデータには図形のレイアウトの情報が設定されているため,この内容を元に図形を配置することで見栄えの良い図を描くことが可能となる.

ヒストグラムレイアウト,扇形レイアウト,スタックレイアウトはグラフを描画する際に頻繁に利用することとなるだろう.次に示す5つのレイアウトは何れも階層構造をとるデータの表示に利用するレイアウトである.元となるデータが数値でなくともレイアウト処理を行うことが可能だ.

以下はそれ以外のレイアウトについて示す.なお最後の2つは非常に構造の理解が難しいので,実装する場合は注意が必要.

ここまで到達できたなら,後は実践するのみ.各種レイアウトを組み合わせたり,配置を変更したり,座標を変更したりと行うべき作業は多々あるが,最も重要なことはデータの中の「何を際立たせたいのか」だ.苦労しても使い途がなければ自己満足に過ぎないことを忘れないようにしたい.

補足)全てのsvg部品をd3.jsで構築すべきか

d3.jsは非常に高機能なフレームワークであるものの,svgの静的な構造までもd3.jsで記述しようするのはsvgが持つ簡潔さをjavascriptで再構成しているだけである.従って,意味なくソースコードが煩雑となることでかえってd3.jsのもつ良さを殺す結果となりかねない.

この問題はsvgをd3.jsで動的に生成する部分と固定的に利用する部分に分離することで解決する.例を示す.上で定義した内容を下のグラフから参照している.グラフの描画とスタイル指定とを明確に分離することで,グラフの動的構成部と見た目の制御のそれぞれが非常に判りやすくなったことだろう.

固定的なsvg定義部 filterの実行サンプル
<svg>
    <defs>
        <filter id="shadow" filterUnits="userSpaceOnUse" x="0" y="0" width="100%" height="100%">
            <feOffset in="SourceAlpha" dx="2" dy="2"/>
            <feMerge>
                <feMergeNode/>
                <feMergeNode in="SourceGraphic"/>
            </feMerge>
        </filter>
    </defs>
    <style>
        #useSvg svg{shape-rendering:crispEdges;}
        #useSvg rect{filter:url(#shadow);}
        #useSvg rect:nth-child(odd){fill:blue;}
        #useSvg rect:nth-child(even){fill:red;}
    </style>
    <text y="20" font-size="20">固定的なsvg定義部</text>
    <text y="40" fill="blue" font-family="sans-serif" font-size="20" filter="url(#shadow)">filterの実行サンプル</text>
</svg>