<?php

/**
 *
 * @author Jeck Lamnent
 * @version 1.02
 * @GNU public dec 2007
 * modify it and change it as much as you'd like
 * I made it quickly for what I needed so its not optimized for a 15,000 charts per second server
 * 


 * Examples after these quick field/function descriptions

 * The chart is displayed as an image. The style sheet class is class="googleChart"

 * If your new to using classes then look at the examples section.
 * For the below "var something;" entries, instance is a references to 
 * the $blah variable in $blah=new googleChart();

 * Google charts require supplied data to be separated using commas and | pipes so I
 * keep the commas and pipes in check inside the functions. If things don't work 
 * right then look at the examples so see what to do.

 * If the supplied data is negative numbers then set negativeMode=true and load the data using ->loadData();.

 * data/labels can be arrays or strings.
 * var $barWidth=null; 					//sets the width of the bars for vert/horiz bar charts.
 * instance->barWidth=10;
 * var $negativeMode;						//Set to true if data is negative data
 * instance->negativeMode=true;
 * var $dimensions;						//dimensions are width x height
 * instance->dimensions='300x100';
 * var $colors; 							//colors are separated by commas
 * instance->colors='8888ff,4444ff'; 	//used to colour charts, can be 1 or many colour sets separated by commas
 * var $legend; 							//legend items are separated by pipes
 * instance->legend='Cows|Dogs|Buffalo';
 * var $title;								//title is plane text
 * instance->title='My chart';
 * var $fillColor;							//colors don't have # preceeding them
 * instance->fillcolor='76A4FB';
 * var $showGrid;							//if 1 tries to auto calc grid. If > 1 then show this many grid lines
 * instance->showGrid=1;
 * instance->showGrid=30;

 * f googleChart($data=null,$type=null,$title=null,$dimensions=null);
 * -data is an array or a comma separated string. 
 * 
 * f setLabels(string or array,string); default 'left'
 * -sets labels on the chart, 2nd param is 'top' 'left' 'bottom' or 'right'
 * 
 * f draw(boolean,$append='&chxr.png'); default (true,'&chxr.png')
 * -prints an img tag with the chart, if set to false, returns the chart url.
 * -The value of $append is append to the chart url. By default it will append &chxr.png so the url is detected as an image
 * 
 * f smartDataLabel(array);
 * -Adds a legend and data based on array keys
 * $dat2=array(
 * 'cows'=>array(4,5,6,7,9),
 * 'dogs'=>array(6,1,4,2,6),
 * 'peas'=>array(5.4,9,1,6,2)
 * );
 * 
 * f simpleDataMode()
 * -for 0..61 different plottable values. Default options can plot 1000 values
 * f setLabelsMinMax(integer,string); default (3,'left')
 * -
 * f setType(string) default ('line')
 * -sets the type of chart (pie,pie3d,barx,bary,line)
 * 
 ******EXAMPLES******

 * BASIC CHART:
 * $data=array('4,6,21,7,1,6,17,5,2,1,7,9'); //note arrays don't have | pipes in them
 * $moo=new googleChart($data);
 * $moo->draw();


 * CHART WITH LABEL:
 * $moo= new googleChart('4,6,21,7,1,6,17,5,2,1,7,9');
 * $moo->setLabels('june|july|aug','bottom');
 * $moo->draw();

 * CHART BASIC WITH LABELS
 * $chart1= new googleChart('4,1,6,8,1','pie','foods','300x200');
 * $chart1->setLabels('cake|pie|muffins|cookies|icecream');
 * $chart1->draw();
 * 
 * 
 * CHART WITH LABEL AND MIN/MAX VALUES ON RIGHT
 * $moo= new googleChart('4,6,21,7,1,6,17,5,2,1,7,9');
 * $moo->setLabelsMinMax(5,'right'); //call before other funcs that make labels
 * $moo->setLabels('june|july|aug','bottom');
 * $moo->draw();

 * PIE CHART WITH LABELS
 * $data='4,23,65';
 * $moo= new googleChart($data,'pie');
 * $moo->setLabels('cows|dogs|peas');
 * $moo->draw(true);
 * 
 * //or an alternative to above 'pie' you can use $moo->setType('pie');

 * CHART WITH 3 DIFFERENT DATA SETS using smartDataLabel(), A LEGEND AND MINMAX LABELS
 * $data=array(
 * 'cows'=>array(4,5,6,7,9),
 * 'dogs'=>array(6,1,4,2,6),
 * 'peas'=>array(5.4,9,1,6,2)
 * );
 * $moo= new googleChart();
 * $moo->smartDataLabel($data);
 * $moo->setLabelsMinMax(5,'left'); 
 * $moo->draw(true);

 * CHART USING NEGATIVE NUMBERS
 * $chart=new googleChart(null,$chartType); //don't load data yet
 * $chart->negativeMode=true; //set negative mode first
 * $chart->loadData($chartData); //then load data
 * $chart->setLabelsMinMax();
 * $chart->draw();

 * CHART WITH LOTS OF OPTIONS, data sets are supplied in a string 
 * $data='1,4,6,8,2|3,7,8,3,2';
 * $chart1=new googleChart($data);
 * $chart1->dimensions='400x125';
 * $chart1->fillColor='76A4FB';
 * $chart1->setLabelsMinMax();
 * $chart1->legend='Ammount of cookies|Raisens';
 * $chart1->setLabels('jan|feb|march|apr|may','bottom');
 * $chart1->setLabels('+low|mid|top','left');
 * $chart1->showGrid=1;
 * $chart1->draw();

 * CHART USING NEGATIVE NUMBERS AND GRID
 * $month='May';
 * $daysInMonth=31;
 * $chartDates='';
 * for ($i=1;$i<=$daysInMonth;$i++) {
 * if ($chartDates) $chartDates.='|';
 * $chartDates.=$i;
 * }
 * $chartData='0,0,-109.9,0,0,0,-12310.58,-478.5,0,0,-473.38,0,0,0,-398,0,0,0,0,0,-134.6,-1513.1,0,0,-454.26,0,0,0,0,-1258.04,-2237.8';
 * $chart=new googleChart(null,'line','Expenses','700x250');
 * $chart->negativeMode=true; //values are negative
 * $chart->loadData($chartData);
 * $chart->barWidth=10; //makes bars thinner
 * $chart->setLabelsMinMax();
 * $chart->setLabels($chartDates,'bottom');
 * $chart->setLabels($month,'bottom');
 * $chart->showGrid=1;
 * $chart->draw();
 * 
 * 
 * 
 * 
 * 
 * 
 * 
 * TO ADD A LEGEND MANUALLY:
 * $moo->legend='cows|dogs|carrots|rubber';

 * CHART WITH OTHER FUNCTIONS

 * To convert data to simple mode (for smaller data sets I guess. I don't see a purpose)
 * $moo->simpleDataMode();
 * 
 * To changes some colors (you can supply more than needed)
 * $moo->colors='8888ff,4444ff,670032';
 */


