Bar Graph Class (No GD Required - 100% CSS based)

Small, short code snippets that other people may find useful. Do you have a good regex that you would like to share? Share it! Even better, the code can be commented on, and improved.

Moderator: General Moderators

User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Bar Graph Class (No GD Required - 100% CSS based)

Post by Chris Corbyn »

Please note that this has now moved to Sourceforge http://sourceforge.net/projects/cssgrapher

Code: Select all

<?php

/*
 A 100% CSS, XHTML 1.1 Compliant Bar Graphing tool
 -- No need to use GD, Renders quickly, Interactive charts,
 -- - Very customizable.
 
 Compatibility: PHP >= 5.0
 
 This class is not licensed, do with it what you will 
 (But please give the author credit where due)
 
 Author: d11wtq (Chris Corbyn)
 Date: 2005-10-06
 */

class BarGraph
{
	//Title
	private $ShowTitle = false;						//Show the graph title
	private $TitleText = 'New Bar Graph';			//Graph title
	private $TitleFontColor = '#000000';			//Graph title color
	private $TitleFontWeight = 'bold';				//Graph title font weight
	private $TitleFontStyle = 'normal';				//Graph title font style
	private $TitleTextDecoration = 'underline';		//Graph title text decoration
	private $TitleFontFamily = 'arial,sans-serif';	//Graph title font family
	private $TitleFontSize = '1.0em';				//Graph title font size
	private $TitleAlign = 'center';					//Title left, right or center
	private $ShowTitleBackground = false;			//Show graph title background
	private $TitleBackgroundColor = '#CCCCFF';		//Graph title background color
	
	//General
	private $XDimension = 0;						//Width
	private $YDimension = 0;						//Height
	
	private $ShowBorder = false;					//Show graph border
	private $LBorderColor = '#CCCCCC';				//Graph left-border color
	private $RBorderColor = '#CCCCCC';				//Graph right-border color
	private $TBorderColor = '#CCCCCC';				//Graph top-border color
	private $BBorderColor = '#CCCCCC';				//Graph bottom-border color
	private $LBorderStyle = 'solid';				//Graph left-border style
	private $RBorderStyle = 'solid';				//Graph right-border style
	private $TBorderStyle = 'solid';				//Graph top-border style
	private $BBorderStyle = 'solid';				//Graph bottom-border style
	private $LBorderWidth = '1px';					//Graph left-border width
	private $RBorderWidth = '1px';					//Graph right-border width
	private $TBorderWidth = '1px';					//Graph top-border width
	private $BBorderWidth = '1px';					//Graph bottom-border width
	private $ShowBackground = false;				//Show Graph background
	private $BackgroundColor = '#F4F4F4';			//Graph background color
	private $BackgroundImage = '';					//Graph background Image
	private $BackgroundRepeat = '';					//Background repeat (x, y, no-repeat)
	private $BackgroundPosition = '';				//Background position
	
	private $BarDirection = 'v';					//Vertical or horizontal bars
	
	//Bars
	private $ShowBars = true;
	private $BarBackgroundColor = '#DDDDDD';		//Bar background color
	private $BarBorderColor = '#000000';			//Bar border color
	private $BarBorderWidth = '1px';				//Bar border width
	private $BarBorderStyle = 'solid';				//Bar border style
	private $Bars = array();
	
	//Axes
	private $ShowXAxis = true;						//Show X-axis
	private $XAxisColor = '#BBBBBB';				//X-axis color
	private $XAxisWidth = '1px';					//X-axis width
	private $XAxisStyle = 'solid';					//X-axis style
	
	private $ShowAxisLabels = true;					//Show X-axis labels
	private $AxisLabels = array();
	private $AxisLabelFontColor = '#000000';		//X-axis font color
	private $AxisLabelFontWeight = 'normal';		//X-axis font weight
	private $AxisLabelFontStyle = 'normal';		//X-axis font style
	private $AxisLabelTextDecoration = 'none';		//X-axis text decoration
	private $AxisLabelFontFamily = 'arial,sans-serif'; //X-axis font family
	private $AxisLabelFontSize = '0.8em';			//X-axis font size
	
	private $ShowXAxisLegend = false;				//Show legend on X-axis
	private $XAxisLegendText = 'X Axis';			//X-axis legend text
	private $XAxisLegendFontColor = '#000000';		//X-axis legend font color
	private $XAxisLegendFontWeight = 'normal';		//X-axis legend font weight
	private $XAxisLegendFontStyle = 'italic';		//X-axis legend font style
	private $XAxisLegendTextDecoration = 'none';	//X-axis legend text decoration
	private $XAxisLegendFontFamily = 'arial,sans-serif'; //X-axis legend font family
	private $XAxisLegendFontSize = '0.8em';			//X-axis legend font size
	private $XAxisLegendPosition = 'right';
	
	/* ----------------- */
	
	private $ShowYAxis = true;						//Show Y-axis
	private $YAxisColor = '#BBBBBB';				//See X-axis comment
	private $YAxisWidth = '1px';					//See X-axis comment
	private $YAxisStyle = 'solid';					//See X-axis comment
	
	private $ShowAxisValues = false;				//Show Y-axis labels
	private $AxisValuesFontColor = '#000000';		//See X-axis comment
	private $AxisValuesFontWeight = 'normal';		//See X-axis comment
	private $AxisValuesFontStyle = 'normal';		//See X-axis comment
	private $AxisValuesTextDecoration = 'none';		//See X-axis comment
	private $AxisValuesFontFamily = 'arial,sans-serif'; //See X-axis comment
	private $AxisValuesFontSize = '0.8em';			//See X-axis comment
	
