WHAT'S NEW?
Loading...

PHPとjQueryライブラリ「jqPlot」で綺麗なグラフを描画する【8/10】

CakePHPを使用して、jqPlotの表示テストを行う。
用意するものは当然としてCakePHPだ。バージョンは安定している1.2を使う。
プラットフォームはLinuxだが、XAMPPなどを使用している人は適宜自分の環境に合わせて構築していただこう。XAMPPは止むを得ない場合以外は使ってないので良く分からんのだ。

CakePHPをダウンロード後展開し、最低限の設定が終わっていることを前提とする。そしてアクセスする際のURLは
http://example.com/cake_test/
とする。

ドキュメントルート内の「/cake_test」を仮想的なドキュメントルートにしたいので、CakePHPにそれを教えておかないとマズい事になる。どういうことかと言うと、CSSやJavaScript、画像などの絶対パスがおかしくなる。/imgなどとすると、/cake_test/imgではなく、/imgを見てしまうからだ。これはmod_rewrite側で指定するのが楽だ。
各「.htaccess」にRewriteBaseを記述しておく必要があるので、以下の様に編集しておく。
変更ファイル 追記内容
/cake_test/.htaccess RewriteBase /cake_test
/cake_test/app/.htaccess RewriteBase /cake_test/app
/cake_test/app/webroot/.htaccess RewriteBase /cake_test/app/webroot
これで絶対パスが正しく認識されるようになる。mod_rewrite便利すぎ。

次にデータベースだ。データベース名はjqplotとして作る。
create database `cake_test` default character set utf8 collate utf8_general_ci;


