読者です 読者をやめる 読者になる 読者になる

初めてのCanvasでtwitterの発言数を折れ線グラフにしてみるのと、jsdo.itを使ってみた

canvas JavaScript jsdo.it

jsdo.itというサイトを知ったので、使ってみようと思い、ついでに触ったことないCanvasに触れてみた。

参考にしたサイト

初めにHTML

ということでcanvasの作るためにまずはHTMLを書きます。
これは簡単。canvasタグを書くだけです。

<canvas width='500' height='300'></canvas>

style属性で指定すると要素のサイズはその指定通りになるのだけど、canvasの秒が領域が300x150のデフォルトのままなのでwidthとheightで指定する。

描画部分のJavaScript

折れ線グラフの描画部分のJavaScriptですが、下のような配列を想定してます。

var data = [
  {label: 0, value: 10},
  {label: 10, value: 20},
  {label: 20, value: 30},
  {label: 30, value: 40},
  {label: 40, value: 50}
];

labalはx軸の目盛りに書かれる内容。valueがその時のyの値。
x軸は等間隔でプロットされます。



で、実際に描画するdraw関数は以下。
初めのADJはcanvasの調整分です。この辺は適当。
細かい部分は後述。

function draw(data) {
  var ADJ = 20; // canvasのマージン

  var canvas = document.getElementsByTagName('canvas')[0];

  // canvasに対応してなければ抜ける
  if (! canvas.getContext) return;

  var ctx = canvas.getContext('2d');

  // x,yの最小値、最大値のcanvasの座標
  var point = {
    x: {
      min: ADJ,
      max: canvas.width - ADJ
    },
    y: {
      min: canvas.height - ADJ,
      max: ADJ
    }
  };
  // 軸の長さ
  var axisLengthOf = {
    x: point.x.max - point.x.min,
    y: point.y.min - point.y.max,
  };

  // x軸とy軸を描く
  ctx.beginPath();
  // 線の色を黒にする
  ctx.strokeStyle = "#000";
  // x
  ctx.moveTo(point.x.min, point.y.min);
  ctx.lineTo(point.x.max, point.y.min);
  // y
  ctx.moveTo(point.x.min, point.y.min);
  ctx.lineTo(point.x.min, point.y.max);
  ctx.stroke();

  // 折れ線グラフを描く
  // yの縮尺を変えるために最大値を取得
  var dataMax = 0;
  for (var i=0, l=data.length; i<l; ++i) {
    if (data[i].value > dataMax) {
      dataMax = data[i].value;
    }
  }
  ctx.beginPath();
  // 線の色を赤にする
  ctx.strokeStyle = "#f00";
  for (var i=0, l=data.length; i<l; ++i) {
    // x: 配列の要素数で等分になるように
    // y: valuesの最大の値がcanvasを超えても描画できるように
    var x = point.x.min + (axisLengthOf.x / l * i);
    var y = point.y.min - (data[i].value / dataMax * axisLengthOf.y);
    
    // 目盛り
    ctx.fillText(data[i].label, x, point.y.min);

    // 点
    ctx.arc(x, y, 1, 0, Math.PI * 2, false);
    // text
    ctx.fillText(data[i].value, x, y);
  }
  ctx.stroke();
}
canvasを使うための出だし

まず、canvasを使うためにcanvas要素を取得します。
で、canvas要素にgetContextがない場合はそもそもcanvasに対応していないブラウザなので何もせず抜けます。
最後のcanvas.getContext('2d')は「2次元描画のためのAPIを取得」かな。
この辺はいつも同じ使い回しできる場所です。

  var canvas = document.getElementsByTagName('canvas')[0];

  // canvasに対応してなければ抜ける
  if (! canvas.getContext) return;

  var ctx = canvas.getContext('2d');
x軸、y軸の基準点など

次に折れ線グラフを描画するためにx軸、y軸についての情報を定義してます。
canvasは(というよりもブラウザは)左上が0,0なので折れ線グラフを描画する時わかりにくいなと思い、
x軸、y軸の交点が基準になるようにしたかったので定義しました。

  // x,yの最小値、最大値のcanvasの座標
  var point = {
    x: {
      min: ADJ,
      max: canvas.width - ADJ
    },
    y: {
      min: canvas.height - ADJ,
      max: ADJ
    }
  };
  // 軸の長さ
  var axisLengthOf = {
    x: point.x.max - point.x.min,
    y: point.y.min - point.y.max,
  };
x軸、y軸の描画