	private $ShowYAxisLegend = false;				//See X-axis comment
	private $YAxisLegendText = 'Y Axis';			//See X-axis comment
	private $YAxisLegendFontColor = '#000000';		//See X-axis comment
	private $YAxisLegendFontWeight = 'normal';		//See X-axis comment
	private $YAxisLegendFontStyle = 'italic';		//See X-axis comment
	private $YAxisLegendTextDecoration = 'none';	//See X-axis comment
	private $YAxisLegendFontFamily = 'arial,sans-serif'; //See X-axis comment
	private $YAxisLegendFontSize = '0.8em';			//See X-axis comment
	
	//Stacks
	private $Vals = array();
	private $BarLengths = array();
	
	//Other graph related propeties
	private $XAxisSize = 0;
	private $YAxisSize = 0;
	private $BarWidth = 'auto';
	private $FFSize;
	private $IESize;
	
	function __construct($w, $h)
	{
		if (is_integer($w)) $this->XDimension = $w;
		else exit('Parameter 1 in object constructor must be an integer: '.gettype($w).' given');
		
		if (is_integer($h)) $this->YDimension = $h;
		else exit('Parameter 2 in object constructor must be an integer: '.gettype($h).' given');
	}
	
	//Overloading to cut down on setters - it does sanity check though
	function __call($method, $args)
	{
		if (isset($this->{$method})
		&& gettype($this->{$method}) != 'boolean'
		&& gettype ($this->{$method}) != 'array') //We only want to apply to our string/numeric properties
		{
			if (sizeof($args) == 1) //All of our methods only set one property
			{
				$this->CheckError($args[0], __CLASS__.'::'.$method); //Sanity check (is string or number)
				$this->{$method} = $args[0];
			}
			else
			{
				exit('Wrong parameter count given for '.__CLASS__.'::'.$method.': Expecting 1: '.count($args).' given');
			}
		}
		else
		{
			exit ('Undefined method '.__CLASS__.'::'.$method.' Requested');
		}
	}
	
	private function ThrowError($f, $t) //(Method, value)
	{
		exit('<b>Invlaid argument given</b> in '.$f.'. <b>Parameter must be a string or numeric:</b> '.gettype($t).' given');
	}
	
	private function CheckError($x, $y) //(Value, method)
	{
		if (!is_string($x) && !is_numeric($x)) $this->ThrowError($y, $x);
	}
	
	//Some real setters
	public function ShowTitle($x=true)
	{
		$this->{__FUNCTION__} = $x;
	}
		
	public function ShowTitleBackground($x=true)
	{
		$this->{__FUNCTION__} = $x;
	}
	
	public function ShowBorder($x=true)
	{
		$this->{__FUNCTION__} = $x;
	}
	
	public function BorderColor($t)
	{
		$this->CheckError($t, __METHOD__);
		$this->LBorderColor = $t;
		$this->RBorderColor = $t;
		$this->TBorderColor = $t;
		$this->BBorderColor = $t;
	}
	
	public function BorderWidth($t)
	{
		$this->CheckError($t, __METHOD__);
		$this->LBorderWidth = $t;
		$this->RBorderWidth = $t;
		$this->TBorderWidth = $t;
		$this->BBorderWidth = $t;
	}
	
	public function BorderStyle($t)
	{
		$this->CheckError($t, __METHOD__);
		$this->LBorderStyle = $t;
		$this->RBorderStyle = $t;
		$this->TBorderStyle = $t;
		$this->BBorderStyle = $t;
	}
	
	public function ShowBackground($x=true)
	{
		$this->{__FUNCTION__} = $x;
	}
	
	public function ShowBars($x=true)
	{
		$this->{__FUNCTION__} = $x;
	}
	
	public function ShowAxes($x=true)
	{
		$this->ShowXAxis($x);
		$this->ShowYAxis($x);
	}
	
	public function AxisColor($t)
	{
		$this->CheckError($t, __METHOD__);
		$this->XAxisColor = $t;
		$this->YAxisColor = $t;
	}
	
	public function ShowXAxis($x=true)
	{
		$this->{__FUNCTION__} = $x;
	}
	
	public function ShowAxisLabels($x=true)
	{
		$this->{__FUNCTION__} = $x;
	}
	
	public function ShowXAxisLegend($x=true)
	{
		$this->{__FUNCTION__} = $x;
	}
	
	public function ShowYAxis($x=true)
	{
		$this->{__FUNCTION__} = $x;
	}
	
	public function ShowAxisValues($x=true)
	{
		$this->{__FUNCTION__} = $x;
	}
	
	public function ShowYAxisLegend($x=true)
	{
		$this->{__FUNCTION__} = $x;
	}
	
	//Graphing methods
	
	/*
	 Add some data to the graph
	 */
	public function PushValues($x, $assoc=false) //Arrays to insert
	{
		if (is_array($x)) //Sanity check
		{
			foreach ($x as $k => $val)
			{
				if ($assoc) $this->Vals[$k] = $val;
				else array_push($this->Vals, $val);
			}
		}
		else exit('Invalid parameter 1 given in '.__METHOD__.'. Expecting array: '.gettype($x).' given');
	}
	
	//Labels for the bar points
	public function PushLabels($x, $assoc=false)
	{
		if (is_array($x))
		{
			foreach ($x as $k => $val)
			{
				if ($assoc) $this->AxisLabels[$k] = $val;
				else array_push($this->AxisLabels, $val);
			}
		}
		else exit('Invalid argument given in '.__METHOD__.'. Expecting array: '.gettype($x).' given');
	}
	
	public function PushBarBackgroundColors($x, $a=false)
	{
		if (is_array($x))
		{
			foreach ($x as $k => $val)
			{
				if ($a) 
				{
					if (!is_array($this->Bars[$k])) $this->Bars[$k] = array();
					$this->Bars[$k]['BackgroundColor'] = $val;
				}
				else array_push($this->Bars, array('BackgroundColor' => $val));
			}
		}
		else exit('Invalid argument given in '.__METHOD__.'. Expecting array: '.gettype($x).' given');
	}
	
