canvasタグを使って円グラフを描いてみよ〜

この記事はだいぶ前に書かれたものなので情報が古いかもしれません
HTML5にはcanvasタグというのがあって、これを使うと何かいろいろと図形が描けちゃったりするみたいなんですが、今回はそれを使って簡単な円グラフでも描いてみようかなと思います。

canvasタグがどんなものかについては、この辺を読んだりすると良いんじゃないかしら?

むしろ僕もものは試しに使ってみた感じなんで、毎度のことながらあまり詳しくは存じ上げてないです。

「とりあえずググれ。さすれば道は開かれん」

それがうちのサイトのコンセプトなもので…すんまそん。



とりあえずは円グラフを生成

canvasタグを使いこなすと見せかけて、ほぼjavascript頼りです。っていうかjavascript頼りです。しかもそのjavascriptすら他の人が公開してくれているサンプルをいただいてそれをちょこっといじらせてもらった程度の開発です。ぶっちゃけ僕の功績なんぞありゃしねえww

画像は使いません。canvasタグの……メソッドやプロパティって言うんですか? それらを使って何かそれらしい感じに円グラフを表現します。

というわけでさっそく怪盗ロワイヤルのごとくサンプルをいただきに行きましょう、ヒッヒッヒ。

今回参考にさせていただいた……ってか予告状もなしにいただきに参ったのはこちらのサイトです。この場を借りてお礼申し上げます。あばよとっつぁ〜ん。

正直なところ、上記のサイトさんに書いてあることをそのままやれば、それだけでもう円グラフを表示することはできます。とりあえずで円グラフを生成する任務はこれにて完了! と言ってしまっても別に問題はないです。

//円グラフを表示させるhtmlファイルの中身
<script type="text/javascript" src="betsuyakuchart.js"></script>
<script type="text/javascript">
  window.onload = function() {
    var bChart = new BetsuyakuChart("my-canvas", {"fillStyle": "#ff8800"});
    bChart.add("データ1", 120);
    bChart.add("データ2", 20);
    bChart.add("データ3", 10);
    bChart.add("データ4", 50);
    bChart.stroke();
  }
</script>
<canvas id="my-canvas" width="200" height="200"></canvas>

//betsuyakuchart.jsの中身
var BetsuyakuChart = function() {
  this.initialize.apply(this,arguments);
};
 
BetsuyakuChart.prototype.initialize = function(id, options) {
  this.canvas = document.getElementById(id);
  this.pList = [];
  this.sum     = 0;
  this.margin  = 5;
  this.options = {"strokeStyle": "#ffffff", "fillStyle": "#dddddd"};
  for (var i in options) {
    this.options[i] = options[i];
  }
};
 
BetsuyakuChart.prototype.add = function(key, value) {
  this.sum += value;
  var p = {"key":key,"value": value};
  this.pList.push(p);
};
 
BetsuyakuChart.prototype.stroke = function() {
  this.size   = {w: this.canvas.offsetWidth, h: this.canvas.offsetHeight};
  this.center = {x: this.size.w/2, y: this.size.h/2};
  this.radius = Math.min(this.size.w/2, this.size.h/2) - this.margin;
  this._oneStroke("fill");
  this._oneStroke("stroke");
  this._createText();
};
 
BetsuyakuChart.prototype._oneStroke = function(type) {
  var c = this.center;
  var context = this.canvas.getContext("2d");
  context[type + "Style"] = this.options[type + "Style"] || "#888888";
  context.moveTo(c.x, c.y);
  context.lineWidth = 2;
  var sa = 0;

  for (var i = 0; i < this.pList.length; i++) {
    var v = this.pList[i].value;
    var ea = Math.PI * 2 * v / this.sum;
    context.arc(c.x, c.y, this.radius, sa, sa + ea, 0);
    context.lineTo(c.x, c.y);
    sa += ea;
  }

  context[type]();
};
 
BetsuyakuChart.prototype._createText = function() {}

問題はないですが……まあさすがにそれではこの記事を書く意味もないので……もうちょっとだけ頑張ることにしましょう。

一応、余計な前置きをしておくと、今回はいただいたサンプルをいじっての挑戦であるのと、僕はcanvasタグの使い方をよく分かってないので、このやり方がベターかどうかは知りません。いつものことですが、よりイケてるやり方があれば教えてほしいっす。



二色を使って円グラフを生成

上記のサイトさんの円グラフは単色で表現されているのですが、それをちょっと二色で表現してみましょう。サイトさんの方でも「色も一色しか表示していませんが、この辺はCANVASのスクリプト操作に慣れれば結構簡単にできるのではないでしょうか」って言ってるし、オーケー、その言葉を信じて行ってみましょう。

最終目的地は↓な感じの円グラフです。



達成率とか進捗率とか正答率とか、まあ何でもいいんですが、表示したい率が0%だったら灰色一色の円になり、あとはパーセンテージ応じた部分を青色にすると、そういう感じです。あとこれはどうでもいいワンポイントなんですが、青の方をより目立たせるために円の半径を若干大きくしてます。

