Mantis1.1.2 のグラフを JpGraph から Google Chart に変更

JpGraph は商用利用が無料じゃないのと日本語フォントを入れたりいろいろ設定が面倒なので Google Chat で描画するようにしてみました。

参照サイト

http://code.google.com/intl/ja/apis/chart/

昔は漢字が表示できませんでしたが、今はばりばり普通に利用できます。

棒グラフ

処理的には横の最高値を取って、最高からの61段階でデータを生成します。その後 Google Chart の URL を生成して file_get_contents で取得後ヘッダーを出力してから画像本体を出力します。

<?php
function graph_bar( $p_metrics, $p_title='', $p_graph_width = 350, $p_graph_height = 400 ){

	$max = 1;
	foreach( $p_metrics as $key => $item ){
		$item = floor( $item );
		$p_metrics[$key] = $item;
	
		if( $max < $item ){
			$max = $item;
		}
	}

	if( $max < 10 ){
		$max = 10;
	} elseif( $max < 50 ){
		$max = 50;
	} elseif( $max < 100 ){
		$max = 100;
	} elseif( $max < 1000 ){
		$max = ( floor( $max / 100 ) + 1 ) * 100;
	} else {
		$max = ( floor( $max / 1000 ) + 1 ) * 1000;
	}

	$step = array();
	for( $i = 0 ; $i < 6 ; $i++ ){
		$step[] = floor( ( $max / 5 ) * $i );
	}

	$data = "chd=s:";
	$name_data = "";
	foreach( $p_metrics as $key => $item ){
		$data .= substr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", $item/$max*61,1);
	}

	$keys = array();
	$i = 0;
	foreach( $p_metrics as $key => $item ){
		$keys[] = 't' . urlencode($key) . '(' . $p_metrics[$key] . ')' . ',000000,0,'.$i.',13,1';
		$i++;
	}
	$name_data .= implode( "|", $keys );

	$param = array();
	$param[] = 'cht=bhg';
	$param[] = 'chtt='.urlencode($p_title);
	$param[] = 'chts=0000FF,16';
	$param[] = 'chf=bg,s,fafafa';
	$param[] = 'chs='.$p_graph_width.'x'.floor($p_graph_height);
	$param[] = $data;
	$param[] = 'chm='.$name_data;
	$param[] = 'chxt=x';
	$param[] = 'chxl=0:|' . implode( '|', $step );
	$param[] = 'chg=20,0';

	$url = 'http://chart.apis.google.com/chart?' . implode( '&', $param );
	$chart = file_get_contents($url);

	header("Content-type: image/png");
	header("Cache-control: no-cache");

	echo $chart;

}

円グラフ

円グラフで注意しないといけないのが Google Chart の円グラフは横に長いことが前提になっています。キャプションが横に伸びるので横幅の半分以下の縦幅に縮小しています。

あとは合計を計算して、合計に占める割合でデータを作ってあげます。その後は棒グラフと一緒です。

<?php
function graph_pie( $p_metrics, $p_title='',
		$p_graph_width = 500, $p_graph_height = 350, $p_center = 0.4, $p_poshorizontal = 0.10, $p_posvertical = 0.09 ){

	if( $p_graph_width * 0.50 < $p_graph_height ){
		$p_graph_height = $p_graph_width * 0.50;
	}

	$sum = 0;
	foreach( $p_metrics as $key => $item ){
		$item = floor( $item );
		$p_metrics[$key] = $item;
	
		$sum += $item;
	}
	if( $sum == 0 ){
		$sum = 1;
	}

	$data = "chd=s:";
	$name_data = "";
	foreach( $p_metrics as $key => $item ){
		$data .= substr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", $item/$sum*61,1);
	}

	$keys = array();
	$i = 0;
	foreach( $p_metrics as $key => $item ){
		$keys[] = urlencode($key) . '(' . $p_metrics[$key] . ')';
	}
	$name_data .= implode( "|", $keys );

	$param = array();
	$param[] = 'cht=p';
	$param[] = 'chl='.$name_data;
	$param[] = 'chtt='.urlencode($p_title);
	$param[] = 'chts=0000FF,16';
	$param[] = 'chf=bg,s,fafafa';
	$param[] = 'chs='.$p_graph_width.'x'.floor($p_graph_height);
	$param[] = $data;

	$url = 'http://chart.apis.google.com/chart?' . implode( '&', $param );
	$chart = file_get_contents($url);

	header("Content-type: image/png");
	header("Cache-control: no-cache");

	echo $chart;
}

積層グラフ

本物は謎なグラフなのですが、普通の積層グラフにしてみました。処理はデータが複数追加しているぐらいで棒グラフと一緒ですね。