	public function PushBarBorderWidths($x, $a=false)
	{
		if (is_array($x))
		{
			foreach ($x as $k => $val)
			{
				if ($a) 
				{
					if (!is_array($this->Bars[$k])) $this->Bars[$k] = array();
					$this->Bars[$k]['BorderWidth'] = $val;
				}
				else array_push($this->Bars, array('BorderWidth' => $val));
			}
		}
		else exit('Invalid argument given in '.__METHOD__.'. Expecting array: '.gettype($x).' given');
	}
	
	public function PushBarBorderStyles($x, $a=false)
	{
		if (is_array($x))
		{
			foreach ($x as $k => $val)
			{
				if ($a) 
				{
					if (!is_array($this->Bars[$k])) $this->Bars[$k] = array();
					$this->Bars[$k]['BorderStyle'] = $val;
				}
				else array_push($this->Bars, array('BorderStyle' => $val));
			}
		}
		else exit('Invalid argument given in '.__METHOD__.'. Expecting array: '.gettype($x).' given');
	}
	
	public function PushBarBorderColors($x, $a=false)
	{
		if (is_array($x))
		{
			foreach ($x as $k => $val)
			{
				if ($a) 
				{
					if (!is_array($this->Bars[$k])) $this->Bars[$k] = array();
					$this->Bars[$k]['BorderColor'] = $val;
				}
				else array_push($this->Bars, array('BorderStyle' => $val));
			}
		}
		else exit('Invalid argument given in '.__METHOD__.'. Expecting array: '.gettype($x).' given');
	}
	
	//Takes a multidimensional associative array "attribute => value"
	// e.g. PushBarAttributes(array('onclick' => 'alert("Foo")'), array('style' => 'opacity: 0.5', 'onmouseover' => 'foo()'), array('id' => 'some_element'))
	// or .. PushBarAttributes(3 => array('onclick' => 'alert("Foo")'), 7 => array('style' => 'opacity: 0.5', 'onmouseover' => 'foo()')) .. for known indices
	public function PushBarAttributes($x, $a=false)
	{
		if (is_array($x))
		{
			foreach ($x as $k => $arr)
			{
				if (!is_array($arr)) exit('Invalid argument given in '.__METHOD__.'. Expecting 2D array: '.gettype($k).' given for element '.$k);
				if ($a)
				{
					if (!is_array($this->Bars[$k])) $this->Bars[$k] = array();
					if (!is_array($this->Bars[$k]['Attributes'])) $this->Bars[$k]['Attributes'] = array();
					foreach ($arr as $at => $val)
					{
						$this->Bars[$k]['Attributes'][$at] = $val;
					}
				}
				else
				{
					array_push($this->Bars, array('Attributes' => $arr));
				}
			}
		}
		else exit('Invalid argument given in '.__METHOD__.'. Expecting array: '.gettype($x).' given');
	}
	
	private function GetBarWidth($d)
	{
		if ($d === 0)
		{
			//if (empty($this->XAxisLabels)) $this->XAxisLabels = $this->XVals;
			if (count($this->Vals) > 0)
			{
				$count = count($this->Vals);
				$virtBars = $count + (($count+1)/2); //Imaginary bars
				$width = floor(($this->XAxisSize*0.98)/$virtBars); //The 0.98 is an IE safeguard for floating divs stying in place
				return $width;
			}
			else return false;
		}
		elseif($d === 1)
		{
			if (count($this->Vals) > 0)
			{
				$count = count($this->Vals);
				$virtBars = $count + (($count+1)/2); //Imaginary bars
				$height = floor($this->YAxisSize*0.92/$virtBars);
				return $height;
			}
			else return false;
		}
	}
	
	public function Create($ret=false) //Return or echo the graph
	{
		if (empty($this->Vals)) exit('Cannot render graph without any data. Use '.__CLASS__.'::PushValues to insert data');
		
		if (strtolower($this->BarDirection) == 'v'
		|| strtolower($this->BarDirection) == 'vertical'
		|| $this->BarDirection === 0)
		{
			if ($ret) return $this->RenderVertical();
			else echo $this->RenderVertical();
		}
		elseif (strtolower($this->BarDirection) == 'h'
		|| strtolower($this->BarDirection) == 'horizontal'
		|| $this->BarDirection === 1)
		{
			if ($ret) return $this->RenderHorizontal();
			else echo $this->RenderHorizontal();
		}
		else
		{
			exit('Cannot render graph. BarDirection must be one of "v", "vertical", 0 or "h", "horizontal", 1');
		}
	}
	
	private function RenderHorizontal()
	{
		$Graph = '<div style="overflow: hidden; text-align: left; width: '.$this->XDimension.'px; height: '.$this->YDimension.'px;';
		
		if ($this->ShowBorder) $Graph .= $this->GraphBorder();
		
		$Graph .= '">'."\n"; //Finish opening outer <div> tag
		
		if ($this->ShowTitle) $Graph .= $this->GraphTitle();
		
		$Graph .= $this->SetAxis();
		
		if ($this->ShowAxisLabels) $Graph .= $this->BuildYAxisLabels();
		
		if ($this->ShowBars
		&& (strtolower($this->BarWidth) == 'auto'
		  || (count($this->Vals) + ((count($this->Vals)+1)/2)) * $this->BarWidth * 0.9 > $this->YAxisSize)) $this->BarWidth = $this->GetBarWidth(1);
		if (!$this->BarWidth) $this->ShowBars = false; //Bars either 0 width or no data to show
		
		if ($this->ShowBars) $Graph .= $this->BuildHBars(); //The bars
		
		if ($this->ShowAxisValues)
		{
			$Graph .= '<div style="font-size: 0; height: 0; clear: both;">&nbsp;</div>'."\n";
			$Graph .= $this->BuildXAxisValues();
		}
		
		if ($this->ShowXAxisLegend)
		{
			$Graph .= '<div style="font-size: 0; height: 0; clear: both;">&nbsp;</div>'."\n";
			$Graph .= $this->SetXLegend(1);
		}
		
		$Graph .= '</div>'."\n"; //Close outer div
		
		return $Graph;
	}
	
