2015年12月13日日曜日

パイチャートのSVG仕立て 〜アニメーションを添えて〜

Internet Explorer 50.86% Chrome 31.12% Firefox 11.28% Safari 5.01% Opera 1.28% other 0.45%
2015年10月の世界ブラウザシェア
出典: https://www.netmarketshare.com/

これはSVG Advent Calendar 2015の13日目の記事です。

お仕事でも個人的なものでも構いませんが、ウェブサイトにグラフを表示したいと思ったこと、またはそういう要望を受けたことはありませんか?
私はありまます。ちょうど今もそうだったりします。まぁ、どちらもお仕事でなんですけど。
数字をビジュアル的に見せられると中身のうすっぺらいプレゼンでも妙に説得力があったりしますよね。

ウェブサイトにグラフを表示する手段のひとつとしてSVGがあります。Retinaなどの高密度ディスプレイでもキレイに表示でき、なおかつJavaScriptからグリグリ動かすことができる、優れものです。
ここでは数あるグラフの中からパイチャート(円グラフ)に絞って、最初にこれをライブラリを使わずスクラッチで描く方法をご紹介します。お題目は冒頭に示した2015年10月の世界ブラウザシェア。これをつくります。
後半ではこのパイチャートにアニメーションを付けて見栄えを良くしてみます。
対象ブラウザはChrome、Firefox、Safari、Operaの各最新版とIE9以上とします。

この記事で扱うこと

  • SVGをhtmlにインラインで書く方法
  • SVGをJavaScriptで書く方法
  • SVGをアニメーションさせる方法

この記事で扱わないこと

  • グラフライブラリの使い方
  • ブログ書いたの1年半ぶりとかいう、この体たらくについて

SVGで円を描いてみよう

まずは肩ならしに円を描いてみましょう。
SVGには基本的な図形を描くための要素が用意されています。円はcircle要素を使って簡単に描くことができます。
具体的にはこんな感じです。

SVGタグについているxmlnsやversionは省略可能です。widthとheightはSVG領域のサイズですね。
この中にcircle要素を定義します。cxとcyは円の中心座標です。SVG領域が300px*300pxですので、この場合はちょうど領域の中心でもあります。rは円の半径、fillは円を塗りつぶす色です。
ちなみに3行目はラベル要素です。
このスニペットをhtmlに貼り付けてブラウザで開くと、以下のように描画されます。

Internet Explorer 100%
2015年10月の世界ブラウザシェア
出典: https://www.netmarketshare.com/

このように、円を描くのは非常に簡単です。
ちなみに15年ぐらい前に実際にこのグラフにかなり近い状況がありましたけど(笑)、あえてこんなラベルをつけたのには意味があります。circle要素では、今回のお題目であるパイチャートは描けないのです。半円さえも描けません。

SVGで円弧を描いてみよう

パイチャートを描くには角度を指定できる円弧(アーク)が必要になってきます。

安心してください、円弧はarc要素を使って簡単に描くことが...できませんよ!
SVGには円弧を描く要素は提供されていません。arc要素はウソです。

SVGにはpathという、任意の図形を描くための汎用要素があります。円弧はそれを使って描くしかありません。
こんな感じです。

2行目でpath要素を使っています。
どうでしょう、安心なんか、できないですね!パッと見では全然意味が分からない。
ちなみに実際に表示させてみるとこんな感じになります。

Internet Explorer 50.86%
2015年10月の世界ブラウザシェア(IEのみ)
出典: https://www.netmarketshare.com/

path要素ではd属性というもので図形を定義するのですが、それはこちらの「svg要素の基本的な使い方まとめ」で詳しく解説されていますので参考になさってください。私はpath要素に限らずSVGのほぼ全てをこちらのサイトで学びました。
ただ、たとえd属性の書式を理解したとしても、これを手書きするのは人間の所業ではありません。そのため大抵のグラフライブラリには円弧を描く機能が提供されているはずです。
今回はスクラッチで描く方法のご紹介なので、簡易な円弧専用のd属性書き出し関数を作ってみました。

IEのシェアは50.86%だそうなので、角度に換算すると183.096度になります。11行目の例にあるような呼び出し方をすると望みのd属性が得られます。

"M 150,0 A 150,150 0 1,1 141.8986347236027,299.7810664959306 L 150,150 Z"

あとは他のブラウザの分も同様に、シェアを角度に換算して、またこの関数を呼んで、結果をコピペして、

...って、やりませんよ!
ここまできたら、d属性だけでなく全てJavaScriptから動的に生成したほうが良いでしょう。

JavaScriptでパイチャートを描いてみよう

htmlにはSVGタグだけを記述しておき、JavaScriptで円弧を生成してそこにappendChildする、という方法でパイチャートを描いてみます。
前述のd属性出力関数を使用して円弧を生成する、パイチャート描画関数を作成してみました。