/**
 * Displays a visual data chart
 * $data can be an array of values from 0.0 to 100.0 or a comma separated string
 * Separate multiple data sets with |
 * Put -1 for any missing values
 * If the last parameter set is 'noreturn' then the chart is output as an image
 * EXAMPLE: googleChart('4,6,2.3,5|7,9.2,1,12','noreturn');
 * 
 * @param string or array $data
 * @param string $type
 * @param string $label
 * @param string $dimensions
 */
class googleChart {
    var $url = 'http://chart.apis.google.com/chart?';
    var $simple = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; //simple data chart
    var $numericBase = 't'; //'t' = numeric 0-1000 url data, 's' = simple 0..61 data
    var $dimensions = '450x200';
    var $data = ''; //array of 0.0 to 100.0 or comman separated string (use -1 for missing values)
    var $type = 'lc'; //type of chart
    var $title; //chart's title
    var $legend = ''; //data description legend
    var $labels = ''; //for pie charts
    var $minMaxLabel = false; //is turned on via function
    var $lineLabels = ''; //for non pie charts
    var $lineLabelAxis = ''; //for non pie charts
    var $labelCount = 0; //for non pie chart labels
    var $colors = '0088ff,8800ff,4444ff'; //colors for multiple sets
    var $fillColor = ''; //used to paint fill. hex rgb ('8054a1')
    var $showGrid = ''; //if more than one data set it wiil auto create a grid, else will place (int)showGrid gridlines
    var $max = 100; //is autoset when data is supplied
    var $min = 0; //is autoset when data is supplied
    var $negativeMode = false; //negative mode converts numbers to positive so google can display them
    var $barWidth = null; //sets the width of the bars for vert/horiz bar charts.

