CodeIgniterの学習 48 - PHPLOTのラッパーライブラリを作ってグラフ生成を簡単にする その1 (PHPでグラフを生成して表示したい 2 (実装))

(08/12/04
Thanks for introduce this source to the CodeIgniter Wiki. I feel a little happy!
http://codeigniter.com/wiki/Phplot/

CodeIgniter本家のWikiにこの拙いソースを翻訳して投稿してくれた方がいた。
ついでに本家wiki側ソースの、自動翻訳絡みでの誤変換の微修正を行った。
(一部ディレクトリ構成等に差違有り))



今日は CodeIgniter上でのグラフ生成を、 PHPLOT(http://phplot.sourceforge.net/) を使って簡単に出来るようにしてみる。

かなり昔のエントリー(CodeIgniterの学習 5 - PHPでグラフを生成して表示したい 1 (調査) http://d.hatena.ne.jp/dix3/20080922/1222042738 の続き。

以前のエントリーで調査したまま放置プレイしていて気になっていたので、
呼びだしと生成を簡単に出来るように、適当にラッパーライブラリを作ってみた。
まだまだα版だけど、モチベーションをあげるために貼っておく。


画面

先に画面を貼っておく。

こんなかんじ。


呼びだし時のパラメータを極力少なくしているが、
実際は、きめ細かにパラメータ設定出来るので、調整すればもっと良くなるはず。


インラインで表示させるために、一度画像ファイルを物理的に生成している。
古い画像ファイル(img/plot/plot_xxx.png)は、次回呼びだし時に自動的に消去するようにしているが、
unlinkを呼んでいるので、意図しないファイルを消してしまわないように注意。(var $old_file_del_flg = false;で試した方がいいかも)


なお、画像ファイルの作成パスは、
デフォルトでは ドキュメントルート/img/plot/plot_ランダムな文字列12桁_タイムスタンプ.png
としている。

ファイルが消えるまでは、直リンクで見えてしまうので注意。後で何とかしたい。



作成ソース

作成したソースは下記のとおり。いつもながら無保証。

まだα版につきいろいろ気に入らないところがある。たぶん不具合もある。


作る方で疲れたので、今日は説明は省略する。
ソース内のコメントを参照のこと。詳しくは次回に書くかも。

ライブラリ:application/libraries/graph.php

<?php if ( !defined( 'BASEPATH' ) ) exit( 'No direct script access allowed' );
 // phplotのラッパーライブラリ 作成中
 // 目的、phplotの呼びだしが煩雑なのでラッパーを噛まして簡略化する
 //  @version 0.01-alpha
 //  @link http://d.hatena.ne.jp/dix3/20081125/1227568495
 // TODO:デフォルトパラメータの調整
 // TODO:不要ファイル削除周りをもっと洗練する
 // TODO:直リンク拒否対策
 // TODO:コメントの整備
 class Graph {
  var $CI;
  var $obj;

  var $base_path = 'img/plot/';
  var $random_file_prefix = 'plot'; //ランダムなファイル名の生成ファイルのprefix
  var $random_file_name_length = 12; //ランダムなファイル名の時のprefix,suffixを除いた部分の長さ
  var $old_file_del_flg = true; //生成後base_path配下の古いファイルを削除するか
  var $old_file_del_span = 10; //古いファイルの保存秒数
  var $font_path; //グラフ用フォントパス

  var $width; //幅
  var $height; //高さ
  var $file_path; //生成ファイルのフルパス
  var $url; //生成ファイルのURL
  var $input_file_path; //

  var $data;
  var $status;

  function Graph( $params = array() )
  {
    $this -> CI = get_instance();
    $this -> CI -> load -> helper( 'string' );
    $this -> CI -> load -> helper( 'url' );
    $this -> CI -> load -> helper( 'file' );
    $this -> CI -> load -> helper( 'html' );
    // 初期化
    $this -> init($params);
  }
  // 初期化
  function init( $params = array() )
  {
    require_once( 'phplot.php' );
    // フォント
    $default_font_path = BASEPATH . 'fonts/sazanami-gothic.ttf';
    if ( isset( $params['font_path'] ) && realpath( $params['font_path'] ) && is_file( realpath( $params['font_path'] ) ) ) {
      $this -> font_path = $params['font_path'];
    }else {
      if ( isset( $default_font_path ) && realpath( $default_font_path ) && is_file( realpath( $default_font_path ) ) ) {
        $this -> font_path = $default_font_path ;
      }
    }
    // 生成ファイルを保存するディレクトリ
    $this -> base_path = isset( $params['base_path'] ) ? $params['base_path'] : $this -> base_path ;
    // ランダムなファイル名の生成ファイルのprefix
    $this -> random_file_prefix = isset( $params['random_file_prefix'] ) ? $params['random_file_prefix'] : $this -> random_file_prefix ;
    // ランダムなファイル名の時のprefix,suffixを除いた部分の長さ
    $this -> random_file_name_length = isset( $params['random_file_name_length'] ) ? ( int ) $params['random_file_name_length'] : $this -> random_file_name_length ;
    // 生成後古いファイルを削除するか
    $this -> old_file_del_flg = isset( $params['old_file_del_flg'] ) ? $params['old_file_del_flg'] : $this -> old_file_del_flg ;
    // 幅
    $this -> width = isset( $params['width'] ) ? ( int ) $params['width'] : 450 ;
    // 高さ
    $this -> height = isset( $params['height'] ) ? ( int ) $params['height'] : 350 ;
    // グラフファイルの保存パス
    $fpath = isset( $params['path'] ) ? $params['path'] : '' ;
    // グラフファイル名、指定無しの時はランダムなファイル名.pngにする
    $fname = isset( $params['name'] ) ? $params['name'] : $this -> random_file_prefix . '_' . random_string( 'alnum', $this -> random_file_name_length ) . '_' . time() . '.png';
    // ベースパスの設定
    if ( $fpath && realpath( $fpath ) ) {
      $this -> file_path = rtrim( realpath( $fpath ), '/' ) . '/' . $fname ;
    }else {
      // ベースパスが指定されていないときは、ドキュメントルート/img/plot/以下に作成
      if ( !realpath( $this -> base_path ) || !is_dir( realpath( $this -> base_path ) ) ) {
        mkdir( $this -> base_path, 0755 );
      }
      $this -> file_path = realpath( $this -> base_path ) . '/' . $fname ;
    }
    // 生成ファイルのURL
    $this -> url = rtrim( base_url(), '/' ) . '/' . rtrim( $this -> base_path, '/' ) . '/' . $fname;
    //
    if ( isset( $params['input'] ) && realpath( $params['input'] ) && is_file( realpath( $params['input'] ) ) ) {
      $this -> input_file_path = $params['input'];
    }else {
      $this -> input_file_path = NULL ;
    }
    $this -> obj = new PHPlot( $this -> width, $this -> height, $this -> file_path, $this -> input_file_path );
  }
  // データとパラメータのセット
  function setdata( $data = array(), $params = array() )
  {
    if ( !$data || !is_array( $data ) ) {
      return false;
    }else {
      $this -> data = $data;
    }
    // デフォルトパラメータのセット
    $this -> _setdefaultparams();

    // 追加パラメータのセット
    if ( $params ) {
      $this -> _setparams( $params );
    }
    $this -> obj -> SetDataValues( $this -> data );

    return true;
  }
  // デフォルトパラメータのセット、todo:あとで良い感じに調整する。
  // (追加パラメータをなるべく渡さないで良いように調整する)
  function _setdefaultparams()
  {
    // フォントの指定
    if($this -> font_path){
      $this -> obj -> SetDefaultTTFont( $this -> font_path );
    }
    // ファイルとして生成する
    $this -> obj -> SetIsInline( true );
    // Select the data array representation and store the data:
    $this -> obj -> SetDataType( 'text-data' );
    // 背景の色
    $this -> obj -> SetBackgroundColor( '#dddddd' );
    $this -> obj -> SetPlotBgColor( '#f9f9f9' );
    $this -> obj -> SetDrawPlotAreaBackground( true );
    // フォントサイズ
    if($this -> font_path){
      $this -> obj -> SetFont( 'generic', $this -> font_path, 9 );
      $this -> obj -> SetFont( 'title', $this -> font_path, 11 );
      $this -> obj -> SetFont( 'x_label', $this -> font_path, 9 );
      $this -> obj -> SetFont( 'y_label', $this -> font_path, 9 );
    }
    // 内側の枠線
    $this -> obj -> SetPlotBorderType( 'full' );
    // 凡例の位置
    // $this -> obj -> SetLegendWorld( 0.1, 30 );
    // Define the data range. PHPlot can do this automatically, but not as well.
    // $this -> obj -> SetPlotAreaWorld( 0, 0, 7, 100 );
    // ラベルの有無と、刻みの有無と位置
    $this -> obj -> SetXTickPos( 'none' );
    $this -> obj -> SetXTickLabelPos( 'none' );
    // $this -> obj -> SetXDataLabelPos( 'plotdown' );
    // $this -> obj -> SetYTickPos( 'plotright' );
    // $this -> obj -> SetYTickLabelPos( 'plotright' );
    return true;
  }
  // 追加パラメータのセット
  function _setparams( $params = array() )
  {
    $class_methods = get_class_methods( get_class( $this -> obj ) );
    // 各種メソッドを呼びだしてパラメータをセットする
    foreach( $params as $k => $v ) {
      if ( in_array( $k, $class_methods ) ) {
        if(is_array($v)){
          $this -> obj -> $k( $v );
        }elseif(is_string($v)){
          //TODO:後で、もっと綺麗に書けるんだっけ?
          $p = explode(',',$v);
          $cnt = count($p);
          switch($cnt){
            case 1:
              $this -> obj -> $k( $p[0] );
              break;
            case 2:
              $this -> obj -> $k( $p[0],$p[1] );
              break;
            case 3:
              $this -> obj -> $k( $p[0],$p[1],$p[2] );
              break;
            case 4:
              $this -> obj -> $k( $p[0],$p[1],$p[2],$p[3] );
              break;
            case 5:
              $this -> obj -> $k( $p[0],$p[1],$p[2],$p[3],$p[4]);
              break;
            default:
              break;
          }
        }else{
        }
      }
    }
    return true;
  }
  // グラフファイルの生成
  function draw()
  {
    if ( $this -> data ) {
      if ( $this -> old_file_del_flg ) {
        $this -> gcfiles();
      }
      $this -> status = $this -> obj -> DrawGraph();
      return $this -> status;
    }else {
      return false;
    }
  }
  // 生成したグラフファイルのURLを取得
  function geturl()
  {
    return ( $this -> status ) ? $this -> url : '';
  }
  // 生成したグラフファイルのimageタグを取得
  function getimg( $index_page = FALSE )
  {
    return ( $this -> status ) ? img( $this -> url, $index_page ) : '';
  }
  // 古い画像ファイル(ランダムなファイル名の画像ファイル)の削除
  function gcfiles()
  {
    $file_arr = get_dir_file_info( $this -> base_path );
    $now = time();
    if ( is_array( $file_arr ) ) {
      $regexp = '#^' . $this -> random_file_prefix . "_.{{$this->random_file_name_length}}_\d+\..+$#u";
      foreach( $file_arr as $k => $v ) {
        if ( preg_match( $regexp, basename( $v['name'] ) ) ) {
          // 古いファイルの削除
          if ( ( ( int )$now - ( $v['date'] + ( int )$this -> old_file_del_span ) ) > 0 ) {
            @unlink( $v['server_path'] );
          }
        }
      }
    }
  }
} //endofclass
 /**
  * End of file graph.php
  */
 /**
  * Location: ./application/libraries/graph.php
  */
 ?>

設置方法

手順1
上記のライブラリを、application/libraries/graph.phpとして設置する。
エンコードUTF-8Linux環境以外は知らん。)


手順2
phplot.phpを、http://sourceforge.net/projects/phplot/ からダウンロードして、
解凍したファイルをapplication/libraries/phplot.php として設置する。(他のファイルも同様)


手順3
日本語を表示したいときは、
任意の日本語ttfフォント(デフォルトでは、sazanami-gothic.ttf)を、
system/fonts/sazanami-gothic.ttf として設置する。

(sazanami-gothic.ttf自体は、Fedora8の場合は、/usr/share/fonts/sazanami-fonts-gothic/sazanami-gothic.ttf
にあるのでコピーすればよい。他のディストリビューションでも似たような場所にある筈。)


以上

使い方ポイント

  1. $arr に渡したい配列データを(phplotが認識できる形式で)セットし、
  2. $paramsに、phplotのメソッド名をキー、引数をvalueとした 配列データをセット、
  3. $this -> load -> library( 'graph' );
  4. $this -> graph -> setdata( $arr, $params );で、データとパラメータをセット、
  5. $this -> graph -> draw();でグラフ画像生成、
  6. $data['graph_img1'] = $this -> graph -> getimg();で画像のimgタグ生成

という流れ。だいぶ簡単になった。


動作確認ソース

1)コントローラ:application/controllers/graphtest.php
適当なコントローラで、こんな感じにソースを書いてみる。

