<?php
/*bChart - Simple PHP chart v3.2 - 2011-08-02
* http://bchart.sf.net
* This program 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 3 of the License, or
* (at your option) any later version.
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
//If you want to use fmt=sql*, please add your server connection here===========
//@mysql_connect('localhost','user','password');
//@mysql_select_db('db_name');
@session_start();
@bChart_create();
function bChart_create(){
if ($_GET['ses']) $init=$_SESSION[$_GET['ses']];
else $init=($_GET['data'] ? $_GET : $_POST);
if (!$init['data']) return;
$data=$init['data'];
unset($init['data']);
if (!isset($init['sepRow'])) $init['sepRow']=';';
$img=new bChart($data,$init);
$img->cout();
}
class bChart{
function bChart($data,$init){
$this->init=$init;
$this->initInit($init,$data);
$w=$this->init['w']; $h=$this->init['h'];
$this->w=$w;//used for public access
$this->img=imagecreatetruecolor($w,$h);
$fff=imagecolorallocate($this->img,255,255,255);
imagefilledrectangle($this->img,0,0,$w,$h,$fff);
imagecolortransparent($this->img,$fff);
$this->drawRectRoundFill(3,3,$w-3,$h-3,5,240,240,240);
$this->drawRectRound(1,1,$w-1,$h-1,5,230,230,230);
$this->drawGraphArea(255,255,255);
$this->drawScale(120,120,120);
if ($this->init['legend']) $this->drawLegend();
$this->drawChart($this->data);
if (isset($this->data2)) $this->drawChart($this->data2,2);
}
function cout($url=0){
if ($url) imagepng($this->img,$url);
else{
header('Content-type: image/png');
imagepng($this->img);
imagedestroy($this->img);
}
}
function drawGraphArea($R,$G,$B){
$color=imagecolorallocate($this->img,$R,$G,$B);
$x1=$this->init['x1']; $y1=$this->init['y1'];
$x2=$this->init['x2']; $y2=$this->init['y2'];
imagefilledrectangle($this->img,$x1,$y1,$x2,$y2,$color);
$color2=imagecolorallocate($this->img,$R-40,$G-40,$B-40);
imagerectangle($this->img,$x1,$y1,$x2,$y2,$color2);
if (!$this->init['bgStripe']) return;
$color3=imagecolorallocate($this->img,$R-15,$G-15,$B-14);
$imgH=$y2-$y1-1;
for($i=$x1-$imgH;$i<=$x2;$i=$i+4){
$a1=$i; $b1=$y2; $a2=$i+$imgH; $b2=$y1;
if ($a1<$x1){
$a1=$x1; $b1=$y1+$a2-$x1+1;
}
if ($a2>=$x2){
$b2=$y1+$a2-$x2+1; $a2=$x2-1;
}
imageline($this->img,$a1,$b1,$a2,$b2+1,$color3);
}
}
function drawRectRound($x1,$y1,$x2,$y2,$r,$R,$G,$B){
$color=imagecolorallocate($this->img,$R,$G,$B);
imageline($this->img,$x1+$r,$y1,$x2-$r,$y1,$color);
imageline($this->img,$x2,$y1+$r,$x2,$y2-$r,$color);
imageline($this->img,$x1+$r,$y2,$x2-$r,$y2,$color);
imageline($this->img,$x1,$y1+$r,$x1,$y2-$r,$color);
imagearc($this->img,$x1+$r,$y1+$r,$r*2,$r*2,180,270,$color);
imagearc($this->img,$x2-$r,$y1+$r,$r*2,$r*2,270,0,$color);
imagearc($this->img,$x2-$r,$y2-$r,$r*2,$r*2,0,90,$color);
imagearc($this->img,$x1+$r,$y2-$r,$r*2,$r*2,90,180,$color);
}
function drawRectRoundFill($x1,$y1,$x2,$y2,$r,$R,$G,$B){
$color=imagecolorallocate($this->img,$R,$G,$B);
imagefilledrectangle($this->img,$x1,$y1+$r,$x2,$y2-$r,$color);
imagefilledrectangle($this->img,$x1+$r,$y1,$x2-$r,$y2,$color);
imagefilledarc($this->img,$x1+$r,$y1+$r,$r*2,$r*2,180,270,$color,IMG_ARC_PIE);
imagefilledarc($this->img,$x2-$r,$y1+$r,$r*2,$r*2,270,0,$color,IMG_ARC_PIE);
imagefilledarc($this->img,$x2-$r,$y2-$r,$r*2,$r*2,0,90,$color,IMG_ARC_PIE);
imagefilledarc($this->img,$x1+$r,$y2-$r,$r*2,$r*2,90,180,$color,IMG_ARC_PIE);
}
function drawText($x,$y,$angle=0,$txt,$color,$pos=7,$x1=null,$y2=null){
//pos:1 top left 2 top center 3 top right
//pos:4 mid left 5 mid center 6 mid right
//pos:7 btm left 8 btm center 9 btm right
$font=$this->init['font'];
$fontSize=$this->init['fontSize'];
if ($x1<>null && ($pos==2 || $pos==5 || $pos==8)) $x=$x+($x1-$x)/2;
if ($y1<>null && ($pos==4 || $pos==5 || $pos==6)) $y=$y+($y1-$y)/2;
if ($pos<>7){
$box=imagettfbbox($fontSize,0,$font,$txt);
$Y=$box[5]-$box[3]; $X=$box[2]-$box[0];
if ($angle==90 || $angle==-90) $this->swap($X,$Y);
if ($pos<4) $y=$y-$Y;//top
elseif ($pos<7) $y=$y-$Y/2;//center
if ($pos==2 || $pos==5 || $pos==8) $x=$x-$X/2;//center
elseif ($pos==3 || $pos==6 || $pos==9) $x=$x-$X;//right
}
imagettftext($this->img,$fontSize,$angle,$x,$y,$color,$font,$txt);
}
function drawLegend(){
$legend=@substr($this->init['legend'],0,1);
if (@strlen($this->init['legend'])==2){
foreach ($this->init['serial'] as $sn) $str .=$sn;
$box=imagettfbbox($this->init['fontSize'],0,$this->init['font'],$str);
$X=$box[2]-$box[0];
$x=($this->w-$X-count($this->init['serial'])*15)/2;
}elseif ($legend<>1) $x=10;
if ($legend=='T') $y=$this->init['y1']-20;
elseif ($legend=='B') $y=$this->init['h']-15;
else{
$x=$this->init['x2']+10;
$y=$this->init['y1'];
}
$colorN=count($this->init['colors']);
foreach ($this->init['serial'] as $i=>$sn){
$c=$this->init['colors'][$i%$colorN];
$c=imagecolorallocate($this->img,$c[0],$c[1],$c[2]);
if ($legend==1){
if ($i) $y +=$this->init['fontSize']+5;
imagefilledrectangle($this->img,$x,$y,$x+8,$y+8,$c);
$this->drawText($x+15,$y+8,0,$sn,$c);
}else{
imagefilledrectangle($this->img,$x,$y,$x+8,$y+8,$c);
$this->drawText($x+15,$y+8,0,$sn,$c);
$box=imagettfbbox($this->init['fontSize'],0,$this->init['font'],$sn);
$X=$box[2]-$box[0];
$x +=($X+20);
}
}
}
function drawScale($R,$G,$B){
$color=imagecolorallocate($this->img,$R,$G,$B);
$x1=$this->init['x1']; $y1=$this->init['y1'];
$x2=$this->init['x2']; $y2=$this->init['y2'];
imageline($this->img,$x1,$y1,$x1,$y2,$color);
imageline($this->img,$x1,$y2,$x2,$y2,$color);
$color2=imagecolorallocate($this->img,$R+100,$G+100,$B+100);
//y axis
$yDiv=count($this->init['yVal']);
for ($i=0;$i<$yDiv;$i++){
$y=$i*$this->init['step']+$y1;
imageline($this->img,$x1-3,$y,$x1,$y,$color);
$this->drawText($x1-8,$y,0,$this->formatVal($this->init['yVal'][$i]).' '.$this->init['yUnit'],$color,6);
if ($i && $i<$yDiv-1){
for ($j=$x1;$j<$x2;$j=$j+10) imageline($this->img,$j,$y,$j+4,$y,$color2);
}
}
if ($this->init['yTitle']) $this->drawText(15,$this->init['y2'],90,$this->init['yTitle'],$color,8,null,$this->init['y1']);
//y2 axis
if (isset($this->data2)){
$yDiv=count($this->init['yVal2']);
for ($i=0;$i<$yDiv;$i++){
$y=$i*$this->init['step2']+$y1;
imageline($this->img,$x2,$y,$x2+3,$y,$color);
$this->drawText($x2+7,$y,0,$this->formatVal($this->init['yVal2'][$i]).' '.$this->init['yUnit'],$color,4);
}
}
//x axis
$is_bar=(substr($this->init['chart'],0,3)=='bar');
$xNum=count($this->init['col']);
$xDiv=($x2-$x1)/($is_bar ? $xNum : $xNum-1);
if ($is_bar){
$fm=$xDiv/2;
$tickFm=0; $tickTo=$xNum;
}else{
$tickFm=1; $tickTo=$xNum-2;
}
for ($i=0;$i<$xNum;$i++){
$x=$i*$xDiv+$fm+$x1;
$this->init['xVal'][$i]=$x;
imageline($this->img,$x,$y2,$x,$y2+3,$color);
if (!$this->init['xSkip'] || $i==($this->init['xSkip']+1)*$skip){
$skip++;
if (!$this->init['xAngle']){
$a=0; $b=18; $align=8;
}elseif ($this->init['xAngle']==-45){
$a=0; $b=8; $align=7;
}elseif ($this->init['xAngle']==-90){
$a=-8; $b=8; $align=8;
}
$this->drawText($x+$a,$y2+$b,$this->init['xAngle'],$this->formatVal($this->init['col'][$i],1),$color,$align);
}
if ($i>=$tickFm && $i<=$tickTo){
for ($j=$y1;$j<$y2;$j=$j+10) imageline($this->img,$x,$j,$x,$j+4,$color2);
}
}
if ($this->init['xTitle']) $this->drawText($this->init['x1'],$this->init['y2']+25,0,$this->init['xTitle'],$color,2,$this->init['x2']);
if ($this->init['title']) $this->drawText(10,8,0,$this->init['title'],$color,$this->init['titleAlign'],$this->init['w']-10);
}
function formatVal($v,$x=0){
if ($x && !$this->init['xFormat']) return $v;
if (!$x && !$this->init['yFormat']) return $v;
$fmt=($x ? $this->init['xFormat'] : $this->init['yFormat']);
$fmt=@explode("|",$fmt,4);
if ($fmt[0]=='format') return @number_format($v,$fmt[1],$fmt[2],$fmt[3]);
if ($fmt[0]=='substr'){
if ($fmt[2]<>'') return @substr($v,$fmt[1],$fmt[2]);
return @substr($v,$fmt[1]);
}
$id=($x ? $this->init['xID'] : $this->init['yID']);
if ($id[$v]<>'') return $id[$v];
return $v;
}
function drawChart($data,$mode=''){
if (count($this->init['xVal'])<2) $W=$this->init['x2']-$this->init['x1'];
else $W=$this->init['xVal'][1]-$this->init['xVal'][0]-2;
if (count($data)==2 && isset($data['bChart_SumUp_Original'])){
$is_sum=1; $w=$W;
}else $w=$W/count($data);
if (isset($this->data2)) $w=$W/2;
$lw=$this->init['lineWidth']/2;//for line chart
$colorN=count($this->init['colors']);
foreach ($this->init['xVal'] as $i=>$x){
$j=($mode ? 1 : 0);
foreach ($data as $sn=>$arr){
$c=$this->init['colors'][$j%$colorN];
$c=imagecolorallocate($this->img,$c[0],$c[1],$c[2]);
$y=($this->init['yVal'.$mode][0]-$arr[$i])*$this->init['step'.$mode]/($this->init['yVal'.$mode][0]-$this->init['yVal'.$mode][1]);
//draw bar
if ($this->init['chart']=="barV" || $is_sum){
$barX=$x-$W/2;
$barX2=$barX+$W;
if ($is_sum){//same copy from bar
$ya=$this->init['y1']+$y;
$yb=$this->init['y1']+$this->init['yVal'][0]*$this->init['step']/($this->init['yVal'][0]-$this->init['yVal'][1]);
}elseif (!$j){
$ya=$this->init['y1']+$y;
$yb=$this->init['y1']+$this->init['yVal'][0]*$this->init['step']/($this->init['yVal'][0]-$this->init['yVal'][1]);
}else{
$yb=$ya;
$ya -=$arr[$i]*$this->init['step']/($this->init['yVal'][0]-$this->init['yVal'][1]);
}
}else{
$barX=$x-$W/2+$j*$w;
$barX2=$barX+$w;
$ya=$this->init['y1']+$y;
$yb=$this->init['y1']+$this->init['yVal'.$mode][0]*$this->init['step'.$mode]/($this->init['yVal'.$mode][0]-$this->init['yVal'.$mode][1]);
}
if ($ya>$yb) $this->swap($ya,$yb);
if ($ya<$this->init['y1']) $ya=$this->init['y1'];
if ($yb>$this->init['y2']) $yb=$this->init['y2'];
imagefilledrectangle($this->img,$barX,$ya,$barX2,$yb,$c);
//draw value
if ($this->init['chart']<>"barV" && $this->init['valShow']) $this->drawText($barX,$ya-2,$this->init['valAngle'],$arr[$i],$c,8,$barX+$w);
$j++;
}
if ($this->init['chart']=="barV" && $this->init['valShow']) $this->drawText($barX,$ya-2,$this->init['valAngle'],$this->init['ttl'][$i],$c,8,$barX+$W);
}
}
function initInit($init,$data){
$this->initInitDefa('h',230);
$this->initInitDefa('chart','bar');
$this->initInitDefa('bgStripe',1);
$this->initInitDefa('fmt','str');
$this->initInitDefa('sepCol',',');
$this->initInitDefa('sepRow',chr(10));
//font dir might need a fix? fix later
$this->initInitDefa('font',@dirname(__FILE__).'/DejaVuSansCondensed.ttf');
$this->initInitDefa('fontSize',8);
$this->initInitDefa('x1',($this->init['yTitle'] ? 70 : 55));
$this->initInitDefa('y1',20+($this->init['title'] ? 15 : 0));
$this->initInitDefa('y2',$this->init['h']-($this->init['xTitle'] ? 45 : 30));
$this->initInitDefa('lineWidth',1);
if ($init['titleAlign']<>1) $this->init['titleAlign']=2;
$data_fmt=@substr($init['fmt'],0,3);
if ($data_fmt=='csv') $this->initDataCSV($data);
elseif ($data_fmt=='sql') $this->initDataSQL($data);
elseif ($data_fmt=='arr') $this->initDataArr($data);
else $this->initDataStr($data);
if ($init['gapT']) $this->init['y1'] +=$init['gapT'];
if ($init['gapB']) $this->init['y2'] -=$init['gapB'];
if ($init['gapL']) $this->init['x1'] +=$init['gapL'];
$legend=@substr($this->init['legend'],0,1);
if ($legend=='T') $this->init['y1'] +=15;
if ($legend=='B') $this->init['y2'] -=15;
if ($init['yAxis']==2 && count($this->data)==2){
$this->init['chart']='bar';
$i=0;
foreach ($this->data as $k=>$v){
if (!$i) $tmp[$k]=$v;
else $this->data2[$k]=$v;
$i++;
}
$this->data=$tmp;
}
$this->auditData($this->data);
if (!isset($this->init['legend']))
$this->init['legend']=(count($this->init['serial'])>1 ? 1 : 0);
if ($init['w']=='fix'){
$num=count($this->init['col']);
if ($this->init['chart']=='bar') $num=$num*count($this->init['serial'])*20;
else $num=$num*21;
$this->init['w']=$this->init['x1']+20+$num+($this->init['legend'] ? 95 : 0);
}else $this->initInitDefa('w',448);
$this->init['x2']=$this->init['w']-20-($this->init['legend']==1 ? 95 : 0);
if ($init['gapR']) $this->init['x2'] -=$init['gapR'];
if (isset($this->data2)){
$this->init['x2'] -=35;
$this->auditData($this->data2,2);
}
$this->initColor();
}
function initColor(){//ttl 22 colors - should be enough
$color="94,48,0;51,102,153;247,143,1;90,181,110;201,34,0;238,208,181;153,102,51;153,51,51;153,153,102;102,204,204;102,102,153;51,153,153;102,204,102;153,102,102;153,153,153;51,153,51;204,102,102;51,204,153;102,153,102;153,153,255;102,153,153;204,153,102";
if ($this->init['color']) $color=$this->init['color'].";".$color;//add at front
if ($this->init['colorAdd']) $color .=";".$this->init['colorAdd'];//add at end
$arr=@explode(';',$color);
foreach ($arr as $v){
$arr2=@explode(',',$v);
$R=@ceil($arr2[0]);
$G=@ceil($arr2[1]);
$B=@ceil($arr2[2]);
if ($R>-1 && $R<256 && $G>-1 && $G<256 && $B>-1 && $B<256) $colors[]=@array($R,$G,$B);
}
if ($this->init['colorDel']<>''){
$arr=@explode(",",$this->init['colorDel']);
foreach ($arr as $v) unset($colors[$v]);
foreach ($colors as $v) $this->init['colors'][]=$v;
}else $this->init['colors']=$colors;
}
function initInitDefa($name,$var){if (!isset($this->init[$name])) $this->init[$name]=$var;}
function initDataCSV($file){$this->initDataStr(@file_get_contents($file));}
function initDataStr($str){
/*csv,str,sql,arr are in same format: line1=header
Year,Jan,Feb,Mar,... csv|str|sql|arr
2000,123,234,345,...
2003,333,444,555,...
Year,Mon,Amt -- csvV2|strV2|sqlV2|arrV2
2002,Jan,123
2002,Feb,234
2003,May,122
Mon,Amt,IP,Hit -- csvV|strV|sqlV|arrV
Jan,100,22,333
Feb,200,44,555*/
$str=@trim($str);
$arr=@explode($this->init['sepRow'],$str);
foreach ($arr as $i=>$line) $data[]=@explode($this->init['sepCol'],$line);
$this->initDataArr($data);
}
function initDataSQL($sql){
$res=@mysql_query($sql);
$num=@mysql_num_fields($res);
for ($i=0;$i<$num;$i++) $data[0][]=@mysql_field_name($res,$i);
while ($row=@mysql_fetch_row($res)) $data[]=$row;
$this->initDataArr($data);
}
function initDataArr($data){
$style=@substr($this->init['fmt'],3);//'',V,V2
$head=@array_shift($data);
$num=count($head);
if ($style=='V2' && $num<3) $style='V';
if ($num==1){
foreach ($data as $arr) $this->data[$head[0]][]=$arr[0];
}elseif ($style=='V'){
$sn=$head;
@array_shift($sn);
foreach ($data as $arr){
$this->init['col'][]=$arr[0];
foreach ($sn as $k=>$v) $this->data[$v][]=$arr[$k+1];
}
}elseif ($style=='V2'){
foreach ($data as $arr){
if (!@in_array($arr[0],$sn)) $sn[]=$arr[0];
if (!@in_array($arr[1],$col)) $col[]=$arr[1];
$tmp[$arr[0]][$arr[1]]=$arr[2];
}
$this->init['col']=$col;
foreach ($sn as $s){
foreach ($col as $v) $this->data[$s][]=$tmp[$s][$v];
}
}else{
$this->init['col']=$head;
@array_shift($this->init['col']);
foreach ($data as $arr) $this->data[@array_shift($arr)]=$arr;
}
}
function auditData(&$data,$mode=''){
$SUM=$this->init['sum'];
if ($SUM && ($mode || count($data)==1)) $data_hold=$data;
foreach ($data as $k=>$v){
$this->init['serial'][]=$k;
foreach ($v as $k1=>$v1){
if ($colNum<$k1) $colNum=$k1;
if ($SUM){
if ($SUM=='all' || @substr($this->init['col'][$k1],0,$SUM)==$hold_k) $sum[$k] +=$v1;
else{
$sum[$k]=$v1;
$hold_k=@substr($this->init['col'][$k1],0,$SUM);
}
$v1=$data[$k][$k1]=$sum[$k];
}
if ($this->init['chart']=='barV') $this->init['ttl'][$k1] +=$v1;
else{
if (!isset($max) || $max<$v1) $max=$v1;
if (!isset($min) || $min>$v1) $min=$v1;
}
}
}
if (isset($data_hold)) $data['bChart_SumUp_Original']=$data_hold[$k];
if ($this->init['chart']=='barV'){
foreach ($this->init['ttl'] as $v1){
if (!isset($max) || $max<$v1) $max=$v1;
if (!isset($min) || $min>$v1) $min=$v1;
}
}
$this->init['max'.$mode]=$max;
$this->init['min'.$mode]=$min;
for ($i=0;$i<=$colNum;$i++){
if (!$this->init['col'][$i]) $this->init['col'][$i]=$i;
}
//calculat y-axis
if ($max<0 && $min<0) $ttl=$min;
elseif ($max<0 || $min<0) $ttl=$max-$min;
else $ttl=$max;
if ($ttl<0) $ttl=abs($ttl);
$this->init['zoom'.$mode]=$zoom=pow(10,floor(log10($ttl)));
$ttl=$ttl/$zoom;
if ($ttl==1) $step=0.2;
elseif ($ttl>6) $step=2;
elseif ($ttl>5) $step=1.2;
elseif ($ttl>4) $step=1;
elseif ($ttl>3) $step=0.8;
elseif ($ttl>2) $step=0.6;
else $step=0.4;
$this->init['step'.$mode]=$step;
if ($max<=0 && $min<0){
for ($i=0;$i<6;$i++) $this->init['yVal'.$mode][]=0-$i*$zoom*$step;
}else{
for ($i=5;$i>-1;$i--) $this->init['yVal'.$mode][]=$i*$zoom*$step;
if ($max<=0 || $min<0) $this->auditDataYval($max,$min,$step,0,$mode);
}
if ($max>0 && $this->init['yVal'.$mode][1]>=$max) array_shift($this->init['yVal'.$mode]);
elseif ($min<0 && $this->init['yVal'.$mode][4]<=$min) array_pop($this->init['yVal'.$mode]);
$step=count($this->init['yVal'.$mode]);
if ($min<$this->init['yVal'.$mode][$step-1]) $this->init['yVal'.$mode][]=$this->init['yVal'.$mode][$step-1]-abs($this->init['yVal'.$mode][$step-2]-$this->init['yVal'.$mode][$step-3]);
$this->init['step'.$mode]=($this->init['y2']-$this->init['y1'])/(count($this->init['yVal'.$mode])-1);
}
function auditDataYval($max,$min,$step,$count=0,$mode){
if ($count>5) return;
$count++;
if ($this->init['yVal'.$mode][1]>$max && $this->init['yVal'.$mode][5]>$min){
$this->init['yVal'.$mode][]=$this->init['yVal'.$mode][5]-$this->init['zoom'.$mode]*$step;
array_shift($this->init['yVal'.$mode]);
$this->auditDataYval($max,$min,$step,$count,$mode);
}
}
function swap(&$a,&$b){$c=$a;$a=$b;$b=$c;}
}
?>