Location: PHPKode > scripts > SVGraph > svgraph/SVGraph.php
<?php

/**
 * This is a PHP port of javascript SVGraph -
 * a class for creating SVG-diagrams displaying numeric $data.
 */

class SVGraph {
	
	/**
	 * @var int
	 */
	static private $_id = 1;
	private $id;
	
	/**
	 * Graphic width & height (together with all the legends, margins, paddings etc)
	 *
	 * @var int
	 */
	protected $outerWidth, $outerHeight;
	
	/**
	 * Paddings
	 *
	 * @var int
	 */
	protected $paddingLeft, $paddingBottom, $paddingTop, $paddingRight;
	
	/**
	 * Width & height of the chart itself
	 *
	 * @var int
	 */
	protected $innerWidth, $innerHeight;
	
	/**
	 * points on the X-axis of the graph
	 *
	 * @var mixed[]
	 */
	protected $xdata = array();
	
	/**
	 * points on the Y-axis of the graph
	 *
	 * @var array[] (each item is an array of points for multiple charts)
	 */
	protected $ydata = array();
	
	/**
	 * legends for each line of the chart
	 *
	 * @var string[] (ChartNo => Legend)
	 */
	protected $legends = array();
	
	/**
	 * min/max values
	 *
	 * @var float
	 */
	protected $min, $max;
	
	/**
	 * Scale factor
	 * @var float
	 */
	protected $sf;
	
	/**
	 * Factors to be [optionally] applied to values of the charts
	 *
	 * @var float[] (ChartNo => Factor)
	 */
	protected $factors = array();
	/**
	 * Colors for the chart lines.
	 * if you have to render more than 10 lines, use setColor() to add your own colors.
	 * You may as well overwrite default colors with setColor()
	 *
	 * @var string[]
	 */
	protected $colors = array('red', 'green', 'blue', 'orange', 'magenta', 'darkblue', 'maroon', 'indigo', 'peru', 'teal');
	
	/**
	 * Constructor
	 *
	 * @param int $width
	 * @param int $height
	 */
	function __construct($width, $height)
	{
		$this->outerWidth = $width;
		$this->outerHeight = $height;
		$this->setDefaultPaddings();
		$this->id = self::$_id++;
	}
	
