PHPでのグラフ画像作成

現在グラフを描画する方法として、有名なものに以下の方法があります。

本当は外部APIを利用したかったのですが、Google Chart API(http://code.google.com/apis/chart/)は漢字が現在利用できません。

ひらがなは表示できているので、もう少したったら漢字にも対応しそうではあります。

次に検討したのがFlash。各種ライブラリがあってわりと使いやすいのですが、Firefoxとの相性がいまいちでした。

印刷などで使えないのと、リロードしたときに描画がおかしくなること。印刷時はどうやらFlashの1フレーム目などが描画し終わる前に印刷用の画像を内部で生成していると思われて真っ白の画面か、たまに画像が表示される状況で実用的ではありませんでした。

次にJavaScriptですが、積上グラフに対応したもので使って見たかったものがありませんでした。

あと気をつけないといけないことが、FlashJavaScriptはクライアント側で描画しますのでサーバーへの負荷はありませんが、統計データなどが外部に出てしまうことです。

画面上に表示しているので、内部のデータを見ることができてしまってもあまり問題はないと思いますが、折れ線グラフのようなものに関しては詳細なデータまでは公開したくない場合があると考えられます。

あとは右クリックからグラフを保存するためには、画像形式でないと対応できないのも忘れてはいけません。

ちなみに使ったグラフソフトは「XML/SWF Charts」です。これはPHPからも出力できるみたいですが、データの管理のなくなXML版を利用しました。

http://www.maani.us/xml_charts/

サンプルとしては以下のような画面になります。注意点としては項目がはみ出る場合には渡す項目名に改行を入れて2行に分離するなどが必要です。

数値表示に関しては表示できない横幅では表示しないので、その点はかなり賢いです。ライセンス的には無料で使えるのですが、クリックするとサイトへジャンプする設定になっています。

.dammy_img {
  position: relative;
  top: -300px;
  margin-bottom: -300px;
  display: block;
  width: 630px;
  height: 300px;
}

上記のようなスタイルを設定した透明GIF画像をFlashの上に描画することでクリックでのジャンプを阻止することができますが、お勧めしません。

また、透明GIFを上に設置すると印刷時に真っ白になりますので、印刷用のスタイルシートで非表示にするなどが必要になります。

印刷時などの取り扱いなどを考えてとりあえずPHPでの画像生成にしました。

PHPでのグラフ描画といえばJpGraphですが、営利の利用だと有料になってしまうのでPearのライブラリを利用しようと思います。

Image_Graph

選んだライブラリがImage_Graphです。このモジュールはオープンソースのGraPHPiteを元にできたものになります。

欠点としては2006年が最終バージョンとなります。ある程度の処理ができるようになったので開発が止まってしまったのですね。

そして日本語の情報は実はあまりありません。同じようなとりあえず使ってみた感じの情報はありますが、実務でがりがり使っている情報とかを簡単に探すことはできませんでした。

環境構築

環境に依存するのはあまり好きじゃないので、このグラフ用のPear環境を準備します。とはいってもコマンドで準備するのではなく、手動で解凍します。。。

http://pear.php.net/package/PEAR

上記からPaer本体をダウンロードします。利用するのは中に入っているPEAR.PHPのみ。

  • Image_Graph

http://pear.php.net/package/Image_Graph

Image_Graph 本体をダウンロード。依存環境を確認したところ、Image_Canvasがありました。オプションとして、Numbers_Romanと Numbers_Wordsを利用しているようですがローマ数字などの変換ライブラリなので、今回は利用しないでおきます。

http://pear.php.net/package/Image_Canvas/

Image_Graph本体をダウンロード。依存環境を確認したところ、Image_Colorがありました。

  • Image_Color

http://pear.php.net/package/Image_Color/

Image_Color本体をダウンロード。

http://ossipedia.ipa.go.jp/ipafont/

日本語表示を行うために必要になります。

最終的に以下のフォルダ構成になりました。

設置ディレクトリ
├─fonts
│  │
│  ├─ipam.ttf
│  │
│  ├─ipamp.ttf
│  │
│  ├─ipag.ttf
│  │
│  ├─ipagp.ttf
│  │
│  └─ipagui.ttf
│
├─PEAR
│  │
│  ├─Image
│  │  │
│  │  ├─Canvas
│  │  │  └─...
│  │  ├─docs
│  │  │  └─...
│  │  ├─Graph
│  │  │  └─...
│  │  ├─tests
│  │  │  └─...
│  │  │
│  │  ├─Canvas.php
│  │  │
│  │  ├─Color.php
│  │  │
│  │  └─Graph.php
│  │
│  └─PEAR.php
│
└─test.php

一番下のtest.phpはまだ作っていませんが、ここにコンテンツを設置します。本当はPEARとfontsディレクトリは外部から見えない場所に設置することをお勧めします。

サンプル実行

PEAR/Image/docs/examples 以下にサンプルが入っていますので、それを設置ディレクトリにコピーして動かしてみます。

今回テストで利用するのは目に付いた「antialias.php」にします。

  • とりあえず実行
Fatal error: require_once() [function.require]: Failed opening required 'Image/Graph.php'

最初はエラーが出ます、PEARの設定をしていませんからね。

  • PEARのInclude Pathを設定
ini_set('include_path', './PEAR');

上記を「antialias.php」の上の方に追加します。面倒であれば「.htaccess」に記述をすることが可能です。

php_value include_path ./PEAR
  • 再度実行
Warning: imagettfbbox() [function.imagettfbbox]: Could not find/open font

フォントが開けないエラーがでています。

  • フォントの設定追加
// add a TrueType font
-$Font =& $Graph->addNew('font', 'Verdana');
+$Font =& $Graph->addNew('font', 'fonts/ipagp.ttf');
  • 再度実行

グラフが表示されました。動作が確認できましたので次からグラフを作っていきます。


ベースを探す

http://pear.veggerby.dk/samples/

上記のページや、examplesフォルダよりベースとなるファイルを見つけます。今回は横の積み上げ100%グラフを作成していきます。

今回は「plot_bar_stacked_horizontal.php」を使うことにしました。

初期設定

フォントとPEARの設定のみ行いました。

<?php
/**
 * Usage example for Image_Graph.
 *
 * Main purpose:
 * Show stacked bar chart
 *
 * Other:
 * Data selector
 * Fill style array (no ID) working in sync with legend
 *
 * $Id: plot_bar_stacked_horizontal.php,v 1.1 2005/09/08 19:02:17 nosey Exp $
 *
 * @package Image_Graph
 * @author Jesper Veggerby <pear.nosey@veggerby.dk>
 */

ini_set('include_path', './PEAR');
require_once 'Image/Graph.php';

// create the graph
$Graph =& Image_Graph::factory('graph', array(400, 300));
// add a TrueType font
$Font =& $Graph->addNew('font', 'fonts/ipagp.ttf');
// set the font size to 11 pixels
$Font->setSize(8);

$Graph->setFont($Font);

$Graph->add(
    Image_Graph::vertical(
        Image_Graph::factory('title', array('Stacked Bar Chart Sample', 12)),        
        Image_Graph::vertical(
            $Plotarea = Image_Graph::factory('plotarea',
                array(
                    'category',
                    'axis',
                    'horizontal'
                )
            ),
            $Legend = Image_Graph::factory('legend'),
            90
        ),
        5
    )
);   

$Legend->setPlotarea($Plotarea);        
    
// create the dataset
$Datasets[] =& Image_Graph::factory('random', array(10, 0, 4, false));
$Datasets[] =& Image_Graph::factory('random', array(10, 0, 4, false));
$Datasets[] =& Image_Graph::factory('random', array(10, 0, 4, false));

// create the 1st plot as smoothed area chart using the 1st dataset
$Plot =& $Plotarea->addNew('bar', array($Datasets, 'stacked'));

// set a line color
$Plot->setLineColor('gray');

// create a fill array   
$FillArray =& Image_Graph::factory('Image_Graph_Fill_Array');
$FillArray->addColor('blue@0.2');
$FillArray->addColor('yellow@0.2');
$FillArray->addColor('green@0.2');

// set a standard fill style
$Plot->setFillStyle($FillArray);

// create a Y data value marker
$Marker =& $Plot->addNew('Image_Graph_Marker_Value', IMAGE_GRAPH_VALUE_Y);
// and use the marker on the 1st plot
$Plot->setMarker($Marker);    

$Plot->setDataSelector(Image_Graph::factory('Image_Graph_DataSelector_NoZeros'));

// output the Graph
$Graph->done();
?>


タイトルを変えてみる

-        Image_Graph::factory('title', array('Stacked Bar Chart Sample', 12)),
+        Image_Graph::factory('title', array('テストグラフです', 12)),

ファイルはUTF8Nで保存します。


データを入れる

現在はランダムでデータ作成していますが、自分でデータを指定します。

// create the dataset
$Datasets[] =& Image_Graph::factory('random', array(10, 0, 4, false));
$Datasets[] =& Image_Graph::factory('random', array(10, 0, 4, false));
$Datasets[] =& Image_Graph::factory('random', array(10, 0, 4, false));

上記の部分を以下に変更しました。

$Datasets[0] =& Image_Graph::factory('dataset');
$Datasets[1] =& Image_Graph::factory('dataset');
$Datasets[2] =& Image_Graph::factory('dataset');

$Datasets[0]->addPoint('グラフ1', 10);
$Datasets[1]->addPoint('グラフ1', 20);
$Datasets[2]->addPoint('グラフ1', 70);

$Datasets[0]->addPoint('グラフ2', 50);
$Datasets[1]->addPoint('グラフ2', 30);
$Datasets[2]->addPoint('グラフ2', 20);

$Datasets[0]->addPoint('グラフ3', 15);
$Datasets[1]->addPoint('グラフ3', 80);
$Datasets[2]->addPoint('グラフ3', 5);


数値の表示形式の変更

小数点付きの形で出したいので、設定を追加します。

// create a Y data value marker
$Marker =& $Plot->add(new Image_Graph_Marker_Value(IMAGE_GRAPH_VALUE_Y));
$Marker->setDataPreprocessor(Image_Graph::factory('Image_Graph_DataPreprocessor_Function', 'user_format'));

function user_format($str){
    return sprintf( "%0.1f", $str);
}

Marker と呼ばれるデータ表示部分に setDataPreprocessor を利用して関数を追加します。


小さい数値の場合表示しなくする

5.0がはみ出ていますので、6以下の場合には表示しなくさせます。

function user_format($str){
    if( 6 < $str ) {
        return sprintf( "%0.1f", $str);
    }
}


数値の枠を消す

ここが結構無理やりなのですが、消すことができます。
正式なやり方がありました。。。空白だとだめで透明色を指定します。

$Marker->setBackgroundColor('transparent');
$Marker->setBorderColor('transparent');

setBackgroundColor で色の名前を設定しない透明色を指定すると背景描画を行いません。setBorderColor も同様に枠線の描画を行わない設定になります。

setBorderColor などは上位クラスから継承している関数なのですが、APIマニュアル上では表示していないので、かなり探しにくい作りになっています。


凡例に名前をつける

$Datasets[0]->setName('満足');
$Datasets[1]->setName('普通');
$Datasets[2]->setName('不満');


最後に

結構癖があるライブラリだと思いますが、それなりに使いやすいのではないでしょうか。ただし情報が少ないのと微妙に画像が切れたりするので、細かい調整が必要になると思います。

最終的なソース

<?php
/**
 * Usage example for Image_Graph.
 *
 * Main purpose:
 * Show stacked bar chart
 *
 * Other:
 * Data selector
 * Fill style array (no ID) working in sync with legend
 *
 * $Id: plot_bar_stacked_horizontal.php,v 1.1 2005/09/08 19:02:17 nosey Exp $
 *
 * @package Image_Graph
 * @author Jesper Veggerby <pear.nosey@veggerby.dk>
 */

ini_set('include_path', './PEAR');
require_once 'Image/Graph.php';

// create the graph
$Graph =& Image_Graph::factory('graph', array(400, 300));
// add a TrueType font
$Font =& $Graph->addNew('font', 'fonts/ipagp.ttf');
// set the font size to 11 pixels
$Font->setSize(8);

$Graph->setFont($Font);

$Graph->add(
    Image_Graph::vertical(
        Image_Graph::factory('title', array('テストグラフです', 12)),        
        Image_Graph::vertical(
            $Plotarea = Image_Graph::factory('plotarea',
                array(
                    'category',
                    'axis',
                    'horizontal'
                )
            ),
            $Legend = Image_Graph::factory('legend'),
            90
        ),
        5
    )
);   

$Legend->setPlotarea($Plotarea);        
    
// create the dataset
$Datasets[0] =& Image_Graph::factory('dataset');
$Datasets[1] =& Image_Graph::factory('dataset');
$Datasets[2] =& Image_Graph::factory('dataset');

$Datasets[0]->setName('満足');
$Datasets[1]->setName('普通');
$Datasets[2]->setName('不満');

$Datasets[0]->addPoint('グラフ1', 10);
$Datasets[1]->addPoint('グラフ1', 20);
$Datasets[2]->addPoint('グラフ1', 70);

$Datasets[0]->addPoint('グラフ2', 50);
$Datasets[1]->addPoint('グラフ2', 30);
$Datasets[2]->addPoint('グラフ2', 20);

$Datasets[0]->addPoint('グラフ3', 15);
$Datasets[1]->addPoint('グラフ3', 80);
$Datasets[2]->addPoint('グラフ3', 5);

// create the 1st plot as smoothed area chart using the 1st dataset
$Plot =& $Plotarea->addNew('bar', array($Datasets, 'stacked'));

// set a line color
$Plot->setLineColor('gray');

// create a fill array   
$FillArray =& Image_Graph::factory('Image_Graph_Fill_Array');
$FillArray->addColor('blue@0.2');
$FillArray->addColor('yellow@0.2');
$FillArray->addColor('green@0.2');

// set a standard fill style
$Plot->setFillStyle($FillArray);

// create a Y data value marker
$Marker =& $Plot->addNew('Image_Graph_Marker_Value', IMAGE_GRAPH_VALUE_Y);
$Marker->setDataPreprocessor(Image_Graph::factory('Image_Graph_DataPreprocessor_Function', 'user_format'));
$Marker->setBackgroundColor('transparent');
$Marker->setBorderColor('transparent');

// and use the marker on the 1st plot
$Plot->setMarker($Marker);    

$Plot->setDataSelector(Image_Graph::factory('Image_Graph_DataSelector_NoZeros'));

// output the Graph
$Graph->done();

function user_format($str){
    if( 6 < $str ) {
        return sprintf( "%0.1f", $str);
    }
}