This repository has been archived on 2024-04-08. You can view files and clone it, but cannot push or open issues or pull requests.
khosb/modules/gchart/classes/googlechart.php
2012-01-12 19:53:56 +11:00

424 lines
11 KiB
PHP

<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* This class provides access to Google's Chart API
*
* @package lnApp
* @subpackage GoogleChart
* @category Helper
* @author Deon George
* @copyright (c) 2010 Open Source Billing
* @license http://dev.osbill.net/license.html
*/
class GoogleChart implements Iterator,Countable {
// Chart URL
private $url = 'http://chart.apis.google.com/chart';
// Hold our plot data
private $plotdata = array();
private $chartdata = array();
// Number of Series to plot
private $numseries = 0;
// The type of chart we'll plot
private $chart_type = '';
// Default chart tytle.
private $chart_title = '';
// Default chart size.
private $chart_size = '700x200';
// Colors to use for series
private $series_colors = array('AAACCC','E0E0E0','CCC888','EEEBBB','666CCC','888888');
// Data encoding type to use
private $data_encode = 's';
private $div_mode = FALSE;
// Implementation Methods
public function rewind() {
reset($this->plotdata);
}
public function current() {
return current($this->plotdata);
}
public function key() {
return key($this->plotdata);
}
public function next() {
return next($this->plotdata);
}
public function valid() {
return key($this->plotdata) ? TRUE : FALSE;
}
public function count() {
return count($this->plotdata);
}
// Chart Types
private $cht = array(
// Line
// 'line' => 'lc',
// 'sparkline' => 'ls',
// 'line_xy' => 'lxy',
// Bar
// 'horizontal_bar' => 'bhs',
'vertical_bar' => 'bvs',
// 'horizontal_bar_grp' => 'bhg',
// 'vertical_bar_grp' => 'bvg',
// Pie
// 'pie' => 'p',
// 'pie_3d' => 'p3',
// 'pie_concentric' => 'pc',
// Venn
// 'venn' => 'v',
// Scatter
// 'scatter' => 's',
// Radar
// 'radar' => 'r',
// 'radar_fill' => 'rs',
// Maps
// 'map' => 't',
// Google-o-meter
// 'google_o_meter' => 'gom',
// QR
// 'qr' => 'qr',
);
public function __construct($type) {
if (empty($this->cht[$type]))
throw new Kohana_Exception('Unknown chart type :type for :class',array(':type'=>$type,':class'=>get_class($this)));
$this->chart_type = $this->cht[$type];
}
public function __set($key,$value) {
switch ($key) {
case 'div':
$this->div_mode = $value;
break;
case 'encode':
// Encoding options are t=none,s=simple,e=extend
if (! in_array($value,array('t','s','e')))
throw new Kohana_Exception('Unknown encoding type :type',array(':type'=>$value));
$this->data_encode = $value;
break;
case 'title':
$this->chart_title = $value;
break;
default:
throw new Kohana_Exception('Unknown variable :key (:value) for :class',array(':key'=>$key,':value'=>$value,':class'=>get_class($this)));
}
}
public function __toString() {
try {
return $this->render();
}
catch (Exception $e) {
echo Debug::vars($e);
}
}
/**
* Return an instance of this class
*
* @return GoogleChart
*/
public static function factory($type) {
return new GoogleChart($type);
}
public function series($data) {
// Quick Sanity check that we have the right indexes
foreach (array('data','axis') as $k)
if (! isset($data[$k]))
throw new Kohana_Exception('Missing key :key',array(':key'=>$k));
// Quick check that we have the right types
if (! in_array($data['axis'],array('x','r')))
throw new Kohana_Exception('Unknown data type :type',array(':type'=>$data['axis']));
if (is_array($data['data'])) {
foreach ($data['data'] as $title => $values) {
$this->numseries++;
$this->chartdata['legend'][$this->numseries] = $title;
$this->chartdata['axis'][$data['axis']][] = $this->numseries;
if (is_array($values))
foreach ($values as $k => $v)
$this->plotdata[$k][$data['axis']][$this->numseries] = $v;
else
throw new Kohana_Exception('Series data needs to be an array');
}
} else {
throw new Kohana_Exception('Series data needs to be an array');
}
}
private function seriescolors() {
$return = array();
$i = 0;
for ($j=0;$j<$this->numseries;$j++) {
array_push($return,$this->series_colors[$i++]);
// Reset $i if we exceed our available colors
if ($i > count($this->series_colors))
$i = 0;
}
return $return;
}
public function series_colors() {
return implode(',',$this->seriescolors());
}
public function series_chm() {
if (! isset($this->chartdata['axis']['r']))
return '';
$return = array();
$c = $this->seriescolors();
foreach ($this->chartdata['axis']['r'] as $v)
array_push($return,sprintf('%s,%s,%s,%s,%s,%s','D',$c[$v-1],$v-1,0,2,2));// @todo 'D,0,2,2' May need to be configurable
return implode('|',$return);
}
public function series_x_labels() {
// @todo Why the 0:?
return '0:|'.implode('|',array_keys($this->plotdata));
}
public function series_scaling() {
$return = array();
foreach ($this->chartdata['max'] as $k => $v)
array_push($return,sprintf('%s,%s',empty($this->chartdata['min']) ? 0 : $this->chartdata['min'],$v));
return implode(',',$return);
}
public function series_scale() {
$return = array();
$i = 1;
// @todo need to add min values
foreach ($this->chartdata['max'] as $k => $v)
array_push($return,sprintf('%s,0,%s,0',$i++,$v));
return implode('|',$return);
}
public function series_legend() {
return implode('|',$this->chartdata['legend']);
}
public function series_data() {
$return = $sreturn = $invs = array();
$sd = $max = array();
foreach ($this->plotdata as $label => $seriesdata)
foreach ($seriesdata as $type => $data) {
foreach ($data as $key => $value) {
$sd[$key][$label] = $value;
$max[$key] = $this->chartdata['max'][$type];
$invs[$type][$key] = TRUE;
}
}
foreach ($sd as $k => $v)
array_push($sreturn,$this->encode($v,$max[$k]));
// @todo This might need some more work, when used with different graph types.
if (count($invs) > 1)
$prefix = sprintf('%s:',count($invs['x']));
else
$prefix = ':';
// If encoding is text, we need to separate the series with a |
if ($this->data_encode == 't')
return $prefix.implode('|',$sreturn);
else
return $prefix.implode(',',$sreturn);
}
private function render() {
$output = '';
// Testing
if ($this->div_mode) {
$output .= '<div id="GoogleChart">GoogleChart</div>';
$output .= '<div id="showgc" style="display: inline;">';
}
// Render
$output .= sprintf('<img src="%s?%s" alt="%s">',$this->url,http_build_query($this->build()),_('Traffic Summary'));
// $output .= Debug::vars($this->build());
// $output .= Debug::vars($this->chartdata);
// $output .= Debug::vars($this->plotdata);
// Toggle the display of the chart.
// @todo This JS should be placed elsewhere for HTML validation
if ($this->div_mode) {
$output .= '</div>';
$output .= '<script type="text/javascript">$("#GoogleChart").click(function() {$(\'#showgc\').toggle();});</script>';
}
return $output;
}
public function html_table($vertical=TRUE,$class=array()) {
if (! count($this))
return sprintf('<table><tr><td>%s</td></tr></table>',_('No Data'));
$output = sprintf('<table %s>',
isset($class['table']) ? $class['table'] : 'class="google-data-table"');
if ($vertical) {
$output .= '<tr>';
$output .= '<td>&nbsp;</td>';
foreach ($this->chartdata['axis'] as $axis => $adetails)
foreach ($adetails as $k)
$output .= sprintf('<th style="text-align: right;">%s</th>',$this->chartdata['legend'][$k]);
$output .= '</tr>';
foreach ($this as $k => $v) {
$output .= '<tr>';
$output .= sprintf('<th style="text-align: right;">%s</th>',$k);
foreach ($this->chartdata['axis'] as $axis => $adetails)
foreach ($adetails as $k)
$output .= sprintf('<td style="text-align: right;">%s</td>',$v[$axis][$k]);
$output .= '</tr>';
}
// Horizontal table
} else {
$output .= '<tr>';
$output .= '<td>&nbsp;</td>';
foreach ($this as $k => $v)
$output .= sprintf('<th style="text-align: right;">%s</th>',$k);
$output .= '</tr>';
foreach ($this->chartdata['axis'] as $axis => $adetails)
foreach ($adetails as $id) {
$output .= '<tr>';
$output .= sprintf('<th style="text-align: right;">%s</th>',$this->chartdata['legend'][$id]);
foreach ($this as $k => $v)
$output .= sprintf('<td style="text-align: right;">%s</td>',$v[$axis][$id]);
$output .= '</tr>';
}
}
$output .= '</table>';
return $output;
}
/**
* Some pre-render processing
*/
private function process() {
$max = array();
// Work out our maximum number for each series.
foreach ($this->plotdata as $label => $seriesdata) {
foreach ($seriesdata as $type => $data) {
$c = 0;
foreach ($data as $value)
$c += $value;
// Scale up our max, so we get some extra space at the top of our chart.
// @todo This should be configurable.
$c *= 1.1;
if (! isset($this->chartdata['max'][$type]))
$this->chartdata['max'][$type] = $c;
else
$this->chartdata['max'][$type] = ($c > $this->chartdata['max'][$type]) ? $c : $this->chartdata['max'][$type];
}
}
}
/**
* Build the chart
*/
private function build() {
if ($this->plotdata) {
$this->process();
return array(
'chf'=>'bg,s,FFFFFF00',
'cht'=>$this->chart_type,
'chs'=>$this->chart_size,
'chtt'=>$this->chart_title,
'chbh'=>'a', // @todo This might need to be calculated, valid options (a,r);
'chg'=>'7.7,12.5,1,5', // @todo This should be calculated
'chco'=>$this->series_colors(),
'chdl'=>$this->series_legend(),
'chd'=>$this->data_encode.$this->series_data(),
// 'chds'=>$this->series_scaling(),
'chm'=>$this->series_chm(),
'chxt'=>'x,y,r', // @todo configurable?
'chxl'=>$this->series_x_labels(),
'chxr'=>$this->series_scale(),
);
} else
return array();
}
/**
* Encode the series data
*/
private function encode($data,$max=NULL) {
$table = array();
$table['simple'] = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
$table['extend'] = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.';
$size = array();
$size['simple'] = strlen($table['simple']);
$size['extend'] = strlen($table['extend']);
if (is_null($max) OR $max == 0)
$max = max($data) > 0 ? max($data) : 1;
$encode = '';
switch ($this->data_encode) {
case 't' :
return join(',',$data);
case 's' :
foreach ($data as $v)
if ($v > -1)
$encode .= substr($table['simple'],($size['simple']-1)*($v/$max),1);
else
$encode .= '_';
break;
case 'e' :
foreach ($data as $v) {
# Convert to a 0-4095 data range
$y = 4095*$v/$max;
$first = substr($table['extend'],floor($y/$size['extend']),1);
$second = substr($table['extend'],$y%$size['extend'],1);
$encode .= "$first$second";
}
break;
}
return $encode;
}
}
?>