[PHP]レーダーチャート(クモの巣グラフ)をGDで描画する

以前の記事で座標の回転を行う関数を作ったので、
それを利用する形で本格的なレーダーチャートを作ります。

rader

<?php
//値
$values = array(6,7,8,10,7,8);

//ラベル
$labels = array("red", "green", "blue", "yellow", "black", "white");

$max         = 10;    //上限
$step        = 2;     //目盛の間隔
$margin	     = 80;    //グラフの余白
$text_margin = 20;    //ラベルの余白
$size        = 240;   //サイズ(正方形)

//フォント
$font = "migmix-1p-regular.ttf";
$font_size = 10;

//画像
$image = imagecreatetruecolor( $size + $margin , $size + $margin);
imageantialias($image, true);

//色
$bg   = imagecolorallocate($image, 10, 10, 10);    // 背景
$line = imagecolorallocate($image, 255, 255, 255); // チャートの線
$grid = imagecolorallocate($image, 50, 50, 50);	   // グリッドの色
$font_color = imagecolorallocate($image, 255, 160, 200);

$center_x = round(($size + $margin) / 2);
$center_y = round(($size + $margin) / 2);
$count = count($values);
$div = round(360 / $count);
$length = round($size / 2);

// 背景の描画
imagefill($image, 0, 0, $bg);
for($i = 1;$i<=$max;$i++){	
	if($i%$step != 0) continue;
	$points = array();
	for($j=0;$j<$count;$j++){
		list($x, $y) = point_rotate($length * ($i / $max), $div * $j - 90);

		$point = array($x + $center_x, $y + $center_y);
		imageline($image, $center_x, $center_y, $point[0], $point[1], $grid);
		$points = array_merge($points, $point);
	}
	imagepolygon($image, $points, $count, $grid);
}


// 文字の描画
for($i = 0;$i<$count;$i++){
	$box = imagettfbbox($font_size, 0, $font, $labels[$i]);
	$text_width = $box[2] - $box[6];
	$text_height = $box[3] - $box[7];

	list($x, $y) = point_rotate($length + $text_margin, $div * $i - 90);
	
	$text_x = (-1 * $text_width / 2) + $center_x + $x;
	$text_y = ($text_height / 2) + $center_y + $y;
	imagettftext($image, $font_size, 0, $text_x, $text_y, $font_color, $font, $labels[$i]);
}

for($i=0;$i<=$max;$i=$i+$step){
	$box = imagettfbbox($font_size, 0, $font, $i);
	$text_width = $box[2] - $box[6];
	$text_height = $box[3] - $box[7];
	
	$text_x = (-1 * $text_width) + $center_x - $font_size;
	$text_y = ($text_height / 2) + $center_y - ($length * ($i / $max));
	imagettftext($image, $font_size, 0, $text_x, $text_y, $grid, $font, $i);
}

// グラフの描画
$points = array();
for($i=0;$i<$count;$i++){
	$value = $length * $values[$i] / $max;
	list($x, $y) = point_rotate($value, $div * $i - 90);
	$point = array($x + $center_x, $y + $center_y);
	$points = array_merge($points, $point);
	
}
imagepolygon($image, $points, $count, $line);

// 画像の出力
header('Content-type: image/png');
imagepng($image);
imagedestroy($image);

function point_rotate($length, $angle){
  $angle = deg2rad($angle);
  $x = round($length * cos($angle));
  $y = round($length * sin($angle));
  return array($x, $y);
}
?>

このプログラムを利用するには同じフォルダにフォントファイルをアップロードしておく必要があります。
今回はフリーフォント MigMix を利用しました。

MigMix フォント
http://mix-mplus-ipa.sourceforge.jp/

このプログラムでは項目数に応じた多角形のレーダーチャートを生成できます。
頂点には項目名が表示され、垂直の軸にはメモリが表示されます。
目盛の間隔(step)を調整することで、10 ごとに目盛線を入れる等の設定ができます。

項目名が長くなる場合はテキストの余白($text_margin)を大きめにとって下さい。

[PHP]多階層ディレクトリ内のファイル一覧を取得する

ディレクトリ内にあるファイル一覧を表示するには scandir() や DirectoryIterator クラスを使えばできますが、ディレクトリ内にさらにサブディレクトリが有る場合は再帰的に処理するための工夫が必要です。
方法は2つあり、RecursiveIteratorIterator() と RecursiveDirectoryIterator() を組み合わせる方法と、再帰関数を利用した従来の方法です。


RecursiveIteratorIterator() と RecursiveDirectoryIterator() を使った方法

<?php
//検索するディレクトリ
$dir = dirname(__FILE__) . '/directory/';

$result = list_files($dir);
print_r($result);

function list_files($dir){
	$iterator = new RecursiveIteratorIterator(
		new RecursiveDirectoryIterator(
			$dir,
			FilesystemIterator::SKIP_DOTS
	  		|FilesystemIterator::KEY_AS_PATHNAME
			|FilesystemIterator::CURRENT_AS_FILEINFO
		), RecursiveIteratorIterator::LEAVES_ONLY
	);

	$list = array();
	foreach($iterator as $pathname => $info){
		$list[] = $pathname;
	}
	return $list;
}