	private function RenderVertical()
	{
		$Graph = '<div style="overflow: hidden; text-align: left; width: '.$this->XDimension.'px; height: '.$this->YDimension.'px;';
		
		if ($this->ShowBorder) $Graph .= $this->GraphBorder();
		
		$Graph .= '">'."\n"; //Finish opening outer <div> tag
		
		if ($this->ShowTitle) $Graph .= $this->GraphTitle();
		
		$Graph .= $this->SetAxis();
		
		if ($this->ShowAxisValues) $Graph .= $this->BuildYAxisValues();
		
		//Container for the bars (underneath the axis)
		$Graph .= '<div style="width: '.$this->GetAxisWidth().'px; height: 0; float: right; clear: both;">'."\n";
		
		if ($this->ShowBars
		&& (strtolower($this->BarWidth) == 'auto'
		  || (count($this->Vals) + ((count($this->Vals)+1)/2)) * $this->BarWidth * 0.9 > $this->XAxisSize)) $this->BarWidth = $this->GetBarWidth(0);
		if (!$this->BarWidth) $this->ShowBars = false; //Bars either 0 width or no data to show
		
		if ($this->ShowBars) $Graph .= $this->BuildVBars(); //The bars
		
		if ($this->ShowAxisLabels)
		{
			$Graph .= '<div style="font-size: 0; height: 0; clear: both;">&nbsp;</div>'."\n";
			$Graph .= $this->BuildXLabels(); //The labels
		}
		
		if ($this->ShowXAxisLegend)
		{
			$Graph .= '<div style="font-size: 0; height: 0; clear: both;">&nbsp;</div>'."\n";
			$Graph .= $this->SetXLegend();
		}
		
		$Graph .= '</div>'."\n"; //Close bar holder (underneath X axis - yes it really is!)
		$Graph .= '</div>'."\n"; //Close outer div
		
		return $Graph;
	}
	
	private function BuildYAxisLabels()
	{
		$FFSize = 0;
		$IESize = 0;
		$bars = array();
		foreach ($this->Vals as $k => $v)
		{
			//Browser specific dimensions - we need to shift by these amounts soon
			$IESize += ($this->GetBarWidth(1) + floor($this->GetBarWidth(1)/2));
			$FFSize += ($this->GetBarWidth(1) + floor($this->GetBarWidth(1)/2));
			$FFSize += ((isset($this->Bars[$k]['BorderWidth']) ? $this->Bars[$k]['BorderWidth'] : $this->BarBorderWidth)*2);
			
			$temp = &$bars[];
			
			$temp = '<div style="height: '.$this->GetBarWidth(1).'px; border: '.
			(isset($this->Bars[$k]['BorderWidth']) ? $this->Bars[$k]['BorderWidth'] : $this->BarBorderWidth).' solid transparent'.
			'; _border: '.
			(isset($this->Bars[$k]['BorderWidth']) ? $this->Bars[$k]['BorderWidth'] : $this->BarBorderWidth).' solid none'.
			'; border-left-width: 0; clear: both;">'.(isset($this->AxisLabels[$k]) ? $this->AxisLabels[$k] : '&nbsp;').'</div>'."\n";
			$temp .= '<div style="font-size: 0; clear: both; height: '.(floor($this->GetBarWidth(1)/2)).'px;">&nbsp;</div>'."\n";
		}
		$bars = array_reverse($bars);
		
		$html = '<div style="text-align: right; width: 6%; overflow: hidden; float: left;'.
		' font-size: '.$this->AxisValuesFontSize.';'.
		' color: '.$this->AxisValuesFontColor.';'.
		' font-weight: '.$this->AxisValuesFontWeight.
		' font-style: '.$this->AxisValuesFontStyle.
		' text-decoration: '.$this->AxisValuesTextDecoration.';'.
		' font-family: '.$this->AxisValuesFontFamily.';'.
		'">'."\n";
		
		$html .= '<div style="font-size: 0; clear: both; height: '.(floor($this->GetAxisHeight() - $FFSize)).'px; _height: '.(floor($this->GetAxisHeight() - $IESize)).'px;">&nbsp;</div>'."\n";
		foreach ($bars as $bar)
		{
			$html .= $bar;
		}
		$html .= '</div>'."\n";
		
		return $html;
	}
	
	private function BuildXAxisValues()
	{
		$top = max($this->Vals);
		if ($top >= 1 && $v < 10) $t1 = round($top, 1);
		else $t1 = round($top, -1);
		
		$t2 = $t1/2;
		if ($t2 >= 1 && $v < 10) $t2 = round($t2, 1);
		else $t2 = round($t2, -1);
		
		$html = '<div style="width: '.$this->GetAxisWidth().'px; float: right; position: relative; bottom: '.$this->FFSize.'px; _bottom: '.$this->IESize.'px;'.
		' font-size: '.$this->AxisValuesFontSize.';'.
		' color: '.$this->AxisValuesFontColor.';'.
		' font-weight: '.$this->AxisValuesFontWeight.
		' font-style: '.$this->AxisValuesFontStyle.
		' text-decoration: '.$this->AxisValuesTextDecoration.';'.
		' font-family: '.$this->AxisValuesFontFamily.';'.
		' text-align: right;">'."\n";
		$bw = round($this->XAxisSize * 0.9, 1);
		$html .= '<div style="float: left; width: '.$bw.'px; text-align: right;">'."\n";
		$html .= '<div style="float: left; width: '.(floor($bw/2)).'px; text-align: right;">'.$t2.'</div>'."\n";
		$html .= $t1."\n".'</div>'."\n";
		$html .= '&rarr;&nbsp;</div>'."\n";
		return $html;
	}
	