そして肝心のWebアプリだが、今回はクイックポールを作ってみる事にする。
とはいえ、クイックポールを作る事自体が目的ではなく、クイックポールで集められた投票結果をjqPlotで表示するのが目的だ。
DROP TABLE IF EXISTS `quickpolls`;
CREATE TABLE IF NOT EXISTS `quickpolls` (
  `id` int(5) unsigned NOT NULL auto_increment,
  `options` varchar(255) NOT NULL,
  `voted` int(3) unsigned NOT NULL,
  `created` datetime NOT NULL,
  `modified` datetime NOT NULL,
  PRIMARY KEY  (`id`),
  KEY `voted` (`voted`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 ;
続けてデータをインポートする。
INSERT INTO `quickpolls` (`id`, `options`, `voted`, `created`, `modified`) VALUES
(1, 'デュエル', 101, '2010-01-26 12:11:48', '2010-01-26 14:35:44'),
(2, 'バスター', 209, '2010-01-26 12:11:48', '2010-01-26 14:19:22'),
(3, 'ストライク', 193, '2010-01-26 12:14:18', '2010-01-26 14:19:24'),
(4, 'カラミティ', 20, '2010-01-26 12:14:18', '2010-01-26 14:19:26'),
(5, 'ブリッツ', 27, '2010-01-26 12:14:18', '2010-01-26 14:19:30'),
(6, 'フォビドゥン', 35, '2010-01-26 12:14:18', '2010-01-26 14:19:32'),
(7, 'イージス', 90, '2010-01-26 12:14:18', '2010-01-26 14:19:35'),
(8, 'レイダー', 52, '2010-01-26 12:14:18', '2010-01-26 14:19:37'),
(9, 'ジャスティス', 225, '2010-01-26 12:14:18', '2010-01-26 14:45:07'),
(10, 'フリーダム', 266, '2010-01-26 12:14:18', '2010-01-26 14:37:57'),
(11, 'プロヴィデンス', 243, '2010-01-26 12:14:18', '2010-01-26 14:26:34');
これでクイックポールの最低限のデータベース環境がそろった。
※このデータはアニメ「ガンダムSEED」に出てくる、モビルスーツというメカの通称を使用した。元ネタはWikipediaから。

次にCakePHP側の処理を行う。
まずアプリケーションを作るわけではないが、一応簡単にコントローラなどを作ることにする。
最初にベースとなるテンプレート、つまりレイアウトファイルを作り、必要なJavaScriptファイル、CSSファイルを設置する。
レイアウトファイルは以下のソースコードで、jqplot.ctpとして保存しておく。
[app/views/layouts/jqplot.ctp]
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="Content-Script-Type" content="text/javascript" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<title>CakePHP jqPlot Test</title>
<?php echo $html->css('jquery.jqplot.min'); ?>
<!--[if IE]><?php echo $javascript->link('excanvas.min'); ?><![endif]-->
<?php echo $javascript->link('jquery-1.3.2.min'); ?>
<?php echo $javascript->link('jquery.jqplot.min'); ?>
<?php echo $javascript->link('plugins/jqplot.categoryAxisRenderer.min'); ?>
<?php echo $javascript->link('plugins/jqplot.canvasTextRenderer.min'); ?>
<?php echo $javascript->link('plugins/jqplot.canvasAxisTickRenderer.min'); ?>
<?php echo $javascript->link('plugins/jqplot.barRenderer.min'); ?>

</head>
<body>

<h1>CakePHP jqPlot Test</h1>
<?php echo $content_for_layout; ?>

</body>
</html>
そして上記テンプレートで読みこめるように、以下のファイルをCakePHP側にコピーしておく。
コピー元 コピー先
jquery-1.3.2.min.js app/webroot/js/jquery-1.3.2.min.js
jquery.jqplot.min.js app/webroot/js/jquery.jqplot.min.js
excanvas.min.js app/webroot/js/excanvas.min.js
plugins/ app/webroot/js/plugins/
jquery.jqplot.min.css app/webroot/css/jquery.jqplot.min.css

これでインタフェース側のデザイン関係は終了。
次にモデルを作っておく。

[app/models/quickpoll.php]
class Quickpoll extends AppModel {
  var $name = 'Quickpoll';
  var $validate = array(
    'voted' => array(
      array(
        'rule' => array('notempty'),
        'message' => '数字を入れろ',
        'last' => true,
      ),
      array(
        'rule' => array('numeric'),
        'message' => '数字を入れろ',
        'last' => true,
      )
    ),
  );
}
こんな感じでよいだろう。使うのはvotedだけだ。ここに投票数が入る。
※たいしたことやるわけでもないので、$validateは丸ごと書かないでもOK

次にコントローラ。
[app/controllers/quickpolls_controller.php]
class QuickpollsController extends AppController {
  var $name='Quickpolls';
  var $helpers = array('Html', 'Javascript', 'Text');

  function beforeFilter()
  {
    $this->layout = 'jqplot';
  }

  function index()
  {
    $conditions = array(
      'fields' => array(
        'Quickpoll.id',
        'Quickpoll.options',
      ),
    );
    $this->set('quickpolls', $this->Quickpoll->find('list', $conditions));
  }

  function vote()
  {
  }
}
レイアウトファイルをjqplot.ctpというファイル名で作ったので、beforeFilterで毎回指定させている。それと、JSファイル読み込みで使用した$javascriptオブジェクトを使えるように、helperでJavascriptを指定しておく。これがないとエラーが出てしまう(個人的には$javascriptオブジェクトは暗黙の内に読みこんでおいて欲しいが)。
そしてコントローラ内のindexアクションだが、idとoptionsをフィールド指定した条件でfindでlistするだけだ。この状態で、ビュースクリプトには以下の様な配列がわたることになる。
Array
(
    [1] => デュエル
    [2] => バスター
    [3] => ストライク
    [4] => カラミティ
    [5] => ブリッツ
    [6] => フォビドゥン
    [7] => イージス
    [8] => レイダー
    [9] => ジャスティス
    [10] => フリーダム
    [11] => プロヴィデンス
)
ビュースクリプトではこれらの配列をラジオボタンとして表示させるため、このような配列にしておいた。
ビューファイルは以下のようになる。

[app/views/quickpolls/index.ctp]
<?php echo $form->create('Quickpoll', array(
  'url' => array(
    'controller' => 'quickpolls',
    'action' => 'vote',
)));?>
<?php echo $form->input('Quickpoll',array(
  'type' => 'radio',
  'options' => $quickpolls,
  'legend' => false,
  'div' => false,
  'separator' => '<br/>',
  'value' => null,
  )
);?>
<?php echo $form->end('Vote');?>
※上記ソースコードの表示だが、バグか何かでseparatorの値が崩れている。brタグを入れておくこと

上記の状態でブラウザで見てみる。
http://example.com/cake_test/quickpolls/
任意のモビルスーツを選択し「Vote」ボタンをクリックすると、Quickpollsコントローラ内のvoteアクションへ遷移する様になっている。
この時点ではまだvoteアクションは未完成だ。まずはここを作ることにする。
function vote()
  {
    if(!empty($this->data)) {
      // 該当するレコードの投票数を取得
      $selected = $this->Quickpoll->read('voted', $this->data['Quickpoll']);
      // 投票数をインクリメント
      $voted = ++$selected['Quickpoll']['voted'];
      // 投票を反映
      $this->Quickpoll->id = $this->data['Quickpoll'];
      $this->Quickpoll->saveField('voted', $voted);
    }
  }
これで投票された場合の処理は完了。しかし、投票されようがされていまいが、やらなければ行けない処理が1個ある。それは集計だ。

今回は集計はしたことにしておいて、Quickpollモデルから直接votedを取り出し、それをjqPlotのデータとする。よって、voteアクションを以下のように追記編集する必要がある。
function vote()
  {
    if(!empty($this->data)) {
      // 該当するレコードの投票数を取得
      $selected = $this->Quickpoll->read('voted', $this->data['Quickpoll']);
      // 投票数をインクリメント
      $voted = ++$selected['Quickpoll']['voted'];
      // 投票を反映
      $this->Quickpoll->id = $this->data['Quickpoll'];
      $this->Quickpoll->saveField('voted', $voted);
    }

    // quickpollsからデータを取得
    $conditions = array(
      'fields' => array(
        'Quickpoll.options',
        'Quickpoll.voted',
      ),
    );
    $this->set('quickpolls', $this->Quickpoll->find('list', $conditions));
  }
これで、jqPlot用にquickpolls配列をビュースクリプトへ渡すことが可能になった。
ちなみにどういう内容が渡されるかと言うと、こんな感じになる。
Array
(
    [デュエル] => 101
    [バスター] => 209
    [ストライク] => 193
    [カラミティ] => 20
    [ブリッツ] => 28
    [フォビドゥン] => 36
    [イージス] => 90
    [レイダー] => 52
    [ジャスティス] => 225
    [フリーダム] => 266
    [プロヴィデンス] => 243
)
※テストで何度か投票したあとのデータなので数値は初期値ではない

次に、これらを表示する、vote用のビュースクリプトを作っておく。投票された後にここにグラフを表示させればOKだ。
[app/views/quickpolls/vote.ctp]
<div id="graph" style="width:600px;height:300px;margin:30px;"></div>
<script type="text/javascript">
<?php foreach($quickpolls as $key=>$value) { ?>
<?php $data[] = "['{$key}', {$value}]"; ?>
<?php } ?>
  var data = [<?php echo join(",",$data);?>];
  plot = $.jqplot('graph', [data], {
    title: 'モビルスーツ人気投票',
    series:[{renderer:$.jqplot.BarRenderer}],
    axes: {
      xaxis: {
        renderer: $.jqplot.CategoryAxisRenderer,
        tickRenderer: $.jqplot.CanvasAxisTickRenderer,
        tickOptions: {
          enableFontSupport: true,
          angle: -30
        }
      }
    }
  });
</script>
MVCフレームワークの宿命なのか(体裁的には)不明だが、ビュースクリプトにforeachなどの反復処理を書くのは極めて遺憾なんだ。

そもそもプログラムというのは順次処理、条件分岐処理、反復処理が基本なわけで、反復処理は思いっきりビジネスロジックではないか。

しかしここは良くあるパターンとして、断腸の思いで反復処理をさせて見た(これも開発者のエゴだねぇ)。
この状態で投票してみると、ブラウザではこのような表示がされる。
vote.ctpの解説は次回。

まだつづく。