<?php
function graph_group( $p_metrics, $p_title='', $p_graph_width = 350, $p_graph_height = 400, $p_baseline = 100 ){
	# $p_metrics is an array of three arrays
	#   $p_metrics['open'] = array( 'enum' => value, ...)
	#   $p_metrics['resolved']
	#   $p_metrics['closed']

	$max = 1;
	$sum_list = array();
	$item_list = array();
	foreach( $p_metrics as $key => $item ){
		$sum = 0;
		foreach( $item as $key2 => $item2 ){
			$item = floor( $item2 );
			$sum_list[$key2] += $item;
			$item_list[$key2][$key] = $item;

			if( $max < $sum_list[$key2] ){
				$max = $sum_list[$key2];
			}
		}
	}

	if( $max < 10 ){
		$max = 10;
	} elseif( $max < 50 ){
		$max = 50;
	} elseif( $max < 100 ){
		$max = 100;
	} elseif( $max < 1000 ){
		$max = ( floor( $max / 100 ) + 1 ) * 100;
	} else {
		$max = ( floor( $max / 1000 ) + 1 ) * 1000;
	}

	$step = array();
	for( $i = 0 ; $i < 6 ; $i++ ){
		$step[] = floor( ( $max / 5 ) * $i );
	}

	$data = "chd=t:";
	$name_data = "";
	$items = array();;
	foreach( $p_metrics as $key => $item ){
		$item_str = array();
		foreach( $item as $key2 => $item2 ){
			$item_str[] = $item2/$max*100;
		}

		$items[] = implode( ',', $item_str );
	}
	$data .= implode( '|', $items );

	$keys = array();
	$i = 0;
	foreach( $item_list as $key => $item ){
		$keys[] = 't' . urlencode($key) . '(' . $sum_list[$key] . ')' . implode(':', $item_list[$key]) . ',000000,0,'.$i.',13,1';
		$i++;
	}
	$name_data .= implode( "|", $keys );

	$param = array();
	$param[] = 'cht=bhs';
	$param[] = 'chtt='.urlencode($p_title);
	$param[] = 'chts=0000FF,16';
	$param[] = 'chf=bg,s,fafafa';
	$param[] = 'chs='.$p_graph_width.'x'.floor($p_graph_height);
	$param[] = $data;
	$param[] = 'chm='.$name_data;
	$param[] = 'chxt=x';
	$param[] = 'chxl=0:|' . implode( '|', $step );
	$param[] = 'chg=20,0';
	$param[] = 'chco=4d89f9,c6d9fd,c6d900,c6d988';
	$param[] = 'chdl=' . lang_get( 'legend_opened' ) . '|' . lang_get( 'legend_closed' ) . '|' . lang_get( 'legend_resolved' );

	$url = 'http://chart.apis.google.com/chart?' . implode( '&', $param );
	$chart = file_get_contents($url);

	header("Content-type: image/png");
	header("Cache-control: no-cache");

	echo $chart;

}

折れ線グラフ その1

これは検索画面の折れ線と似ているけれどタイトルとかが直指定だったため、検索画面の折れ線用のデータ形式に変換してから検索画面の方を呼び出しています。

<?php
function graph_cumulative_bydate( $p_metrics, $p_graph_width = 300, $p_graph_height = 380 ){

	$key_list = array_keys($p_metrics);
	for( $date = $key_list[0] ; $date < $key_list[count($key_list)-2] ; $date += 60*60*24 ){
		if( !isset( $p_metrics[$date] ) ){
			$p_metrics[$date] = $p_metrics[$last];
		}
		$last = $date;
	}
	ksort($p_metrics);

	$p_title = lang_get( 'cumulative' );
        $p_labels = array( lang_get( 'legend_reported' ), lang_get( 'legend_resolved' ), lang_get( 'legend_still_open' ) );

	$data = array();
	$data[0] = array();
	foreach( $p_metrics as $key => $item ){
		$data[0][] = $key;

		foreach( $item as $key2 => $item2 ){
			$data[$key2+1][] = $item2;
		}
	}

	graph_bydate( $data, $p_labels, $p_title, $p_graph_width, $p_graph_height );
}

折れ線グラフ その2

先頭で画像の多きさチェックをいれています。600*500とか30000以内の画像しか Google Chart は作成できないのですが、検索画面は越えているんですよね。だから少し縦を縮小しています。この手のグラフは横は触ってはだめですが縦で調整ができる構造にした方がいいと思います。

元の描画部分がその1で作った物を流用して、その後統一したのでデータ形式をその1に変換かけています(苦笑) あと今週で検索を行うと一時間ごとのデータを取り出してくるので前回から4時間以上経過していないデータは無視するコードでデータ量を減らしています。この辺が Google Chart の限界だと思います。

あとは色の自動生成部分がいけてないのですがこの辺で妥協しています。。。