	private function BuildHBars()
	{
		$top = max($this->Vals);
		
		$this->FFSize = &$FFSize;
		$this->IESize = &$IESize;
		
		$FFSize = 0;
		$IESize = 0;
		$bars = array();
		foreach ($this->Vals as $k => $v)
		{
			//Browser specific dimensions - we need to shift by these amounts soon
			$IESize += ($this->GetBarWidth(1) + floor($this->GetBarWidth(1)/2));
			$FFSize += ($this->GetBarWidth(1) + floor($this->GetBarWidth(1)/2));
			$FFSize += ((isset($this->Bars[$k]['BorderWidth']) ? $this->Bars[$k]['BorderWidth'] : $this->BarBorderWidth)*2);
			
			if (is_numeric($this->Vals[$k]))
			{
				$this->BarLengths[$k] = $height = round(($this->Vals[$k]/$top) * 0.9 * $this->XAxisSize, 1); //90% of axis height
			}
			else $this->BarLengths[$k] = $height = 0;
			$temp = &$bars[];
			
			$temp = '<div style="font-size: 0; width: '.$this->BarLengths[$k].'px; height: '.$this->GetBarWidth(1).'px; background-color: '.
			(isset($this->Bars[$k]['BackgroundColor']) ? $this->Bars[$k]['BackgroundColor'] : $this->BarBackgroundColor).
			'; border: '.
			(isset($this->Bars[$k]['BorderWidth']) ? $this->Bars[$k]['BorderWidth'] : $this->BarBorderWidth).' '.
			(isset($this->Bars[$k]['BorderStyle']) ? $this->Bars[$k]['BorderStyle'] : $this->BarBorderStyle).' '.
			(isset($this->Bars[$k]['BorderColor']) ? $this->Bars[$k]['BorderColor'] : $this->BarBorderColor).'; border-left-width: 0; float: left;'; //Think of a cleaner way than leaving the quote open for a while? **
			
			if (isset($this->Bars[$k]['Attributes'])) $temp .= $this->SetBarAttributes($k);
			else $temp .= '"';
			
			$temp .= '>&nbsp;</div>'."\n";
			$temp .= '<div style="font-size: 0; clear: both; height: '.(floor($this->GetBarWidth(1)/2)).'px;">&nbsp;</div>'."\n";
		}
		$bars = array_reverse($bars);
		
		$html = '<div style="height: '.$FFSize.'px; _height: '.$IESize.'px; width: '.$this->GetAxisWidth().'px; position: relative; float: right; bottom: '.$FFSize.'px; _bottom: '.$IESize.'px; _left: '.$this->YAxisWidth.';">'."\n";
		foreach ($bars as $bar)
		{
			$html .= $bar;
		}
		$html .= '</div>'."\n";
		
		return $html;
	}
	
	private function BuildYAxisValues()
	{
		$top = max($this->Vals);
		if ($top >= 1 && $v < 10) $t1 = round($top, 1);
		else $t1 = round($top, -1);
		
		$t2 = $t1/2;
		if ($t2 >= 1 && $v < 10) $t2 = round($t2, 1);
		else $t2 = round($t2, -1);
		
		$bw = (int) $this->BarBorderWidth;
		
		$html = '<div style="text-align: right; z-index: 2; overflow: visible; float: right; position: relative; right: '.($bw*2).'px; height: '.$this->GetAxisHeight().'px;'.
		' font-size: '.$this->AxisValuesFontSize.';'.
		' color: '.$this->AxisValuesFontColor.';'.
		' font-weight: '.$this->AxisValuesFontWeight.
		' font-style: '.$this->AxisValuesFontStyle.
		' text-decoration: '.$this->AxisValuesTextDecoration.';'.
		' font-family: '.$this->AxisValuesFontFamily.';'.
		'">'."\n";
		
		$bh = $this->GetAxisHeight() - round(0.9 * $this->YAxisSize, 1);
		$html .= '<div style="height: '.$bh.'px; clear: both;">&nbsp;</div>'."\n";
		$html .= '<div style="height: '.(floor(($this->GetAxisHeight()-$bh)/2)).'px;">&uarr;&nbsp;'.$t1.'</div>'."\n";
		$html .= '<div style="clear: both;">'.$t2.'</div>'."\n";
		$html .= '</div>'."\n";
		return $html;
	}
	
	private function SetXLegend($hor=false)
	{
		if (!$hor) $bot = ' bottom: '.max($this->BarLengths).'px;';
		else $bot = ' bottom: '.$this->FFSize.'px; _bottom: '.$this->IESize.'px;';
		
		$html = '<div style="width: '.$this->XAxisSize.'px;'.
				' font-weight: '.$this->XAxisLegendFontWeight.';'.
				' font-size: '.$this->XAxisLegendFontSize.';'.
				' color: '.$this->XAxisLegendFontColor.';'.
				' font-family: '.$this->XAxisLegendFontFamily.';'.
				' font-style: '.$this->XAxisLegendFontStyle.';'.
				' position: relative;'.
				$bot.
				' text-align: '.$this->XAxisLegendPosition.';'.
				' float: right;">'.
				'&nbsp;'.$this->XAxisLegendText.'&nbsp;'.
				'</div>'."\n";
		return $html;
	}
	
	private function BuildXLabels()
	{
		$d = max($this->BarLengths);
		$html = '';
		foreach ($this->Vals as $k => $v)
		{
			$text = (isset($this->AxisLabels[$k]) ? $this->AxisLabels[$k] : '&nbsp;');
			$html .= '<div style="font-size; 0; width: '.(floor($this->GetBarWidth(0)/2)).'px; float: left;">&nbsp;</div>';
			$html .= '<div style="font-size: '.$this->AxisLabelFontSize.
					'; font-family: '.$this->AxisLabelFontFamily.
					'; color: '.$this->AxisLabelFontColor.
					'; font-weight: '.$this->AxisLabelFontWeight.
					'; font-style: '.$this->AxisLabelFontStyle.
					'; text-decoration; '.$this->AxisLabelTextDecoration.
					'; width: '.$this->BarWidth.'px'.
					'; text-align: center; float: left; overflow: visible; position: relative; bottom: '.$d.'px; border: '.(isset($this->Bars[$k]['BorderWidth']) ? $this->Bars[$k]['BorderWidth'] : $this->BarBorderWidth).' solid transparent; _border: '.(isset($this->Bars[$k]['BorderWidth']) ? $this->Bars[$k]['BorderWidth'] : $this->BarBorderWidth).' solid none;">';
			$html .= $text;
			$html .= '</div>'."\n";
		}
		return $html;
	}
	