13行目のforEachは、あらかじめ割合の分母を知っておきたいためにループして値を合算しています。
17行目でpath要素をcreateElementNS(createElementではないので注意)で生成し、その下でd属性をはじめ必要な属性を設定、23行目でsvg要素にappendChildしています。これを円弧リストの数分繰り返します。
この関数は以下のように呼び出すことができます。

できあがったパイチャートはこんな感じになります。
マウスを置くとブラウザ名と割合が表示されるようにしてありますが、面倒なのでSVGとは直接関係ないので説明は省略します。ソースコードだけでよろしければ記事末にリンクしておきますのでご覧ください。

Internet Explorer 50.86% Chrome 31.12% Firefox 11.28% Safari 5.01% Opera 1.28% other 0.45%
2015年10月の世界ブラウザシェア
出典: https://www.netmarketshare.com/

さらに真ん中に小さい円を追加してあげると、ドーナツチャートもできますね!(こういうインチキっぽい方法ではなくpath要素を駆使してちゃんと円環を描くこともできます)

2015年10月の 世界ブラウザシェア Internet Explorer 50.86% Chrome 31.12% Firefox 11.28% Safari 5.01% Opera 1.28% other 0.45%
出典: https://www.netmarketshare.com/

アニメーションをつけてみよう

ここからは余興です。

グラフはただそれだけでも説得力がありますが、さらにアニメーションの演出を加えて見る者を完全に信じこませましょう。
アニメーションもスクラッチで書いていると本題を見失うので、今回はVelocity.jsの助けを借りることにします。Snap.svgでも良いと思います。

まずは、この怪しげなボタンを押してみてください。

Internet Explorer 辛うじて過半数維持! Internet Explorer 50.86% Chrome 31.12% Firefox 11.28% Safari 5.01% Opera 1.28% other 0.45%
出典: https://www.netmarketshare.com/

選挙風になっているのは特に意味はないですが、このパイチャートを見て分かるのは、IEが50%を切るのは時間の問題だ、といことです。それをアニメーションを使って、より強調してみました。ちなみにこのデータにEdgeは含まれていませんが、もしかしたらotherとして計上されているのかもしれません(それだと少なすぎるかな?)。

さて、アニメーションの方法ですが、考え方はとてもシンプルです。
すでに円弧を描くpathのd属性を生成する関数をご紹介しましたが、これを少しずつ角度を増やしながら何度も呼び出して、ひたすらd属性を書き換えてやります。今回はIE以外の円弧を反時計回りで描く必要があるので、関数を少し改造しました。

引数に方向fを追加しています。1が時計回り、0が反時計回りです。パイチャートは時計回りで描画することが多いので、普段は固定値にしておいて問題ないと思います。

さて、Velocity.jsは本来は要素のtransform属性を操作して滑らかなアニメーションを実現させるライブラリですが、任意の値を指定時間かけて徐々に変化させるという機能も実装されています。今回徐々に変化させたいのはpathのd属性なので、この機能を利用してd属性を変化させていく処理を書きます。

4行目のtweenというのは任意の値を指定するための仮の属性です。180.096は最終的なIEの角度です。値は暗黙的に0スタートなので、このように指定することで「0度から180.096度まで徐々に変化させよ」という意味になります。
6行目のdurationはアニメーション時間です。この場合は「6秒かけて徐々に変化させよ」という意味になります。
7行目のeasingはいわゆるイージング、変化のさせ方です。バウンドする効果が欲しかったのでeaseOutBounceを指定しています(実際にはVelocity.jsにeaseOutBounceはデフォルトでは実装されていません。こちらに関しては記事末のソースコードのリンクをご覧ください)。 8行目のprogressは値が変化する度に呼び出される関数です。この関数の5番目の引数tがその時点での値となります。つまりこの場合t=角度であり、これを使ってd属性を生成し、書き換えてやります。

IE以外のブラウザは反時計回りに描画したいので、progressで呼び出すd属性書き出し関数の第6引数に0を渡すようにします。また、ブラウザをひとつひとつ順番にアニメーションさせていくためPromiseを使います。Promiseを使わないと同時にアニメーションが開始してしまい、うまくいきません。詳しくは記事末にソースコードをリンクしておきますのでご覧ください。

さいごに

SVG、特にグラフに関していえば、実際はライブラリを使うんだとしても、素のSVGを知っているのと知らないのとでは、見えないところで差が出てきます。
素のSVGを知っていれば、何ができて何ができないかを予想することができます。
素のSVGを知らなければ、ライブラリの挙動がバグなのか仕様なのか切り分けることもできません。
ライブラリを完全なブラックボックスにせず、本当はスクラッチでも書けるけど(書く方法を知っているけど)工数を減らすためにライブラリを使う、というのが理想だと思います。

pie-chart.html
pie-chart.js