ここから描画の話。
基本的には「beginPathしてstroke」すれば線が引けるようです。
その間に「色決め」「どこから線を引く」「どこまで線を引く」をやってるだけですね。
「moveToで左上を基準とした座標に移動、lineToでその座標まで線を引く」という流れなので、
サンプルとか見ると「moveToで左上から近い座標へ移動、そこから下まで線を引き(y軸)、さらに右は時まで線を引く(x軸)」って感じでやってたりするのですが、
x軸とy軸の交点から引いた方が感覚的にわかりやすいなと思ったので、
毎回moveTo(point.x.min, point.y.min)から線を引いてます。

  // x軸とy軸を描く
  ctx.beginPath();
  // 線の色を黒にする
  ctx.strokeStyle = "#000";
  // x
  ctx.moveTo(point.x.min, point.y.min);
  ctx.lineTo(point.x.max, point.y.min);
  // y
  ctx.moveTo(point.x.min, point.y.min);
  ctx.lineTo(point.x.min, point.y.max);
  ctx.stroke();
折れ線グラフの描画

最後に折れ線グラフの描画。
まず、canvasのheight以上の値をそのまま描画できないため、最大値を基準に縮尺を変えます。
というこで配列の最大値を取得。

  // 折れ線グラフを描く
  // yの縮尺を変えるために最大値を取得
  var dataMax = 0;
  for (var i=0, l=data.length; i<l; ++i) {
    if (data[i].value > dataMax) {
      dataMax = data[i].value;
    }
  }


その後は折れ線グラフ。
x軸、y軸同様、「beginPathしてstroke」。
配列を回すforの中でやってることは、

  1. xとyの座標を求める。
  2. labelプロパティをy軸上に描画
  3. arcを使って点の描画(一つ前の点からの線を描画)準備
  4. その地点にvalueプロパティを描画

という感じ。
fillTextは実行時に描画されて、点と線に付いてはstroke時点で描画されると。

  ctx.beginPath();
  // 線の色を赤にする
  ctx.strokeStyle = "#f00";
  for (var i=0, l=data.length; i<l; ++i) {
    // x: 配列の要素数で等分になるように
    // y: valuesの最大の値がcanvasを超えても描画できるように
    var x = point.x.min + (axisLengthOf.x / l * i);
    var y = point.y.min - (data[i].value / dataMax * axisLengthOf.y);
    
    // 目盛り
    ctx.fillText(data[i].label, x, point.y.min);

    // 点
    ctx.arc(x, y, 1, 0, Math.PI * 2, false);
    // text
    ctx.fillText(data[i].value, x, y);
  }
  ctx.stroke();

グラフの描画についてはこんな感じで、あとはonload時にでも配列を渡せば折れ線グラフが表示されます。

せっかくなのでtwitterへの投稿数をtwilogから取得して表示してみる

とりあえず折れ線グラフを表示できたけど、このままjsdo.itに貼り付けてもイマイチなので、
twitterの何かでも貼り付けてみようかなとAPIを確認。
イマイチ良さそうなのがなかったのでtwilogRSSを使って最近の投稿数を表示させてみることにしました。

RSSが外部ドメインなのでGoogle AJAX Feed APIを使う

クライアントだけでどうにかしようとするとRSSの取得がでないため、
Google AJAX Feed API への登録からGoogle AJAX Feed APIの登録をしてtwilogのRSSJavaScriptで扱えるようにする。
APIキーが貰えるので、

<script type="text/javascript" src="http://www.google.com/jsapi?key=<APIキー>"></script>

で、Googleのライブラリを読み込み。




APIキーを取得した時にサンプルコードが表示されるので、それを参考にしながら以下のようなコード書けばOK。

var twilogUrl = 'http://twilog.org/rss-feed/lesamoureuses';

google.load("feeds", "1");
google.setOnLoadCallback(initialize);

function initialize() {
  var feed = new google.feeds.Feed(twilogUrl);
  
  var twilog = [];
  feed.load(function(result) {
    if (result.error) return;
   
    for (var i=0, l=result.feed.entries.length; l>i; --l) {
      var entry = result.feed.entries[l-1];
      var $content = $(entry.content);
      twilog.push({label: entry.title.replace(/^[^\d]*([0-9]+)[^0-9]+([0-9]+)[^\d]+/, '$1/$2'), value: $content.filter('div').length})
    }

    draw(twilog);
  });
}

new google.feeds.Feedで外部RSSをjson形式に変換してらえるので、
あとはさっき作ったdraw()に渡す配列twilogを作るだけ。
entry.contentにtwitterの発言が入っているのでjQueryオブジェクト作って発言数を数えたりしてます。

jsdo.itを使ってみる

最後に今まで作ったものをjsdo.itに貼り付け。
JavaScriptのテキストエリアに今まで作ったものを貼り付け。




jsdo.itはjQueryPrototype.jsなどの有名どころのライブラリを読み込めるのだけど、
貼り付けてる時はそこに気付かなかったので、HTMLのcanvasタグの後にscriptタグを書いて読み込むようにしました。
(その上のテキストエリアのJavaScriptはHTMLの後に追記されるので。)



以上でおしまい。
jsdo.itは手軽にJavaScriptが作れるので面白いです。

追記

正規表現ちょっとおかしかったので下のように書き直した

/^[^\d]*([0-9]+)[^0-9]+([0-9]+)[^\d]+/