	private function BuildVBars()
	{
		$html = '';
		$top = max($this->Vals);
		
		//Get the bar heights
		foreach ($this->Vals as $k => $v)
		{
			if (is_numeric($this->Vals[$k]))
			{
				$this->BarLengths[$k] = $height = round(($this->Vals[$k]/$top) * 0.9 * $this->YAxisSize, 1); //90% of axis height
			}
			else $this->BarLengths[$k] = $height = 0;
			$html .= '<div style="font-size; 0; width: '.(floor($this->BarWidth/2)).'px; float: left;">&nbsp;</div>'."\n";
			$html .= '<div style="font-size: 0; width: '.$this->BarWidth.'px; height: '.$height.'px; background-color: '.
			(isset($this->Bars[$k]['BackgroundColor']) ? $this->Bars[$k]['BackgroundColor'] : $this->BarBackgroundColor).
			'; border: '.
			(isset($this->Bars[$k]['BorderWidth']) ? $this->Bars[$k]['BorderWidth'] : $this->BarBorderWidth).' '.
			(isset($this->Bars[$k]['BorderStyle']) ? $this->Bars[$k]['BorderStyle'] : $this->BarBorderStyle).' '.
			(isset($this->Bars[$k]['BorderColor']) ? $this->Bars[$k]['BorderColor'] : $this->BarBorderColor).'; border-bottom-width: 0; float: left; position: relative; bottom: '.($height+(isset($this->Bars[$k]['BorderWidth']) ? $this->Bars[$k]['BorderWidth'] : $this->BarBorderWidth)+$this->XAxisWidth).'px; _bottom: '.($height+$this->XAxisWidth).'px;'; //Think of a cleaner way than leaving the quote open for a while? **
			
			if (isset($this->Bars[$k]['Attributes'])) $html .= $this->SetBarAttributes($k);
			else $html .= '"';
			
			$html .= '>&nbsp;</div>'."\n";
		}
		return $html;
	}
	
	private function SetBarAttributes($n)
	{
		$html = '';
		$st = false;
		foreach ($this->Bars[$n]['Attributes'] as $at => $val)
		{
			if (strtolower($at) == 'style' && !$st) //** quote was left open
			{
				$html = ' '.$val.';"'.$html;
				$st = true;
			}
			else $html .= ' '.$at.'="'.$val.'"';
		}
		return ($st ? '' : '"').$html; //Prepend the closing style quote
	}
	
	private function SetAxis()
	{
		$html = '<div style="font-size: 0; width: '.$this->GetAxisWidth().'px;'.
				' height: '.$this->GetAxisHeight().'px;';
		if ($this->ShowBackground) $html .= $this->GraphBackground();
		if ($this->ShowXAxis) $html .= " border-bottom: {$this->XAxisWidth} {$this->XAxisStyle} {$this->XAxisColor};";
		if ($this->ShowYAxis) $html .= " border-left: {$this->YAxisWidth} {$this->YAxisStyle} {$this->YAxisColor};";
		$html .= ' float: right;">&nbsp;</div>';
		
		return $html;
	}
	
	private function GetAxisHeight()
	{
		if ($this->YAxisSize === 0)
		{
			//This was all trial and error - there's no science here 
			$h = $this->YDimension * 0.97 - 16;
			if ($this->ShowTitle) $h = $h * 0.97 - 16;
			if ($this->ShowXAxisLegend) $h = $h * 0.97 - 16;
			return $this->YAxisSize = $h;
		}
		else
		{
			return $this->YAxisSize;
		}
	}
	
	private function GetAxisWidth()
	{
		if ($this->XAxisSize === 0)
		{
			return $this->XAxisSize = $this->XDimension * 0.93;
		}
		else
		{
			return $this->XAxisSize;
		}
	}
	
	private function GraphBorder()
	{
		$css = " border-left: {$this->LBorderWidth} {$this->LBorderStyle} {$this->LBorderColor};".
				" border-right: {$this->RBorderWidth} {$this->RBorderStyle} {$this->RBorderColor};".
				" border-top: {$this->TBorderWidth} {$this->TBorderStyle} {$this->TBorderColor};".
				" border-bottom: {$this->BBorderWidth} {$this->BBorderStyle} {$this->BBorderColor};";
		return $css;
	}
	
	private function GraphBackground()
	{
		$css = " background-color: {$this->BackgroundColor};";
		if ($this->BackgroundImage != '') $css .= " background-image: url({$this->BackgroundImage});";
		if ($this->BackgroundRepeat != '') $css .= " background-repeat: {$this->BackgroundRepeat};";
		if ($this->BackgroundPosition != '') $css .= " background-position: {$this->BackgroundPosition};";
		return $css;
	}
	
	private function GraphTitle()
	{
		$html = '<div style="color: '.$this->TitleFontColor.
				'; font-size: '.$this->TitleFontSize.
				'; font-weight: '.$this->TitleFontWeight.
				'; font-style: '.$this->TitleFontStyle.
				'; font-family: '.$this->TitleFontFamily.
				'; text-decoration: '.$this->TitleTextDecoration.
				'; text-align: '.$this->TitleAlign.';';
		if ($this->ShowTitleBackground) $html .= ' background-color: '.$this->TitleBackgroundColor.';';
		
		$html .= ' padding: 1px; overflow: hidden; white-space: nowrap;">'."\n"; //Finish opening title <div>
		
		$html .= $this->TitleText."\n";
		
		$html .= '</div>'."\n";
		
		return $html;
	}

}