	/**
	 * Import user $data
	 * @param Arrayarray(); - an array of $data-records, each record being itself an array like this: [x, y1, y2,.. ,yN]
	 */
	function load($data) {
		// reset $data.
		// NOTE: current setup (factors, legends, colors is NOT reset!!!)
		$this->ydata = array();
		$this->xdata = array();
		$this->max = null;
		$this->min = null;// @todo: use this for zero-point to make layout more flexible
		$this->sf = null;
		for ($i = 0; $i < count($data); ++$i)
		{
			$this->xdata[$i] = $data[$i][0];// X-axis
			for ($j = 1; $j < count($data[$i]); ++$j)
			{
				// Y-axis - for all lines we have to render
				$v = (float) $data[$i][$j];
				if (array_key_exists($j - 1, $this->factors))
				{
					$v = $v / $this->factors[$j - 1];
				}
				if (($this->max == null) || ($v > $this->max))
				{
					$this->max = $v;
					$this->sf = $v / $this->innerHeight;
				}
				if (count($this->ydata) < $j)
				{
					$this->ydata[$j - 1] = array();
				}
				$this->ydata[$j - 1][$i] = $v;
			}
		}
		return $this;
	}
	/**
	 * Set paddings.
	 * 
	 * Each value may come in the form of
	 * - integer: number of pixels
	 * - float: a part of outer size
	 * - string (N%): a percentage of outer size
	 *
	 * @param int | float | string $top
	 * @param int | float | string $right
	 * @param int | float | string $bottom
	 * @param int | float | string $left
	 */
	function setPaddings($top, $right, $bottom, $left)
	{
		$top = $this->absolutePadding($top, $this->outerHeight);
		$right = $this->absolutePadding($right, $this->outerWidth);
		$bottom = $this->absolutePadding($bottom, $this->outerHeight);
		$left = $this->absolutePadding($left, $this->outerWidth);
		$this->paddingLeft = $left;
		$this->paddingBottom = $bottom;
		$this->paddingRight = $right;
		$this->paddingTop = $top;
		$this->innerWidth = $this->outerWidth - $left - $right;
		$oldInnerHeight = $this->innerHeight;
		$this->innerHeight = $this->outerHeight - $top - $bottom;
		// update scale factor
		if (($oldInnerHeight != $this->innerHeight) && null !== $this->sf)
		{
			$this->sf *= ($this->innerHeight / $oldInnerHeight);
		}
	}
	private function absolutePadding($value, $outerSize)
	{
		if (is_numeric($value))
		{
			if ($value < 1)
			{// assume float
				return $value * $outerSize;
			} else {
				return $value;
			}
		} else {
			//assume percentage
			return ($value * $outerSize) / 100;
		}
	}
	function setDefaultPaddings()
	{
		$this->setPaddings(.1, .1, .25, .15);
	}
	/**
	 * Set legend for line #n
	 * @param int n
	 * @param string legend
	 */
	function setLegend($n, $legend) {
		$this->legends[$n] = $legend;
		return $this;
	}
	/**
	 * Set factor for line #n - so that every $data value is devided by this factor before being rendered
	 * @param int n
	 * @param numeric factor
	 */
	function setFactor($n, $factor) {
		$this->factors[$n] = $factor;
		return $this;
	}
	/**
	 * Set color for line #n
	 * @param int n
	 * @param string color
	 */
	function setColor($n, $color) {
		$this->colors[$n] = $color;
		return $this;
	}
	/**
	 * Render graphic
	 */
	function render() {
		if (1 >= count($this->xdata))
		{// nothing to render
			return;
		}
		// root SVG element
		?><svg xmlns="http://www.w3.org/2000/svg" 
			version="1.1" 
			width="<?php echo $this->outerWidth; ?>" 
			height="<?php echo $this->outerHeight; ?>" ><?php
		
		$xstep = round($this->innerWidth / (count($this->xdata) - 1));
		
		$this->renderGrid($xstep);
		
		// main cycle
		for ($j = 0; $j < count($this->ydata); ++$j)
		{
			$c = $this->getColor($j);
			// wrap every line-set in a group element,
			// so that we are able to menipulate with it via scritp later
			$legend = $this->getLegend($j);
			?><g id="g-<?php echo $this->id . "-".$j; ?>" title="<?php echo $legend; ?>" style="display:block;"><?php
			?><title><?php echo $legend; ?></title><?php
			for ($i = 0; $i < count($this->xdata); ++$i) {
				if ($i) {
					// create a line part
					$x1 = ($i - 1) * $xstep;
					$x2 = $i * $xstep;
					$y1 = $this->ydata[$j][$i - 1];
					$y2 = $this->ydata[$j][$i];
					$this->line($x1, $y1, $x2, $y2, "stroke:" . $c . ";stroke-width:2;");
					
					// $data point <title>
					$displayedValue = $this->ydata[$j][$i];
					if (array_key_exists($j, $this->factors))
					{
						$displayedValue *= $this->factors[$j];
					}
					$text = $this->getLegend($j, false) . " = " . $displayedValue;
					
					// create $data point
					?><circle r="5" cx="<?php echo $this->x($x2); ?>" 
						cy="<?php echo $this->y($y2);?>" style="stroke:none;fill:<?php echo $c; ?>;cursor:pointer;" 
						title="<?php echo $text; ?>"><?php
					
					?><title><?php echo $text; ?></title><?php
					?></circle><?php
				}
			}
			?></g><?php
		}
		?></svg><?php
	}
	// @access private
	function y($v) {
		return $this->outerHeight - ($this->paddingBottom + ($v / $this->sf));
	}
	function x($x)
	{
		return $this->paddingLeft + $x;
	}
	// @access private
	private function line($x1, $y1, $x2, $y2, $style) {
		?><line x1="<?php echo $this->x($x1); ?>" y1="<?php echo $this->y($y1); ?>" 
			x2="<?php echo $this->x($x2); ?>" y2="<?php echo $this->y($y2); ?>" 
			style="<?php echo $style; ?>" /><?php
	}
	// @access private
	function getLegend($index, $respectFactor = true) {
		$l = array_key_exists($index, $this->legends) ? $this->legends[$index] : 'Chart #' + $index;
		if ($respectFactor && array_key_exists($index, $this->factors))
		{
			$l .= " (x" . $this->factors[$index] . ")";
		}
		return htmlspecialchars($l, ENT_QUOTES);
	}
	// @access private
	private function getColor($index) {
		return ($index < count($this->colors)) ? $this->colors[$index] : 'black';
	}
	// @access private
	private function renderGrid($xstep) {
		// X-Y axis
		$this->line(0, 0, 0, $this->innerHeight * $this->sf, 'stroke:black;');
		$this->line(0, 0, $this->innerWidth, 0, 'stroke:black;');
		
		// vertical grid
		for ($i = 0; $i < count($this->xdata); ++$i)
		{
			$x = $i * $xstep;
			$tx = $this->x($x);
			$ty = $this->outerHeight - $this->paddingBottom + 10;
			
			?><text x="<?php echo $tx; ?>" y="<?php echo $ty; ?>" 
				transform="rotate(45,<?php echo $tx; ?>,<?php echo $ty; ?>)" 
				style="font-size:10;"><?php echo htmlspecialchars($this->xdata[$i], ENT_QUOTES); ?></text><?php
			
			$this->line($x, 0, $x, $this->innerHeight, 'stroke:black;stroke-width:.2;');
		}
		
		// horizontal grid
		$ystep = pow(10, floor(log($this->max, 10)));
		for ($j = $ystep; $j <= $this->max; $j += $ystep)
		{
			$y = $this->y($j);
			?><text x="1" y="<?php echo $this->y($j); ?>"><?php echo $j; ?></text><?php
			$this->line(0, $j, $this->innerWidth, $j, 'stroke:black;stroke-width:.2;');
		}
	}
	
	/**
	 * Get $this instance ID
	 */
	function getId()
	{
		return $this->id;
	}
}
Return current item: SVGraph