    function googleChart($data = null, $type = null, $title = null, $dimensions = null) {
        if ($data) $this->loadData($data);
        if ($type) $this->setType($type);
        if ($title) $this->title = $title;
        if ($dimensions) $this->dimensions = $dimensions;
    }

    /**
     * Draw chart or return chart
     *
     * @param string_or_boolean $noReturn
     * @return string
     */
    function draw($noReturn = true, $append = '&chxr.png') {
        //convert arrays
        if ($this->numericBase == 't') $this->normalDataMode();
        if (is_array($this->data)) $this->data = implode(',', $this->data);
        if (is_array($this->legend)) $this->legend = implode('|', $this->legend);
        //build chart url
        $return = $this->url . 'chs=' . $this->dimensions;
        $return .= '&cht=' . $this->type; //chart type
        if ($this->title) $return .= '&chtt=' . str_replace(' ', '+', $this->title); //chart label
        if ($this->legend) $return .= '&chdl=' . $this->legend;
        if ($this->labels) $return .= '&chl=' . $this->labels;
        if ($this->barWidth) $return .= '&chbh=' . $this->barWidth;
        if ($this->minMaxLabel) {

        }
        if ($this->lineLabelAxis) {
            $this->lineLabelAxis = rtrim($this->lineLabelAxis, '|');
            $return .= '&chxt=' . $this->lineLabels . '&chxl=' . $this->lineLabelAxis;
        }
        if ($this->fillColor) $return .= '&chm=B,' . $this->fillColor . ',0,0,0';
        if ($this->showGrid) {
            if (($gridx = $this->showGrid) == 1) {
                $items = substr_count($this->data, ',');
                $sets = substr_count($this->data, '|') + 1;
                $items += $sets;
                if ($sets >= 1) $gridx = round($items / $sets); //get count of items per set
                else  $gridx = 0;
                if ($gridx == 0) $gridx = $this->showGrid;
                if ($this->type == 'lc') $gridx--;
            }
            if ($this->minMaxLabel) $gridy = round(100 / ($this->minMaxLabel));
            else  $gridy = 25;
            $return .= '&chg=' . (floor(10000 / $gridx) / 100) . ',' . $gridy . ',1';
        }
        $return .= '&chco=' . $this->colors;
        $return .= '&chd=' . $this->numericBase . ':' . $this->data; //chart data
        $return .= '&chxr=';
        if ($noReturn) echo '<img class="googleChart" src="' . $return . $append .
                '" />';
        else  return $return . $append;
    }

    /**
     * Sets the type of chart
     *
     * @param unknown_type $type
     */
    function setType($type) {
        switch ($type) {
            case 'barx':
            case 'bhg':
                $type = 'bhg';
                break; //horizontal bars
            case 'bary':
            case 'bvg':
                $type = 'bvg';
                break; //vertical bars
            case 'pie':
            case 'p':
                $type = 'p';
                break; //pie chart
            case 'pie3d':
            case 'p3':
                $type = 'p3';
                break; //3d pie chart
            case 'line':
            case 'lc': //line graph
            default:
                $type = 'lc';
        }
        $this->type = $type;
    }

    /**
     * Put labels on the sides of the chart. This must be called first before any other labels
     *
     */
    function setLabelsMinMax($spread = 3, $side = 'left') {
        $this->minMaxLabel = $spread - 1;
        $this->setLabels('minmax', $side);
    }