?>
Last edited by Chris Corbyn on Mon Mar 20, 2006 1:24 pm, edited 1 time in total.
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

It's actually very nice and works in both IE and Firefox. The whole things runs in under 1000th of a second on my home computer with 10 values set.

Features:
  • * Creates fixed dimension bar graphs without GD
    * Highly customizable (fonts, titles, axes, legends...)
    * Renders quickly
    * Lower bandwidth than JP Graph
    * Ability to manipulate each individual bar
    * Horizontal or vertical rendering
    * JavaScript/DHTML integration capable
Limitations (in this first version):
  • * Unable to handle negative values (Will be added very soon)
Usage:

It's actually very simple to use despite it's ugly length.

The basics:

Code: Select all

$Graph = new BarGraph(600, 200); //Instantiate with the (width, height) of graph in pixels

$data = array(
    10,
    12,
    64,
    78,
    5,
    89.4
);

$Graph->PushValues($data); //Insert some data - run this as many times as needed (like array_push())
$Graph->Create(); //Output the graph (very basic)

//or......
$output = $Graph->Create(true);
//Setting the first paramater to true returns the grpah as opposed to echoing it
See example 1 here: http://w3style.co.uk/BarGraph/example1.php

We might want to have a title and some labels on our axes....

Code: Select all

$Graph = new BarGraph(400, 130);

$data = array(
    10,
    12,
    64,
    78,
    5,
    89.4
);

$labels = array(
    'Foo',
    'Bar',
    'Bob',
    'Joe',
    'Jim',
    '???'
);

$Graph->PushValues($data);

$Graph->PushLabels($labels); //Insert some labels like we did for the values
$Graph->ShowAxisLabels(); //Tell the class to display them

$Graph->TitleText('Example 2: Adding Labels/Titles'); //Give it a title
$Graph->ShowTitle(); //Display it

$Graph->Create();
See it here: http://w3style.co.uk/BarGraph/example2.php

OK, you get the idea... you can manipulate the appearance quite a lot - one last example. I'll list all the methods and what they do after this.

Code: Select all

$Graph = new BarGraph(550, 300);

$data = array(
    10,
    12,
    'foo' => 64,  //We can manipulate individual bars associately**
    78,
    5,
    89.4
);

$labels = array(
    'Foo',
    'Bar',
    'foo' => 'Bob',
    'Joe',
    'Jim',
    '???'
);

$Graph->PushValues($data, 1);

$Graph->PushLabels($labels, 1); //Insert some labels like we did for the values
$Graph->ShowAxisLabels(); //Tell the class to display them

$Graph->ShowAxisValues(); //Ticks on axis

$Graph->XAxisLegendText('Example X Axis Legend');
$Graph->ShowXAxisLegend();

$Graph->TitleText('Example 2: Adding Labels/Titles'); //Give it a title
$Graph->TitleFontColor('#555599');
$Graph->TitleFontSize('0.8em');
$Graph->ShowTitle(); //Display it

//Some styling (background)
$Graph->BackgroundColor('#FFFFFF');
$Graph->BackgroundImage('gradient.png');
$Graph->BackgroundRepeat('repeat-x');
$Graph->BackgroundPosition('top-left');
$Graph->ShowBackground();

//Brighten up the bars
$Graph->BarBackgroundColor('#6BE849');

//Fiddle with the bar we called foo
//The idea behind this method is that you can set many at once (hence the array)
$Graph->PushBarBackgroundColors(array('foo' => '#FFCCCC'), 1); //The second parameter means we refer to the bar associately
//Make foo clickable
$Graph->PushBarAttributes(array('foo' => array('onclick' => "alert('You clicked foo')")), 1);

$Graph->Create();
View it here: http://w3style.co.uk/BarGraph/example3.php

Available methods:
>>PushValues(array array [, bool assoc switch])
Pushes new values into the graph. Values appear in the order they were added. Optionally, if the second paramater is set true the array keys are used associatively. This helps when trying to manipulate a specific bar.

>>PushLabels(array array [, bool assoc switch])
Pushes new labels into the graph. Labels appear in the order they were added. Optionally, if the second paramater is set true the array keys are used associatively.

>>ShowTitle([bool switch])
Displays or hides the graph title

>>TitleText(string text)
Sets the text for the title

>>TitleFontColor(), TitleFontWeight(), TitleFontStyle(), TitleFontColor(), TitleTextDecoration(), TitleBackgroundColor(), TitleFontFamily()
Change the appearance of the title. All take a single string as a parameter. Standard CSS values are accepeted.

>>TitleAlign(string alignment)
One of center, left, or right

>>ShowAxis([bool switch])
Show or hide both axes

>>ShowXAxis([bool switch]), ShowYAxis([bool switch])
Show or hide the individual axis

>>AxisColor(string color)
Set the axis color using html/css accpeted codes

>>XAxisColor(string color), YAxisColor(string color)
See above

>>AxisStyle(string style), XAxisStyle(), YAxisStyle()
Dotted, dashed, solid or double (CSS modes)

>>AxisWidth(string size)
Set both axis widths. You need to use CSS units as a string (e.g. '2px' as opposed to 2)

>>XAxisWidth(), YAxisWidth()
See above

>>ShowBars([bool switch])
Show or hide the bars

>>BarBorderWidth(string size)
You must get the idea by now :P

>>BarBorderColor(string color)

>>BarBorderStyle(string style)
Dotted, solid, dashed, double (CSS standards)

>>BackgroungColor(string color)
Background for the graph

>>BackgroundImage(), BackgroundRepeat(), BackgroundPosition()
Background Image for the graph

>>ShowXAxisLegend([bool switch])

>>ShowAxisValues([bool switch])

>>ShowAxisLabels([bool switch])

>>AxisLabelFontColor(), AxisLabelFontStyle(), AxisLabelFontWeight(), AxisLabelFontFamily(), AxisLabelTextDecoration()