<?php
function graph_bydate( $p_metrics, $p_labels, $p_title, $p_graph_width = 300, $p_graph_height = 380 ){

	if( 300000 < $p_graph_width * $p_graph_height ){
		$p_graph_height = 300000 / $p_graph_width;
	}

	$p_metrics0 = $p_metrics;
	$p_metrics = array();
	foreach( $p_metrics0[0] as $key => $item ){
		$data = array();
		for( $i = 0 ; $i < count( $p_metrics0 ) - 1 ; $i++ ){
			$data[$i] = $p_metrics0[$i+1][$key];
		}
		$p_metrics[$item] = $data;
	}

	// skip
	$start = 0;
	foreach( $p_metrics as $key => $item ){
		if( ( $start + 60*60*4 ) <= $key ){
			$start = $key;
		} else {
			unset( $p_metrics[$key] );
		}
	}

	$max = 1;
	$sum_list = array();
	$item_list = array();
	$x_list = array();
	foreach( $p_metrics as $key => $item ){
		$sum = 0;
		foreach( $item as $key2 => $item2 ){
			$item = floor( $item2 );
			$sum_list[$key2] += $item;
			$item_list[$key2][$key] = $item;

			if( $max < $item ){
				$max = $item;
			}
		}

		$x_list[] = date("n/j", $key);
	}

	if( $max < 10 ){
		$max = 10;
	} elseif( $max < 50 ){
		$max = 50;
	} elseif( $max < 100 ){
		$max = 100;
	} elseif( $max < 1000 ){
		$max = ( floor( $max / 100 ) + 1 ) * 100;
	} else {
		$max = ( floor( $max / 1000 ) + 1 ) * 1000;
	}

	$step = array( '' );
	for( $i = 1 ; $i < 6 ; $i++ ){
		$step[] = floor( ( $max / 5 ) * $i );
	}

	$data = "chd=s:";
	$name_data = "";
	$items = array();;
	foreach( $item_list as $key => $item ){
		$item_str = "";
		foreach( $item as $key2 => $item2 ){
			$item_str .= substr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", $item2/$max*61,1);
		}

		$items[] = $item_str;
	}
	$data .= implode( ',', $items );

	$keys = array();
	$i = 0;
	foreach( $item_list as $key => $item ){
		$keys[] = 't' . urlencode($key) . '(' . $sum_list[$key] . ')' . implode(':', $item_list[$key]) . ',000000,0,'.$i.',13,1';
		$i++;
	}
	$name_data .= implode( "|", $keys );

	$count = count( $x_list );
	if( 15 < $count ){
		$cut = floor( ($count+5) / 10 );
		foreach( $x_list as $key => $item ){
			if( $key % $cut != 0 ){
				unset( $x_list[$key] );
			}
		}
	}

	$colors = array();
	$r = 0x80;
	$g = 0x80;
	$b = 0x40;
	for( $i = 0 ; $i < count( $p_labels ) ; $i++ ){
		$r += 0x80;
		if( 0xff < $r ){
			$r = 0x40;
			$g += 0x80;
		}
		if( 0xff < $g ){
			$g = 0x40;
			$b += 0x80;
		}
		if( 0xff < $b ){
			$b = 0x40;
		}

		$colors[] = sprintf( "%02x%02x%02x", $r, $g, $b );
	}

	$param = array();
	$param[] = 'cht=lc';
	$param[] = 'chtt='.urlencode($p_title . ' ' . lang_get( 'by_date' ));
	$param[] = 'chts=0000FF,16';
	$param[] = 'chf=bg,s,fafafa';
	$param[] = 'chs='.$p_graph_width.'x'.floor($p_graph_height);
	$param[] = $data;
	$param[] = 'chxt=y,x';
	$param[] = 'chxl=0:|' . implode( '|', $step ) . '|1:|' . implode( '|', $x_list );
	$param[] = 'chg='.floor(100/(count($x_list)-1)).',20';
	$param[] = 'chco=' . implode( ',', $colors );
	$param[] = 'chdl=' . urlencode( implode( "|", $p_labels ) );

	$url = 'http://chart.apis.google.com/chart?' . implode( '&', $param );
	$chart = file_get_contents($url);

//var_dump($url);echo "<br /><img src=$url><br />";

	header("Content-type: image/png");
	header("Cache-control: no-cache");

	echo $chart;

}

オリジナルのグラフ

下の文字が斜めになっていますが、Google Chart だとつらかったので棒グラフ自体を横に変更しています。

円グラフは凡例取っちゃいました。

積層はサンプル画像がわかりにくいのですが、合計数の高さでグラフがあって、各状態ごとに横に並んでいます。

棒グラフも元のデザインから結構変わっていますね。

事前設定

config_inc.php でグラフを有効にします。ここを設定しないとグラフ描画のリンクが表示されません。

<?php
$g_use_jpgraph  = ON;

全コード

/core/graph_api.php を上書きすることで更新ができます。ただし1.1.2用ですので1.2.xで利用するのは真ん中ぐらいにある utilities より上のグラフ描画部分のみ更新してください。

<?php
# Mantis - a php based bugtracking system

# Copyright (C) 2000 - 2002  Kenzaburo Ito - kenito@300baud.org
# Copyright (C) 2002 - 2007  Mantis Team   - mantisbt-dev@lists.sourceforge.net