FilesystemIterator::SKIP_DOTS は「.」「..」をスキップします。
FilesystemIterator::CURRENT_AS_FILEINFO を指定すると current() として SplFileInfo クラスの形で得られるので、より詳細なファイルの情報も合わせて取得できます。
FilesystemIterator::KEY_AS_PATHNAME を指定すると key() としてファイルパス+ファイル名が得られます。

「RecursiveIteratorIterator::LEAVES_ONLY」を指定すると「葉のみ」、つまりファイルのみが取得されます。
ディレクトリ名も含めて取得するには「RecursiveIteratorIterator::SELF_FIRST」か「RecursiveIteratorIterator::CHILD_FIRST」を指定します。
この2つの違いはディレクトリから先に取得するかファイルから先に取得するかの違いです。

foreach() でリストに追加する際、キー部分を利用していますが $info->getPathname() としても同じ内容が得られます。


再帰関数を使った方法

scandir() で取得したリストから is_file() と is_dir() を用いてファイルかディレクトリかの区別を行い、必要に応じて再帰関数を実行します。

<?php
//検索するフォルダ
$dir = dirname(__FILE__) . '/twism-master/';

$result = list_files($dir);
print_r($result);

function list_files($dir){
	$list = array();
	$files = scandir($dir);
	foreach($files as $file){
		if($file == '.' || $file == '..'){
			continue;
		} else if (is_file($dir . $file)){
			$list[] = $dir . $file;
		} else if( is_dir($dir . $file) ) {
			//$list[] = $dir;
			$list = array_merge($list, list_files($dir . $file . DIRECTORY_SEPARATOR));
		}
	}
	return $list;
}

取得したファイル名には「.」「..」などが含まれるためその場合はスキップします。
対象がファイルであればリストに追加し、ディレクトリであればその内容を再帰的に取得します。
フォルダ自体も一覧に含めるのであれば「$list[] = $dir;」のコメントアウトを外します。


実行結果(実行環境によって異なります)

Array
(
    [0] => /home/user/www/sample/directory/1.txt
    [1] => /home/user/www/sample/directory/2.txt
    [2] => /home/user/www/sample/directory/level1/3.txt
    [3] => /home/user/www/sample/directory/level1/dir1/4.txt
    [4] => /home/user/www/sample/directory/level1/dir1/5.txt
    [5] => /home/user/www/sample/directory/level1/dir2/6.txt
    [6] => /home/user/www/sample/directory/level1/dir2/7.txt
)

例は Linux サーバー上での実行例です。
フォルダ内の7つのファイルすべてが絶対パスで取得されています。
なお、scandir() は PHP5 以上で使用できます。
PHP4 で実行する場合は opendir() などで代用する必要があります。
また、「allow_url_fopen」などの設定が有効になっていない場合はリモートファイル(他のサーバーのファイル)の一覧を取得することはできません。

[PHP]10進数を任意の文字を使って62進数などのn進数に相互変換する(基数変換)

短縮URL等を発行する際に、データベースなどのIDをURLと関連付けて使うことが多いですが、単純に数字だけのIDを使うと多くの桁数を消費してしまいます。
0-9 に a-f を加えて一桁で16個まで表現できる16進数を使えば少し桁数を節約することができます。

この場合 a が 10 を表し、f が 15 となり、10 が 16 を意味します。
PHP で16進数の変換を行うには hexdec() や dechex() を使います。

16進数ではアルファベットの「f」までしか使いませんでしたが、残りの「g-z」を加えればもっと多くの数値を表現できます。また、URL は大文字小文字を区別するので、「A-Z」を更に加えて62進数にすると一桁だけでもかなりの情報量を扱えるようになります。

<?php
$char = array_merge(range('0','9'), range('a', 'z'), range('A', 'Z'));

$number = 1000000;
echo encode($number, $char) . ', ';

$str = "Test";
echo decode($str, $char);

function encode($number, $char){
	$result = "";
	$base = count($char);
	
	while($number > 0){
		$result = $char[ fmod($number, $base) ] . $result;
		$number = floor($number / $base);
	}
	return ($result == "" ) ? 0 : $result;
}

function decode($str, $char){
	$result = 0;
	$base = count($char);
	$table = array_flip($char);
	$digit = array_reverse(preg_split('//', $str, -1, PREG_SPLIT_NO_EMPTY));

	foreach($digit as $i => $value){
		if(!isset($table[$value])) return false;
		$result += pow($base, $i) * $table[$value];
	}
	
	return $result;
}

[実行結果]

4c92(1000000の62進数化)
13163621(「Test」の10進数化)

変数 char に代入された文字列を使って n 進数に変換します
基数は文字列の種類によって自動的に決められます。

ちなみにこの 62進数に更に2文字を加えたのが Base64 というもので、
PHP では base64encode() などの関数として利用できます。
Base64 は上記の英数字に加えて「+」と「/」が使われます。また、4文字ずつ区切って変換するので余った部分は「=」で埋められます。
Base64 で短縮URLを作る場合は「+」や「/」をURLとして問題の起きない「_」などの文字に置き換える必要があります。

[参考]
http://q.hatena.ne.jp/1222172924/186107/#i186107