SVGのpath要素を使って円グラフを描く方法を書いていく。
円グラフは扇形の集まりなので、まず扇形を描く方法を知る必要がある。扇形を描くためにはSVGのpath要素を使う。(path要素について分からない方はMDNのチュートリアルのPath項目を一読することをオススメする。)
Path要素で扇形を描く手順
扇形を描くために必要なpath要素のコマンドは「M」と「L」と「A」と「Z」である。
Mは”Move to”の略で、指定した座標の位置へ移動する。まずこのコマンドで、円の中心点の座標を指定する。
Lは”Line to”の略で、現在位置の座標から指定した座標へ線を引く。このコマンドで、円の中心点から円弧の描き始めの点、つまり扇形の一つの半径を描く。このコマンドに指定する引数は、円弧の描き始めの座標である。
Aはarc要素で、円弧を描く。このコマンドが一番重要である。半径や円弧の描き終わりの座標など引数に色々と指定しないといけないが、後に詳しく説明する。
Zは”Close path”というコマンドで、現在の座標から最初に指定した座標まで線を引く。このコマンドで円弧の描き終わりの点から中心点までの線、つまり扇形のもう一つの半径を描く。
コマンドはpath要素のd属性にて使う。以下具体例を示す。
<svg xmlns="http://www.w3.org/2000/svg" width="300px" height="300px">
<path fill="#6d5450" d="M 150 150
L 150 0
A 150 150 0 0 1 300 150
Z" />
</svg>
下はこのコマンドの動きを目で追った画像である。
Aコマンドは以下の7つの引数を指定する。
rx ry x-axis-rotation large-arc-flag sweep-flag x y
第一引数はx軸の半径、第二引数はy軸の半径で、楕円を描かないのであれば同じ値を指定する。第三引数は円弧の回転度を指定できるが、ここでは必要ないので0を指定する。
第四引数と第五引数を飛ばして先に第六引数と第七引数を説明する。第六引数は円弧の描き終わりのx座標、第七引数はy座標である。
Aコマンドは現在位置の点と第六引数と第七引数で指定した座標の点を繋いで円弧を描くが、下の画像のように2つの点の繋ぎ方には4種類あり、どの円弧を描くか指定しないといけない。そこで、第四引数と第五引数を使う。
http://www.hcn.zaq.ne.jp/___/SVG11-2nd/paths.htmlより引用
第四引数は円弧が180度以上かどうかを指定する。180度より小さければ0、大きければ1を指定する。ちなみに180度ちょうどはどちらでもよい。
第五引数は2つの点を反時計回りで繋ぐか時計回りで繋ぐかの違いで、今回は時計回りで統一し常に1を指定する。
ここまで、path要素での扇形の描き方の概要を書いてきた。あくまでもpath要素はコマンドを用意してるだけで、角度によって変わる円弧の描き始めの点や円弧の描き終わりの点の座標は三角関数を使って自分で算出しなければいけない。
三角関数を使って座標を導く
ただ単に下の三角比の定義さえ分かっていればよくて、それをプログラムに落とし込めば座標は導ける。
この定義を円に当てはめると以下のようになる。今回は12時の方向を0度として時計回りに描くためこのようになる。
まず、円の中心Oからの距離XとYを三角関数で求める。座標は左上が(0, 0)なので、X’は円の中心のx座標にXを足せば導ける。Y’は円の中心のy座標にYを引けば導ける。このように半径と中心角さえ分かれば座標は導ける。扇形を描く際、半径はあらかじめ自分で決めるので、後は好きな角度を指定すればいい。具体的にJavaScriptで座標を求めるプログラムを書くとしたら以下のようになる。
//cxは中心点のx座標; //cyは中心点のy座標; //rは半径; //degreeは角度(度数法); function getCoordinate(cx, cy, r, degree) { var x = cx + r * Math.sin(degree / 180 * Math.PI); var y = cy - r * Math.cos(degree / 180 * Math.PI); return {x: x, y: y}; }
Math.sinやMath.cosの角度θは度数法ではなく弧度法で指定しなければいけない。弧度法は180度をπと表す。例えば1度の場合は「1 / 180 * π」、30度の場合は「30 / 180 * π」、270度の場合は「270 / 180 * π」で度数法から弧度法に変換できる。上の関数は自動で弧度法に変換してくれるが、度数法から弧度法に変換する方法は押さえておきたい。
さて、座標を出す方法がわかったので、path要素のコマンドに座標の数値を指定することで扇形を作れるようになった。ここで、SVGのpath要素で扇形を描くJavaScriptの関数を書いてみる。
//cxは円の中心のx座標; //cyは円の中心のy座標; //rは半径; //startDegreeは扇形の始まりの中心角(度数法); //finishDegreeは扇形の終わりの中心角(度数法); //backColorは扇形の色; //strokeColorは線の色; //strokeWidthは線の幅; function createFanShape(cx, cy, r, startDegree, finishDegree, backColor, strokeColor, strokeWidth) { //円弧の始まりの座標; var startX = cx + r * Math.sin(startDegree / 180 * Math.PI); var startY = cy - r * Math.cos(startDegree / 180 * Math.PI); //円弧の終わり座標; var finishX = cx + r * Math.sin(finishDegree / 180 * Math.PI); var finishY = cy - r * Math.cos(finishDegree / 180 * Math.PI); //扇形の角度が180度を超えているか; var largeArcFlag = (finishDegree - startDegree <= 180) ? 0 : 1; //扇形の中心点へ移動するコマンド; var move = 'M' + cx + ' ' + cy + ' '; //扇形の中心点から円弧の始まりまで線を結ぶコマンド; var line = 'L' + startX + ' ' + startY + ' '; //円弧を描くコマンド; var arc = 'A' + r + ' ' + r + ' ' + 0 + ' ' + largeArcFlag + ' ' + 1 + ' ' + finishX + ' ' + finishY + ' '; //円弧の終おわりから中心点を結ぶコマンド; var close = 'Z'; //path要素を作成; var NS = 'http://www.w3.org/2000/svg'; var path = document.createElementNS(NS, 'path'); path.setAttributeNS(null, 'd', move + line + arc + close); path.setAttributeNS(null, 'fill', backColor); path.setAttributeNS(null, 'stroke', strokeColor); path.setAttributeNS(null, 'stroke-width', strokeWidth); return path; }
この関数はpath要素を返す。このpath要素をsvg要素に追加すると扇形が表示される。この関数は12時の方角を始まりとする時計回りの円を想定している。例えば、1時から5時の扇形を作りたい場合、関数の第四引数と第五引数に次のように指定する。
createFanShape(100, 100, 100, 30, 150, '#6d5450');
次に、実際にこの扇形を繋げて簡単な円グラフを作ってみる。
円グラフを作ってみる
円グラフは扇形を繋げるだけで描ける。まず最初に円の大きさを決める。円は一周360度なのでそれを100%ととして後はグラフの割合で扇形の角度を決めていけばいい。例えば下のようなネタで円グラフを作ってみる。
Q.神はいると思う?
- インターネットで見た 82%
- いない 13%
- わからない 5%
See the Pen MYmJOR by shigure (@webkatu) on CodePen.
この円グラフは、3つの扇形をつなげている。この円グラフは単に、createFanShapeという扇形を作る関数に半径や角度などの情報を引数で与えて、それを繰り返しているだけだ。
円グラフは、テキストも添える必要がある。ここでは、createPieChartTextという関数でテキストを置いている(正確にはtext要素を作っている。)。この関数に扇形の情報を渡すと、ちょうど扇形の円弧上の中心にテキストを配置するtext要素を作成する。円弧と重なると文字が見づらいため、半径を変えてちょうどいい位置を指定する。ここでは、円の半径に0.7をかけた値を引数で渡している。つまり、円グラフの中にもう一つ円があり、その円周上にテキストが配置されているようなものだ。下を参考にしてほしい。
See the Pen qEjYOa by shigure (@webkatu) on CodePen.
テキストは中央に配置するためはみ出す可能性がある。テキストの配置に関してはもっと良い方法があるかもしれない。
このように、SVGで扇形の作り方が分かれば簡単な円グラフも描けるようになる。円グラフを描くためのライブラリは色々とあるが、SVGで直接描く方法を知っておくのは悪くないと思う。記事の最後に、createFanShapeとcreatePieChartTextの関数を載せておくので参考にしてほしい。
計画表を作るアプリ
唐突だが、今回の記事を取り入れたSVGで描く計画表アプリを作ったのでもし機会があれば使ってほしい。LifeLeaderというWebアプリだ。
記事で使った関数
//cxは円の中心のx座標; //cyは円の中心のy座標; //rは半径; //startDegreeは扇形の始まりの中心角(度数法); //finishDegreeは扇形の終わりの中心角(度数法); //backColorは扇形の色; //strokeColorは線の色; //strokeWidthは線の幅; function createFanShape(cx, cy, r, startDegree, finishDegree, backColor, strokeColor, strokeWidth) { //円弧の始まりの座標; var startX = cx + r * Math.sin(startDegree / 180 * Math.PI); var startY = cy - r * Math.cos(startDegree / 180 * Math.PI); //円弧の終わり座標; var finishX = cx + r * Math.sin(finishDegree / 180 * Math.PI); var finishY = cy - r * Math.cos(finishDegree / 180 * Math.PI); //扇形の角度が180度を超えているか; var largeArcFlag = (finishDegree - startDegree <= 180) ? 0 : 1; //扇形の中心点へ移動するコマンド; var move = 'M' + cx + ' ' + cy + ' '; //扇形の中心点から円弧の始まりまで線を結ぶコマンド; var line = 'L' + startX + ' ' + startY + ' '; //円弧を描くコマンド; var arc = 'A' + r + ' ' + r + ' ' + 0 + ' ' + largeArcFlag + ' ' + 1 + ' ' + finishX + ' ' + finishY + ' '; //円弧の終おわりから中心点を結ぶコマンド; var close = 'Z'; //path要素を作成; var NS = 'http://www.w3.org/2000/svg'; var path = document.createElementNS(NS, 'path'); path.setAttributeNS(null, 'd', move + line + arc + close); path.setAttributeNS(null, 'fill', backColor); path.setAttributeNS(null, 'stroke', strokeColor); path.setAttributeNS(null, 'stroke-width', strokeWidth); return path; }
//cxは円の中心のx座標; //cyは円の中心のy座標; //rは半径, 扇形の半径 * xを指定する; //円グラフの内側にテキストを配置するには 0 < x < 1; //円グラフの外側にテキストを配置するには 1 < x; //startDegreeは扇形の始まりの中心角(度数法); //finishDegreeは扇形の終わりの中心角(度数法); //backColorは扇形の色; //fontColorは文字色; //textは配置するテキスト内容; function createPieChartText(cx, cy, r, startDegree, finishDegree, fontColor, text) { //textを表示する角度(扇形の真ん中); var degree = (startDegree + finishDegree) / 2; //textの座標; var textX = cx + r * Math.sin(degree / 180 * Math.PI); var textY = cy - r * Math.cos(degree / 180 * Math.PI); //text要素を作成; var NS = 'http://www.w3.org/2000/svg'; var svgText = document.createElementNS(NS, 'text'); svgText.textContent = text; svgText.setAttributeNS(null, 'x', textX); svgText.setAttributeNS(null, 'y', textY); svgText.setAttributeNS(null, 'fill', fontColor); svgText.setAttributeNS(null, 'dominant-baseline', 'middle'); svgText.setAttributeNS(null, 'text-anchor', 'middle'); return svgText; }