>>BarDirection(string/Int mode)
Changes the orientation of the bars. Set this to 'h', or 'v'.

>>BarWidth(string size)
Set this to "auto" to have the class determine the best widths for the bars. This is the default. You can also set fixed width using CSS units here (e.g. '20px')
Last edited by Chris Corbyn on Sun Oct 09, 2005 8:31 pm, edited 1 time in total.
User avatar
infolock
DevNet Resident
Posts: 1708
Joined: Wed Sep 25, 2002 7:47 pm

Post by infolock »

very nice. i've seen this same type of method used with tables instead though. It's a great way to get away from GD, however, GD would still bring better performance ends. The overhead you will experience with larger loads will be a little bit higher, but not by much, and that is simply due to class versus modules is 9 x out of 10 gonna be slower.

otherwise, nice class and good work.
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

infolock wrote:....however, GD would still bring better performance ends. The overhead you will experience with larger loads will be a little bit higher, but not by much, and that is simply due to class versus modules is 9 x out of 10 gonna be slower....
True about the modules versus classes. But GD alone will not make graph's. There are classes out there (JPGraph) to do this, so you still end up with the object overhead in that sense :)
Roja
Tutorials Group
Posts: 2692
Joined: Sun Jan 04, 2004 10:30 pm

Post by Roja »

infolock wrote:very nice. i've seen this same type of method used with tables instead though. It's a great way to get away from GD, however, GD would still bring better performance ends. The overhead you will experience with larger loads will be a little bit higher, but not by much, and that is simply due to class versus modules is 9 x out of 10 gonna be slower.

otherwise, nice class and good work.
I've actually had fairly strong impact from the jpgraph and gd chart generation.. I'm curious to test this against the same from them, as I think d11wtq's is actually faster.

Regardless, I love the css, and its sharp. The library does seem a bit large, but it does what its supposed to, fast, and very flexibly.

Nice work d11wtq.
User avatar
infolock
DevNet Resident
Posts: 1708
Joined: Wed Sep 25, 2002 7:47 pm

Post by infolock »

aye, that's pretty much the same thing i was thinking. was the speed difference will be, and how this class will handle large data and amounts. let me know your finds Roja as i'm kinda curious to know myself. just don't have the time to do the testing here :?
User avatar
jayshields
DevNet Resident
Posts: 1912
Joined: Mon Aug 22, 2005 12:11 pm
Location: Leeds/Manchester, England

Post by jayshields »

that is a very nice class indeed. good job mate.

if i ever need to make graphs on a website i will definately search for this thread! :wink:
Charles256
DevNet Resident
Posts: 1375
Joined: Fri Sep 16, 2005 9:06 pm

Post by Charles256 »

beautifully done.beautiful
User avatar
John Cartwright
Site Admin
Posts: 11470
Joined: Tue Dec 23, 2003 2:10 am
Location: Toronto
Contact:

Post by John Cartwright »

Great looking class ;)
Will be using this
User avatar
pickle
Briney Mod
Posts: 6445
Joined: Mon Jan 19, 2004 6:11 pm
Location: 53.01N x 112.48W
Contact:

Post by pickle »

Wow! Very impressive. I'll definitely be using this.

By the way, is there any reason this can't be easily modified to work with PHP4? I'm seeing some PHP5 constructs that can definitely be mimiced with PHP4, but I haven't look too closely.
Real programmers don't comment their code. If it was hard to write, it should be hard to understand.
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

pickle wrote:Wow! Very impressive. I'll definitely be using this.

By the way, is there any reason this can't be easily modified to work with PHP4? I'm seeing some PHP5 constructs that can definitely be mimiced with PHP4, but I haven't look too closely.
If you want to convert it to PHP4 then:

a) Change the constructor
b) Drop all the "private" and "public" declarations
c) Replace "__METHOD__" special constants with something that works ( __FUNCTION__ )
d) Don't use overloading ( __call() ) for most of the setters - phyiscally code every single setter method (ouch!)

I don't really have any plans to convert it since it was primarily written for use on a project at work (in my own time) where we use PHP5 OOP heavily. It can be done though.

I may update the code in a day or two since I have modified some of the code to behave in a nicer way and corrected a couple of *minor* bugs.
User avatar
pickle
Briney Mod
Posts: 6445
Joined: Mon Jan 19, 2004 6:11 pm
Location: 53.01N x 112.48W
Contact:

Post by pickle »

d11wtq wrote:I may update the code in a day or two since I have modified some of the code to behave in a nicer way and corrected a couple of *minor* bugs.
Well, once you do, let me know. I'll probably convert it to PHP4 since we use that heavily here at my work ;)
Real programmers don't comment their code. If it was hard to write, it should be hard to understand.
StumpDK
Forum Commoner
Posts: 35
Joined: Thu Feb 12, 2004 2:28 am
Location: Copenhagen, Denmark

Post by StumpDK »

Has this code been converted to php4?
User avatar
pickle
Briney Mod
Posts: 6445
Joined: Mon Jan 19, 2004 6:11 pm
Location: 53.01N x 112.48W
Contact:

Post by pickle »

Heck no. To be honest, I forgot I made that claim. If ~d11wtq's updated the code, then I guess I'll start on it.
Real programmers don't comment their code. If it was hard to write, it should be hard to understand.
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

StumpDK wrote:Has this code been converted to php4?
This is on sourceforge. I put it there so that when I get chance I can work on some of the bugs (there are a handful), optionally allow the data to be returned as an array with the CSS in one index and the XHTML in the other, and also refactor it to allow the possiblity of other graph types (like JP Graph)... I know it can be done because I've seen JS methods for it.

http://sourceforge.net/projects/cssgrapher

If anyone does offer updates/bug fixes/compatibility fixes I'd quite like to know so I can update it :) If anyone fancies digging into what's on SF PM me because I prob won't be working on it for the next 2 months personally at least :(
Post Reply