    /**
     * Put labels on the sides of the chart
     *
     * @param array or string $labels
     * @param string(left,right,bottom,top) $side
     */
    function setLabels($labels, $side = 'left') {
        if (is_array($labels)) $labels = implode('|', $labels);
        $labels = str_replace(' ', '+', $labels);
        switch ($side) {
            case 'top':
                $type = 't';
                break;
            case 'left':
                $type = 'y';
                break;
            case 'bottom':
                $type = 'x';
                break;
            case 'right':
                $type = 'r';
                break;
        }
        if ($this->type[0] == 'p') { //for pie charts
            if ($labels == 'minmax') { //for auto settings min/max range
                $this->labels = '';
                $xLabel = round($this->max / $this->minMaxLabel);
                for ($i = 0; $i++; $i <= $xLabel) {
                    if ($this->labels) $this->labels .= '|';
                    $this->labels .= $xLabel * $i;
                }

            } else
                if ($this->labels) $this->labels .= '|'; //do pie chart label
            $this->labels .= $labels;

        } else { //non pie charts
            if ($labels == 'minmax') { //auto set min/max range
                $this->lineLabels = '';
                $this->labels = '';

                if ($this->minMaxLabel == 0) $this->minMaxLabel = 1;

                $xLabel = round($this->max / $this->minMaxLabel);
                $this->lineLabels = $type; //set side to display on
                $this->lineLabelAxis = '0:';
                if ($this->lineLabelAxis != '') $this->lineLabelAxis .= '|';
                for ($i = 0; $i <= ($this->minMaxLabel); $i++) {

                    $this->lineLabelAxis .= ($xLabel * $i);
                    $this->lineLabelAxis .= '|';
                }
                $this->labelCount++;
            } else {
                if ($this->lineLabels) $this->lineLabels .= ','; //don't separate if empty
                $this->lineLabels .= $type;
                $this->lineLabelAxis .= (string )$this->labelCount . ':|' . $labels . '|'; //strip trailing pipe in draw proc
                $this->labelCount++;
            }
        }
    }

    /**
     * Is auto loaded, don't need to call this func
     *
     * @param string or array $data
     */
    function loadData($data) {
        if (is_array($data)) $data = implode(',', $data);
        $this->data .= $data;
        //determine min/max
        $data = str_replace('|', ',', $data);
        $data = explode(',', $data);
        if ($this->negativeMode)
            foreach ($data as & $datum) $datum = abs($datum);
        $this->max = max($data);
        $this->min = min($data);

    }

    /**
     * Sets mode to Allow for 1000 different values. Is default. Don't need to call this func
     *
     */
    function normalDataMode() {
        $delta = $this->max / 100; //convert to 0.0 to 100.0
        if ($delta == 0) $delta = 1; //stops div by 0
        $data = explode('|', $this->data);
        $newData = '';
        foreach ($data as $datum) {
            $dataInner = explode(',', $datum);
            if ($newData) $newData .= '|';
            $innerData = '';
            foreach ($dataInner as $item) {
                if ($item === -1) $val = $item; //missing val
                else
                    if ($item === 0) $val = (-1); //missing val
                    else {
                        if ($this->negativeMode) $item = abs($item);
                        $val = round(($item * 10) / $delta) / 10;
                        if ($val > 100) $val = 100; //limit val.
                    }
                    if ($innerData != '') $innerData .= ',';
                $innerData .= $val;
            }
            $newData .= $innerData;

        }
        echo '<br/>';
        if ($newData) {
            $this->numericBase = 't'; //simple mode
            $this->data = $newData;
        }
    }

    /**
     * Sets the chart mode to simple (62 different values. Only call this mode if data was already specified
     *
     */
    function simpleDataMode() { //values 0 to 61;
        $delta = $this->max / strlen($this->simple); //convert to 0..61
        if ($delta == 0) $delta = 1; //stops div by 0
        $data = explode('|', $this->data);
        $newData = '';
        foreach ($data as $datum) {
            $dataInner = explode(',', $datum);
            if ($newData) $newData .= ',';
            foreach ($dataInner as $item) {
                if ($item == -1) $newData .= '_'; //missing val
                elseif ($item === 0) $newData .= '_'; //missing val
                else {
                    if ($this->negativeMode) $item = abs($item);
                    $val = round(($item * 10) / $delta) / 10;
                    if ($val > 61) $val = 61; //limit val.
                    $newData .= $this->simple[$val];
                }
            }
        }
        if ($newData) {
            $this->numericBase = 's'; //set simple mode
            $this->data = $newData;
        }
    }

    function smartDataLabel($labelData) {
        $newData = '';
        foreach ($labelData as $label => $data) {
            $this->legend .= str_replace(' ', '+', $label) . '|';
            $label = str_replace(' ', '+', $label);
            if (is_array($data)) $this->data .= implode(',', $data) . '|';
            else  $this->data .= $data . '|';
        }
        $this->legend = rtrim($this->legend, '|'); //trim trailing pipe
        $this->loadData(rtrim($this->data, '|')); //load Data
    }


}

?>