なおここでは、ランダムにデータを与えて、グラフを2種類作っている。
phplotで指定できるパラメータについては、phplotのマニュアルを参照のこと。

<?php if ( ! defined( 'BASEPATH' ) ) exit( 'No direct script access allowed' );
 class Graphtest extends Controller {
  // コンストラクタ
  function Graphtest()
  {
    parent :: Controller();
    // urlヘルパ
    $this -> load -> helper( 'url' );
    // フォームヘルパ
    $this -> load -> helper( 'form' );
    // 文字列ヘルパ
    $this -> load -> helper( 'string' );
  }
  // phplotのテスト、
  // todo:phplot自身のパラメータが多すぎて把握しきれていない
  function index()
  {
    // グラフライブラリのロード
    $this -> load -> library( 'graph' );

    //-------------------------
    // グラフ生成(1回目)
    // 渡すデータ、ここではランダムに作ってみる
    $arr = array(
      array( '1', random_string( 'numeric', 3 ), ),
      array( '2', random_string( 'numeric', 3 ), ),
      array( '3', random_string( 'numeric', 3 ), ),
      array( '4', random_string( 'numeric', 3 ), ),
      array( '5', random_string( 'numeric', 3 ), ),
      array( '6', random_string( 'numeric', 3 ), ),
      array( '7', random_string( 'numeric', 3 ), ),
      );
    // 追加パラメータ、phplotのメソッド名をキーに、引数をvalueにセット
    $params = array( 'SetTitle' => 'アンケートその1', // タイトル
      'SetLegend' => array( 'らぜる', 'あみ', 'こー', 'かなめ', 'あかり', 'しあ', 'ざんげ' ), // 凡例
      'SetDataType' => 'text-data-single',
      'SetPlotType' => 'pie', // チャートの種類 area bars linepoints lines pie points squared stackedbars thinbarline
      );
    // データとパラメータのセット
    $this -> graph -> setdata( $arr, $params );
    // グラフ生成
    $this -> graph -> draw();
    //生成したグラフのIMGタグを取得
    $data['graph_img1'] = $this -> graph -> getimg();
    //生成したグラフのURLを取得
    $data['graph_url1'] = $this -> graph -> geturl();


    //-------------------------
    // グラフ生成(2回目)
    // initで初期化する
    $this -> graph -> init(array('width'=>500,'height'=>400));
    // 渡すデータ、ここではランダムに作ってみる
    $arr = array(
      array( '2005年', random_string( 'numeric', 3 ), random_string( 'numeric', 3 ), random_string( 'numeric', 3 ) , random_string( 'numeric', 3 ) ),
      array( '2006年', random_string( 'numeric', 3 ), random_string( 'numeric', 3 ), random_string( 'numeric', 3 ) , random_string( 'numeric', 3 ) ),
      array( '2007年', random_string( 'numeric', 3 ), random_string( 'numeric', 3 ), random_string( 'numeric', 3 ) , random_string( 'numeric', 3 ) ),
      array( '2008年', random_string( 'numeric', 3 ), random_string( 'numeric', 3 ), random_string( 'numeric', 3 ) , random_string( 'numeric', 3 ) ),
      array( '2009年', random_string( 'numeric', 3 ), random_string( 'numeric', 3 ), random_string( 'numeric', 3 ) , random_string( 'numeric', 3 ) ),
      array( '2010年', random_string( 'numeric', 3 ), random_string( 'numeric', 3 ), random_string( 'numeric', 3 ) , random_string( 'numeric', 3 ) ),
      );
    // 追加パラメータ、phplotのメソッド名をキーに、引数をvalueにセット
    $params = array( 'SetTitle' => 'アンケートその2', // タイトル
      'SetLegend' => array( 'なかすぎ', 'いんでっくす', 'いんく', 'あむ' ), // 凡例
      'SetPlotType' => 'stackedbars', // チャートの種類 area bars linepoints lines pie points squared stackedbars thinbarline
      );
    // データとパラメータのセット
    $this -> graph -> setdata( $arr, $params );
    //メソッドを直接呼んでみる
    $this -> graph ->obj-> SetBackgroundColor( '#f0f000' );

    
    // グラフ生成
    $this -> graph -> draw();
    //生成したグラフのIMGタグを取得
    $data['graph_img2'] = $this -> graph -> getimg();
    //生成したグラフのURLを取得
    $data['graph_url2'] = $this -> graph -> geturl();


    $data['title'] = 'PHPLOTのライブラリ化テスト';
    // ビューの生成
    $this -> load -> view( 'graphtest_index', $data  );

    //$tpl["main_content"] = $this -> load -> view( 'graphtest_index', $data , true );
    // 大枠のテンプレートに、タスクリストのビューをはめ込む
    //$this -> load -> view( 'base_view', $tpl );
  }
} //Endofclass
 /**
  * End of file graphtest.php
  */
 /**
  * Location: ./application/controllers/graphtest.php
  */
 ?>


2)ビュー:application/views/graphtest_index.php
こんな感じ

<h2><?= $title ?></h2>

<h3><?= $graph_url1 ?></h3>
<?= $graph_img1 ?>

<hr>

<h3><?= $graph_url2 ?></h3>
<?= $graph_img2 ?>



疲れたので今日はここまで。
気が向いたら続きを書く。(明日じゃないかも)


そろそろ書くネタが少なくなってきたよん。複雑なネタはちょっと書きにくいし。更新ペースは落ちるかも。