# Mantis is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# Mantis is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Mantis.  If not, see <http://www.gnu.org/licenses/>.

	# --------------------------------------------------------
	# $Id: graph_api.php,v 1.36.2.1 2007-10-13 22:35:29 giallu Exp $
	# --------------------------------------------------------

	if ( ON == config_get( 'use_jpgraph' ) ) {
	}

	function graph_get_font() {
	}

	### Graph API ###
	# --------------------
	# graphing routines
	# --------------------
	function graph_bar( $p_metrics, $p_title='', $p_graph_width = 350, $p_graph_height = 400 ){

		$max = 1;
		foreach( $p_metrics as $key => $item ){
			$item = floor( $item );
			$p_metrics[$key] = $item;
		
			if( $max < $item ){
				$max = $item;
			}
		}

		if( $max < 10 ){
			$max = 10;
		} elseif( $max < 50 ){
			$max = 50;
		} elseif( $max < 100 ){
			$max = 100;
		} elseif( $max < 1000 ){
			$max = ( floor( $max / 100 ) + 1 ) * 100;
		} else {
			$max = ( floor( $max / 1000 ) + 1 ) * 1000;
		}

		$step = array();
		for( $i = 0 ; $i < 6 ; $i++ ){
			$step[] = floor( ( $max / 5 ) * $i );
		}

		$data = "chd=s:";
		$name_data = "";
		foreach( $p_metrics as $key => $item ){
			$data .= substr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", $item/$max*61,1);
		}

		$keys = array();
		$i = 0;
		foreach( $p_metrics as $key => $item ){
			$keys[] = 't' . urlencode($key) . '(' . $p_metrics[$key] . ')' . ',000000,0,'.$i.',13,1';
			$i++;
		}
		$name_data .= implode( "|", $keys );

		$param = array();
		$param[] = 'cht=bhg';
		$param[] = 'chtt='.urlencode($p_title);
		$param[] = 'chts=0000FF,16';
		$param[] = 'chf=bg,s,fafafa';
		$param[] = 'chs='.$p_graph_width.'x'.floor($p_graph_height);
		$param[] = $data;
		$param[] = 'chm='.$name_data;
		$param[] = 'chxt=x';
		$param[] = 'chxl=0:|' . implode( '|', $step );
		$param[] = 'chg=20,0';

		$url = 'http://chart.apis.google.com/chart?' . implode( '&', $param );
		$chart = file_get_contents($url);

		header("Content-type: image/png");
		header("Cache-control: no-cache");

		echo $chart;

	}

	# Function which displays the charts using the absolute values according to the status (opened/closed/resolved)
	function graph_group( $p_metrics, $p_title='', $p_graph_width = 350, $p_graph_height = 400, $p_baseline = 100 ){
		# $p_metrics is an array of three arrays
		#   $p_metrics['open'] = array( 'enum' => value, ...)
		#   $p_metrics['resolved']
		#   $p_metrics['closed']

		$max = 1;
		$sum_list = array();
		$item_list = array();
		foreach( $p_metrics as $key => $item ){
			$sum = 0;
			foreach( $item as $key2 => $item2 ){
				$item = floor( $item2 );
				$sum_list[$key2] += $item;
				$item_list[$key2][$key] = $item;

				if( $max < $sum_list[$key2] ){
					$max = $sum_list[$key2];
				}
			}
		}

		if( $max < 10 ){
			$max = 10;
		} elseif( $max < 50 ){
			$max = 50;
		} elseif( $max < 100 ){
			$max = 100;
		} elseif( $max < 1000 ){
			$max = ( floor( $max / 100 ) + 1 ) * 100;
		} else {
			$max = ( floor( $max / 1000 ) + 1 ) * 1000;
		}

		$step = array();
		for( $i = 0 ; $i < 6 ; $i++ ){
			$step[] = floor( ( $max / 5 ) * $i );
		}

		$data = "chd=t:";
		$name_data = "";
		$items = array();;
		foreach( $p_metrics as $key => $item ){
			$item_str = array();
			foreach( $item as $key2 => $item2 ){
				$item_str[] = $item2/$max*100;
			}

			$items[] = implode( ',', $item_str );
		}
		$data .= implode( '|', $items );

		$keys = array();
		$i = 0;
		foreach( $item_list as $key => $item ){
			$keys[] = 't' . urlencode($key) . '(' . $sum_list[$key] . ')' . implode(':', $item_list[$key]) . ',000000,0,'.$i.',13,1';
			$i++;
		}
		$name_data .= implode( "|", $keys );

		$param = array();
		$param[] = 'cht=bhs';
		$param[] = 'chtt='.urlencode($p_title);
		$param[] = 'chts=0000FF,16';
		$param[] = 'chf=bg,s,fafafa';
		$param[] = 'chs='.$p_graph_width.'x'.floor($p_graph_height);
		$param[] = $data;
		$param[] = 'chm='.$name_data;
		$param[] = 'chxt=x';
		$param[] = 'chxl=0:|' . implode( '|', $step );
		$param[] = 'chg=20,0';
		$param[] = 'chco=4d89f9,c6d9fd,c6d900,c6d988';
		$param[] = 'chdl=' . lang_get( 'legend_opened' ) . '|' . lang_get( 'legend_closed' ) . '|' . lang_get( 'legend_resolved' );

		$url = 'http://chart.apis.google.com/chart?' . implode( '&', $param );
		$chart = file_get_contents($url);

		header("Content-type: image/png");
		header("Cache-control: no-cache");

		echo $chart;

	}

	# --------------------
	# Function that displays charts in % according to the status
	# @@@ this function is not used...
	function graph_group_pct( $p_title='', $p_graph_width = 350, $p_graph_height = 400 ){
	}

	# --------------------
	# Function that displays pie charts
	function graph_pie( $p_metrics, $p_title='',
			$p_graph_width = 500, $p_graph_height = 350, $p_center = 0.4, $p_poshorizontal = 0.10, $p_posvertical = 0.09 ){

		if( $p_graph_width * 0.50 < $p_graph_height ){
			$p_graph_height = $p_graph_width * 0.50;
		}

		$sum = 0;
		foreach( $p_metrics as $key => $item ){
			$item = floor( $item );
			$p_metrics[$key] = $item;
		
			$sum += $item;
		}
		if( $sum == 0 ){
			$sum = 1;
		}

		$data = "chd=s:";
		$name_data = "";
		foreach( $p_metrics as $key => $item ){
			$data .= substr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", $item/$sum*61,1);
		}

		$keys = array();
		$i = 0;
		foreach( $p_metrics as $key => $item ){
			$keys[] = urlencode($key) . '(' . $p_metrics[$key] . ')';
		}
		$name_data .= implode( "|", $keys );

		$param = array();
		$param[] = 'cht=p';
		$param[] = 'chl='.$name_data;
		$param[] = 'chtt='.urlencode($p_title);
		$param[] = 'chts=0000FF,16';
		$param[] = 'chf=bg,s,fafafa';
		$param[] = 'chs='.$p_graph_width.'x'.floor($p_graph_height);
		$param[] = $data;

		$url = 'http://chart.apis.google.com/chart?' . implode( '&', $param );
		$chart = file_get_contents($url);

		header("Content-type: image/png");
		header("Cache-control: no-cache");

		echo $chart;
	}

	# --------------------
	function graph_cumulative_bydate( $p_metrics, $p_graph_width = 300, $p_graph_height = 380 ){

		$key_list = array_keys($p_metrics);
		for( $date = $key_list[0] ; $date < $key_list[count($key_list)-2] ; $date += 60*60*24 ){
			if( !isset( $p_metrics[$date] ) ){
				$p_metrics[$date] = $p_metrics[$last];
			}
			$last = $date;
		}
		ksort($p_metrics);

		$p_title = lang_get( 'cumulative' );
		$p_labels = array( lang_get( 'legend_reported' ), lang_get( 'legend_resolved' ), lang_get( 'legend_still_open' ) );

		$data = array();
		$data[0] = array();
		foreach( $p_metrics as $key => $item ){
			$data[0][] = $key;

			foreach( $item as $key2 => $item2 ){
				$data[$key2+1][] = $item2;
			}
		}

		graph_bydate( $data, $p_labels, $p_title, $p_graph_width, $p_graph_height );
	}


	# --------------------
	function graph_bydate( $p_metrics, $p_labels, $p_title, $p_graph_width = 300, $p_graph_height = 380 ){

		if( 300000 < $p_graph_width * $p_graph_height ){
			$p_graph_height = 300000 / $p_graph_width;
		}

		$p_metrics0 = $p_metrics;
		$p_metrics = array();
		foreach( $p_metrics0[0] as $key => $item ){
			$data = array();
			for( $i = 0 ; $i < count( $p_metrics0 ) - 1 ; $i++ ){
				$data[$i] = $p_metrics0[$i+1][$key];
			}
			$p_metrics[$item] = $data;
		}

		// skip
		$start = 0;
		foreach( $p_metrics as $key => $item ){
			if( ( $start + 60*60*4 ) <= $key ){
				$start = $key;
			} else {
				unset( $p_metrics[$key] );
			}
		}

		$max = 1;
		$sum_list = array();
		$item_list = array();
		$x_list = array();
		foreach( $p_metrics as $key => $item ){
			$sum = 0;
			foreach( $item as $key2 => $item2 ){
				$item = floor( $item2 );
				$sum_list[$key2] += $item;
				$item_list[$key2][$key] = $item;

				if( $max < $item ){
					$max = $item;
				}
			}

			$x_list[] = date("n/j", $key);
		}

		if( $max < 10 ){
			$max = 10;
		} elseif( $max < 50 ){
			$max = 50;
		} elseif( $max < 100 ){
			$max = 100;
		} elseif( $max < 1000 ){
			$max = ( floor( $max / 100 ) + 1 ) * 100;
		} else {
			$max = ( floor( $max / 1000 ) + 1 ) * 1000;
		}

		$step = array( '' );
		for( $i = 1 ; $i < 6 ; $i++ ){
			$step[] = floor( ( $max / 5 ) * $i );
		}

		$data = "chd=s:";
		$name_data = "";
		$items = array();;
		foreach( $item_list as $key => $item ){
			$item_str = "";
			foreach( $item as $key2 => $item2 ){
				$item_str .= substr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", $item2/$max*61,1);
			}

			$items[] = $item_str;
		}
		$data .= implode( ',', $items );

		$keys = array();
		$i = 0;
		foreach( $item_list as $key => $item ){
			$keys[] = 't' . urlencode($key) . '(' . $sum_list[$key] . ')' . implode(':', $item_list[$key]) . ',000000,0,'.$i.',13,1';
			$i++;
		}
		$name_data .= implode( "|", $keys );

		$count = count( $x_list );
		if( 15 < $count ){
			$cut = floor( ($count+5) / 10 );
			foreach( $x_list as $key => $item ){
				if( $key % $cut != 0 ){
					unset( $x_list[$key] );
				}
			}
		}

		$colors = array();
		$r = 0x80;
		$g = 0x80;
		$b = 0x40;
		for( $i = 0 ; $i < count( $p_labels ) ; $i++ ){
			$r += 0x80;
			if( 0xff < $r ){
				$r = 0x40;
				$g += 0x80;
			}
			if( 0xff < $g ){
				$g = 0x40;
				$b += 0x80;
			}
			if( 0xff < $b ){
				$b = 0x40;
			}

			$colors[] = sprintf( "%02x%02x%02x", $r, $g, $b );
		}

		$param = array();
		$param[] = 'cht=lc';
		$param[] = 'chtt='.urlencode($p_title . ' ' . lang_get( 'by_date' ));
		$param[] = 'chts=0000FF,16';
		$param[] = 'chf=bg,s,fafafa';
		$param[] = 'chs='.$p_graph_width.'x'.floor($p_graph_height);
		$param[] = $data;
		$param[] = 'chxt=y,x';
		$param[] = 'chxl=0:|' . implode( '|', $step ) . '|1:|' . implode( '|', $x_list );
		$param[] = 'chg='.floor(100/(count($x_list)-1)).',20';
		$param[] = 'chco=' . implode( ',', $colors );
		$param[] = 'chdl=' . urlencode( implode( "|", $p_labels ) );

		$url = 'http://chart.apis.google.com/chart?' . implode( '&', $param );
		$chart = file_get_contents($url);

//var_dump($url);echo "<br /><img src=$url><br />";

		header("Content-type: image/png");
		header("Cache-control: no-cache");

		echo $chart;

	}
	
	
	# --------------------
	# utilities
	# --------------------
	function graph_total_metrics( $p_metrics ){
		foreach ( $p_metrics['open'] as $t_enum => $t_value ) {
			$total[$t_enum] = $t_value + $p_metrics['resolved'][$t_enum] + $p_metrics['closed'][$t_enum];
		}
		return $total;
	}



	# --------------------
	# Data Extractions
	# --------------------
	# --------------------
	# summarize metrics by a single field in the bug table
	function create_bug_enum_summary( $p_enum_string, $p_enum ) {
		$t_project_id = helper_get_current_project();
		$t_bug_table = config_get( 'mantis_bug_table' );
		$t_user_id = auth_get_current_user_id();
		$specific_where = " AND " . helper_project_specific_where( $t_project_id, $t_user_id );

		$t_arr = explode_enum_string( $p_enum_string );
		$enum_count = count( $t_arr );
		for ($i=0;$i<$enum_count;$i++) {
			$t_s = explode_enum_arr( $t_arr[$i] );
			$c_s[0] = addslashes($t_s[0]);
			$t_key = get_enum_to_string( $p_enum_string, $t_s[0] );

			$query = "SELECT COUNT(*)
					FROM $t_bug_table
					WHERE $p_enum='$c_s[0]' $specific_where";
			$result = db_query( $query );
			$t_metrics[$t_key] = db_result( $result, 0 );
		} # end for
		return $t_metrics;
	}

	# Function which gives the absolute values according to the status (opened/closed/resolved)
	function enum_bug_group( $p_enum_string, $p_enum ) {
		$t_bug_table = config_get( 'mantis_bug_table' );

		$t_project_id = helper_get_current_project();
		$t_bug_table = config_get( 'mantis_bug_table' );
		$t_user_id = auth_get_current_user_id();
		$t_res_val = config_get( 'bug_resolved_status_threshold' );
		$t_clo_val = CLOSED;
		$specific_where = " AND " . helper_project_specific_where( $t_project_id, $t_user_id );

		$t_arr = explode_enum_string( $p_enum_string );
		$enum_count = count( $t_arr );
		for ( $i=0; $i < $enum_count; $i++) {
			$t_s = explode( ':', $t_arr[$i] );
			$t_key = get_enum_to_string( $p_enum_string, $t_s[0] );

			# Calculates the number of bugs opened and puts the results in a table
			$query = "SELECT COUNT(*)
					FROM $t_bug_table
					WHERE $p_enum='$t_s[0]' AND
						status<'$t_res_val' $specific_where";
			$result2 = db_query( $query );
			$t_metrics['open'][$t_key] = db_result( $result2, 0, 0);

			# Calculates the number of bugs closed and puts the results in a table
			$query = "SELECT COUNT(*)
					FROM $t_bug_table
					WHERE $p_enum='$t_s[0]' AND
						status='$t_clo_val' $specific_where";
			$result2 = db_query( $query );
			$t_metrics['closed'][$t_key] = db_result( $result2, 0, 0);

			# Calculates the number of bugs resolved and puts the results in a table
			$query = "SELECT COUNT(*)
					FROM $t_bug_table
					WHERE $p_enum='$t_s[0]' AND
						status>='$t_res_val'  AND
						status<'$t_clo_val' $specific_where";
			$result2 = db_query( $query );
			$t_metrics['resolved'][$t_key] = db_result( $result2, 0, 0);
		} ### end for

		return $t_metrics;
	}

	# --------------------
	function create_developer_summary() {

		$t_project_id = helper_get_current_project();
		$t_user_table = config_get( 'mantis_user_table' );
		$t_bug_table = config_get( 'mantis_bug_table' );
		$t_user_id = auth_get_current_user_id();
		$specific_where = " AND " . helper_project_specific_where( $t_project_id, $t_user_id );

		$t_res_val = config_get( 'bug_resolved_status_threshold' );
		$t_clo_val = CLOSED;

		$query = "SELECT handler_id, status
				 FROM $t_bug_table
				 WHERE handler_id != '' $specific_where";
		$result = db_query( $query );
		$t_total_handled = db_num_rows( $result );

		$t_handler_arr = array();
		for ( $i = 0; $i < $t_total_handled; $i++ ) {
			$row = db_fetch_array( $result );
			if ( !isset( $t_handler_arr[$row['handler_id']] ) ) {
				$t_handler_arr[$row['handler_id']]['res'] = 0;
				$t_handler_arr[$row['handler_id']]['open'] = 0;
				$t_handler_arr[$row['handler_id']]['close'] = 0;
			}
			if ( $row['status'] >= $t_res_val ) {
				if ( $row['status'] >= $t_clo_val ) {
					$t_handler_arr[$row['handler_id']]['close']++;
				} else {
					$t_handler_arr[$row['handler_id']]['res']++;
				}
			} else {
				$t_handler_arr[$row['handler_id']]['open']++;
			}
		}

		if ( count( $t_handler_arr ) == 0 ) {
			return array( 'open' => array() );
		}

		$t_imploded_handlers = implode( ',', array_keys( $t_handler_arr ) );
		$query = "SELECT id, username
				FROM $t_user_table
				WHERE id IN ($t_imploded_handlers)
				ORDER BY username";
		$result = db_query( $query );
		$user_count = db_num_rows( $result );

		for ($i=0;$i<$user_count;$i++) {
			$row = db_fetch_array( $result );
			extract( $row, EXTR_PREFIX_ALL, 'v' );

			$t_metrics['open'][$v_username] = $t_handler_arr[$v_id]['open'];
			$t_metrics['resolved'][$v_username] = $t_handler_arr[$v_id]['res'];
			$t_metrics['closed'][$v_username] = $t_handler_arr[$v_id]['close'];
		} # end for
		return $t_metrics;
	}

	# --------------------
	function create_reporter_summary() {
		global $reporter_name, $reporter_count;


		$t_project_id = helper_get_current_project();
		$t_user_table = config_get( 'mantis_user_table' );
		$t_bug_table = config_get( 'mantis_bug_table' );
		$t_user_id = auth_get_current_user_id();
		$specific_where = " AND " . helper_project_specific_where( $t_project_id, $t_user_id );

		$query = "SELECT reporter_id
				 FROM $t_bug_table
				 WHERE id != '' $specific_where";
		$result = db_query( $query );
		$t_total_reported = db_num_rows( $result );

		$t_reporter_arr = array();
		for ( $i = 0; $i < $t_total_reported; $i++ ) {
			$row = db_fetch_array( $result );

			if ( isset( $t_reporter_arr[$row['reporter_id']] ) ) {
				$t_reporter_arr[$row['reporter_id']]++;
			} else {
				$t_reporter_arr[$row['reporter_id']] = 1;
			}
		}

		if ( count( $t_reporter_arr ) == 0 ) {
			return array();
		}

		$t_imploded_reporters = implode( ',', array_keys( $t_reporter_arr ) );
		$query = "SELECT id, username
				FROM $t_user_table
				WHERE id IN ($t_imploded_reporters)
				ORDER BY username";
		$result = db_query( $query );
		$user_count = db_num_rows( $result );

		for ($i=0;$i<$user_count;$i++) {
			$row = db_fetch_array( $result );
			extract( $row, EXTR_PREFIX_ALL, 'v' );

			$t_metrics[$v_username] = $t_reporter_arr[$v_id];
		} # end for
		return $t_metrics;
	}

	# --------------------
	function create_category_summary() {
		global $category_name, $category_bug_count;

		$t_project_id = helper_get_current_project();
		$t_cat_table = config_get( 'mantis_project_category_table' );
		$t_bug_table = config_get( 'mantis_bug_table' );
		$t_user_id = auth_get_current_user_id();
		$specific_where = helper_project_specific_where( $t_project_id, $t_user_id );

		$query = "SELECT DISTINCT category
				FROM $t_cat_table
				WHERE $specific_where
				ORDER BY category";
		$result = db_query( $query );
		$category_count = db_num_rows( $result );
		if ( 0 == $category_count ) {
			return array();
		}

		for ($i=0;$i<$category_count;$i++) {
			$row = db_fetch_array( $result );
			$t_cat_name = $row['category'];
			$c_category_name = addslashes($t_cat_name);
			$query = "SELECT COUNT(*)
					FROM $t_bug_table
					WHERE category='$c_category_name' AND $specific_where";
			$result2 = db_query( $query );
			$t_metrics[$t_cat_name] = db_result( $result2, 0, 0 );
		} # end for
		return $t_metrics;
	}

	# --------------------
	function cmp_dates($a, $b){
		if ($a[0] == $b[0]) {
			return 0;
		}
		return ( $a[0] < $b[0] ) ? -1 : 1;
	}

	# --------------------
	function find_date_in_metrics($aDate){
		global $metrics;
		$index = -1;
		for ($i=0;$i<count($metrics);$i++) {
			if ($aDate == $metrics[$i][0]){
				$index = $i;
				break;
			}
		}
		return $index;
	}

	# --------------------
	function create_cumulative_bydate(){

		$t_clo_val = CLOSED;
		$t_res_val = config_get( 'bug_resolved_status_threshold' );
		$t_bug_table = config_get( 'mantis_bug_table' );
		$t_history_table = config_get( 'mantis_bug_history_table' );

		$t_project_id = helper_get_current_project();
		$t_user_id = auth_get_current_user_id();
		$specific_where = helper_project_specific_where( $t_project_id, $t_user_id );

		# Get all the submitted dates
		$query = "SELECT date_submitted
				FROM $t_bug_table
				WHERE $specific_where
				ORDER BY date_submitted";
		$result = db_query( $query );
		$bug_count = db_num_rows( $result );

		for ($i=0;$i<$bug_count;$i++) {
			$row = db_fetch_array( $result );
			# rationalise the timestamp to a day to reduce the amount of data
 			$t_date = db_unixtimestamp( $row['date_submitted'] );
			$t_date = (int) ( $t_date / 86400 );

			if ( isset( $metrics[$t_date] ) ){
				$metrics[$t_date][0]++;
			} else {
				$metrics[$t_date] = array( 1, 0, 0 );
			}
		}

		### Get all the dates where a transition from not resolved to resolved may have happened
		#    also, get the last updated date for the bug as this may be all the information we have
		$query = "SELECT $t_bug_table.id, last_updated, date_modified, new_value, old_value
			FROM $t_bug_table LEFT JOIN $t_history_table
			ON $t_bug_table.id = $t_history_table.bug_id
			WHERE $specific_where
						AND $t_bug_table.status >= '$t_res_val'
						AND ( ( $t_history_table.new_value >= '$t_res_val'
								AND $t_history_table.field_name = 'status' )
						OR $t_history_table.id is NULL )
			ORDER BY $t_bug_table.id, date_modified ASC";
		$result = db_query( $query );
		$bug_count = db_num_rows( $result );

		$t_last_id = 0;
		for ($i=0;$i<$bug_count;$i++) {
			$row = db_fetch_array( $result );
			$t_id = $row['id'];
			# if h_last_updated is NULL, there were no appropriate history records
			#  (i.e. pre 0.18 data), use last_updated from bug table instead
			if (NULL == $row['date_modified']) {
				$t_date = db_unixtimestamp( $row['last_updated'] );
			} else {
				if ( $t_res_val > $row['old_value'] ) {
					$t_date = db_unixtimestamp( $row['date_modified'] );
				}
			}
			if ( $t_id <> $t_last_id ) {
				if ( 0 <> $t_last_id ) {
					# rationalise the timestamp to a day to reduce the amount of data
					$t_date_index = (int) ( $t_last_date / 86400 );

					if ( isset( $metrics[$t_date_index] ) ){
						$metrics[$t_date_index][1]++;
					} else {
						$metrics[$t_date_index] = array( 0, 1, 0 );
					}
				}
				$t_last_id = $t_id;
			}
			$t_last_date = $t_date;
		}

		ksort($metrics);

		$metrics_count = count($metrics);
		$t_last_opened = 0;
		$t_last_resolved = 0;
		foreach ($metrics as $i=>$vals) {
			$t_date = $i * 86400;
			$t_metrics[$t_date][0] = $t_last_opened = $metrics[$i][0] + $t_last_opened;
			$t_metrics[$t_date][1] = $t_last_resolved = $metrics[$i][1] + $t_last_resolved;
			$t_metrics[$t_date][2] = $t_metrics[$t_date][0] - $t_metrics[$t_date][1];
		}
		return $t_metrics;
	}

	function graph_date_format ($p_date) {
		return date( config_get( 'short_date_format' ), $p_date );
	}


	# ----------------------------------------------------
	#
	# Check that there is enough data to create graph
	#
	# ----------------------------------------------------
	function error_check( $bug_count, $title ) {

		if ( 0 == $bug_count ) {
			$t_graph_font = graph_get_font();

			$graph = new CanvasGraph(300,380);

			$txt = new Text( lang_get( 'not_enough_data' ), 150, 100);
			$txt->Align("center","center","center");
			$txt->SetFont( $t_graph_font, FS_BOLD );
			$graph->title->Set( $title );
			$graph->title->SetFont( $t_graph_font, FS_BOLD );
			$graph->AddText($txt);
			$graph->Stroke();
			die();
		}
	}
?>

感想

勢いで作ってみましたが、あまりこのグラフみないんだよね(苦笑)

便利だぜって人がいたらもう少しきれいに作って、本家にパッチ送って取り込んでもらえるようにしたいと思います。