0%だと↓な感じってことですね。



やり方としては、最初に灰色の円を作ってしまい、パーセンテージが0じゃなかったら、その上から青色の弧を重ねるといった感じです。

ごちゃごちゃ説明読むのがめんどい! ってかどうせお前の書く説明なんぞ読んでも分からん!! という方は、一応できたソースを下の方に載せとくので、それコピってやってみてくださいな。



灰色の円を描く

まずは前菜、オードブルですね。

この灰色の円はパーセンテージがいくらであろうと必ず描かれるものなので、それ用のメソッドをbetsuyakuchart.jsの中に作成します。

BetsuyakuChart.prototype._defaultStroke = function(type) {
  var c = this.center;
  var context = this.canvas.getContext("2d");
  context.beginPath();
  context[type + "Style"] = this.options[type + "Style"] || "#888888";
  context.moveTo(c.x, c.y);
  context.lineWidth = 2;
  
  var sa = -90 / 360 * 2 * Math.PI;
  var ea = 270 / 360 * 2 * Math.PI;
  context.arc(c.x, c.y, this.radius, sa, ea, 0);
  context.lineTo(c.x, c.y);
  context[type]();
  context.closePath();
}

こんな感じですね。ざっくり言うと、saという変数に始点の位置、eaという変数に終点の位置を入れています。数学でラジアンとか習ったことがあるかと思うんですが、この入力は2πを360°として換算した値を入力してあげる必要があります。

何で-90°からスタートしてるのかってことなんですが、このcontext.arcというメソッドはx軸を0°として描画するらしいんですよ。ってことは、0°からスタートして一周させると、↓みたいになっちゃうんですね。



通常円グラフって、y軸の方を始点にするじゃないですか。だから-90%から初めています。

これで灰色の円を表示させる関数は完成ですので、BetsuyakuChart.prototype.strokeの中身でそれを呼び出すようにします。

BetsuyakuChart.prototype.stroke = function() {
  this.size   = {w: this.canvas.offsetWidth, h: this.canvas.offsetHeight};
  this.center = {x: this.size.w/2, y: this.size.h/2};
  this.radius = Math.min(this.size.w/2, this.size.h/2) - this.margin;
  
  this._defaultStroke("fill");
  this._defaultStroke("stroke");
  
  //this._createText();
};

これで常に灰色の円が表示されるようになりました。もしこの記事を読んでそうならなかった場合は、何かコピーし間違えたか、この記事に誤字脱字があるか、この記事が非常に分かり辛いとかなので、ご一報くだしゃれ。



重なりゆく青い円弧

んじゃ、メインディッシュです。

めんどい説明は置いといて、とりあえずサンプルにあるBetsuyakuChart.prototype._oneStrokeの方を、こんな感じに書き換えます。

BetsuyakuChart.prototype._oneStroke = function(type) {
  var c = this.center;
  var context = this.canvas.getContext("2d");
  context.beginPath();
  context[type + "Style"] = this.options[type + "Style"] || "#888888";
  context.moveTo(c.x, c.y);
  context.lineWidth = 2;
  
  this.radius = this.radius + 5;
  var sa = -90 / 360 * 2 * Math.PI;
  var ea = (3.6 * this.options["percent"] - 90) / 360 * 2 * Math.PI;
  context.arc(c.x, c.y, this.radius, sa, ea, 0);
  context.lineTo(c.x, c.y);
  context[type]();
  context.closePath();
};

あとhtmlの方も若干変更を加えます。

<script type="text/javascript" src="betsuyakuchart.js"></script>
<script type="text/javascript">
  window.onload = function() {
    var bChart = new BetsuyakuChart("my-canvas", {'percent': 40});
    bChart.stroke();
  }
</script>
<canvas id="my-canvas" width="200" height="200"></canvas>

サンプルさんの場合はaddメソッドを使って要素を足しているんですが、今僕が作ろうとしている円グラフにそれは必要ないので、addは消します。その代わり、任意のパーセンテージの数値を渡せるように書き換えています。

『{‘percent’: 40}』の部分がそれですね。ここの数字を任意に変えれば、青い円弧の範囲がそれに応じて変わるようになっています。

ここまで書けたらあともうちょいです。最後にもう一度、BetsuyakuChart.prototype.strokeにちょっと修正を加える。

BetsuyakuChart.prototype.stroke = function() {
  this.size   = {w: this.canvas.offsetWidth, h: this.canvas.offsetHeight};
  this.center = {x: this.size.w/2, y: this.size.h/2};
  this.radius = Math.min(this.size.w/2, this.size.h/2) - this.margin;
  
  if(this.options['percent'] == 0) {
    this.options['strokeStyle'] = "#ffffff";
  }
  
  this._defaultStroke("fill");
  this._defaultStroke("stroke");
  
  if(this.options['percent'] > 0) {
    this.options["strokeStyle"] = "#ffffff";
    this.options["fillStyle"] = "#329aee";
    this._oneStroke("fill");
    this._oneStroke("stroke");
  }
  //this._createText();
};

0%のときは_oneStrokeを呼び出す必要はないので、0%より値が上だったときに青い円弧を描くようにしています。


てな感じで、最終的にソースはこんな感じになりました。これ丸コピすればたぶん普通に動く……はず。

//円グラフを表示させるhtmlファイルの中身
<script type="text/javascript" src="betsuyakuchart.js"></script>
<script type="text/javascript">
  window.onload = function() {
    var bChart = new BetsuyakuChart("my-canvas", {'percent': 40});
    bChart.stroke();
  }
</script>
<canvas id="my-canvas" width="200" height="200"></canvas>

//betsuyakuchart.jsの中身
var BetsuyakuChart = function() {
  this.initialize.apply(this,arguments);
};
 
BetsuyakuChart.prototype.initialize = function(id, options) {
  this.canvas = document.getElementById(id);
  this.pList = [];
  this.sum     = 0;
  this.margin  = 5;
  this.options = {"strokeStyle": "#dddddd", "fillStyle": "#dddddd", "percent": 0};
  for (var i in options) {
    this.options[i] = options[i];
  }
};
 
BetsuyakuChart.prototype.add = function(key, value) {
  this.sum += value;
  var p = {"key":key,"value": value};
  this.pList.push(p);
};
 
BetsuyakuChart.prototype.stroke = function() {
  this.size   = {w: this.canvas.offsetWidth, h: this.canvas.offsetHeight};
  this.center = {x: this.size.w/2, y: this.size.h/2};
  this.radius = Math.min(this.size.w/2, this.size.h/2) - this.margin;
  
  if(this.options['percent'] == 0) {
      this.options['strokeStyle'] = "#ffffff";
  }
  
  this._defaultStroke("fill");
  this._defaultStroke("stroke");
  
  if(this.options['percent'] > 0) {
      this.options["strokeStyle"] = "#ffffff";
      this.options["fillStyle"] = "#329aee";
      this._oneStroke("fill");
      this._oneStroke("stroke");
  }
  //this._createText();
};

BetsuyakuChart.prototype._defaultStroke = function(type) {
  var c = this.center;
  var context = this.canvas.getContext("2d");
  context.beginPath();
  context[type + "Style"] = this.options[type + "Style"] || "#888888";
  context.moveTo(c.x, c.y);
  context.lineWidth = 2;
  
  var sa = -90 / 360 * 2 * Math.PI;
  var ea = 270 / 360 * 2 * Math.PI;
  context.arc(c.x, c.y, this.radius, sa, ea, 0);
  context.lineTo(c.x, c.y);
  context[type]();
  context.closePath();
}
 
BetsuyakuChart.prototype._oneStroke = function(type) {
  var c = this.center;
  var context = this.canvas.getContext("2d");
  context.beginPath();
  context[type + "Style"] = this.options[type + "Style"] || "#888888";
  context.moveTo(c.x, c.y);
  context.lineWidth = 2;
  
  this.radius = this.radius + 5;
  var sa = -90 / 360 * 2 * Math.PI;
  var ea = (3.6 * this.options["percent"] - 90) / 360 * 2 * Math.PI;
  context.arc(c.x, c.y, this.radius, sa, ea, 0);
  context.lineTo(c.x, c.y);
  context[type]();
  context.closePath();
};
 
BetsuyakuChart.prototype._createText = function() {
  // 
}

若干説明を省略したところもありますが、まあ、しょせん僕程度のレベルのエンジニアが書くコードですから、コードを読みさえすれば、難しくて分からないということはない……と思います。



IEに捧ぐ

では、デザートに行きましょうか。

このcanvasタグはHTML5なので、IEでは残念ながら動きません。さすがIEですねっ。

でも大丈夫。あの天下のGoogleさんがIEでもcanvasタグを使えるようにするためのライブラリを公開しています。さすがGoogleですねっ。

それがこちらのExplorerCanvasというやつです。ここからexcanvas.jsというやつをダウンロードして読み込むようにしてやれば、IEでも円グラフが表示されるようになります。

さすがですねっ。ねっ。



さて、こんな長ったらしい記事にするつもりはなかったんですが、結構長くなっちゃいましたね……何となく説明が長い記事に出会うと技術的に小難しいことやらなきゃいけないんじゃないかと思ってしまいそうですが、この円グラフを描くやつはそんなに難しくないです、ほんとに。

なのでまあ、もし機会があれば、チャレンジしてみてください。そして俺のなんかよりもっと素晴らしい円グラフが作れたら、他力本願に定評のあるこのわたくしめにぜひともその技術情報を分けてください☆
 もしかしたら何か関連しているかも? 
 質問や感想などお気軽にコメントしてください