<?php

/*

 * This work is hereby released into the Public Domain.

 * To view a copy of the public domain dedication,

 * visit http://creativecommons.org/licenses/publicdomain/ or send a letter to

 * Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.

 *

 */



/**

 * Handle axis

 *

 * @package Artichow

 */

class awAxis {



	/**

	 * Axis line

	 *

	 * @var Line

	 */

	public $line;



	/**

	 * Axis labels

	 *

	 * @var Label

	 */

	public $label;

	

	/**

	 * Axis title

	 *

	 * @var Label

	 */

	public $title;

	

	/**

	 * Title position

	 *

	 * @var float

	 */

	protected $titlePosition = 0.5;



	/**

	 * Labels number

	 *

	 * @var int

	 */

	protected $labelNumber;

	

	/**

	 * Axis ticks

	 *

	 * @var array

	 */

	protected $ticks = array();



	/**

	 * Axis and ticks color

	 *

	 * @var Color

	 */

	protected $color;



	/**

	 * Axis left and right padding

	 *

	 * @var Side

	 */

	protected $padding;



	/**

	 * Axis range

	 *

	 * @var array

	 */

	protected $range;



	/**

	 * Hide axis

	 *

	 * @var bool

	 */

	protected $hide = FALSE;



	/**

	 * Auto-scaling mode

	 *

	 * @var bool

	 */

	protected $auto = TRUE;



	/**

	 * Axis range callback function

	 *

	 * @var array

	 */

	protected $rangeCallback = array(

		'toValue' => 'toProportionalValue',

		'toPosition' => 'toProportionalPosition'

	);

	

	/**

	 * Build the axis

	 *

	 * @param float $min Begin of the range of the axis

	 * @param float $max End of the range of the axis

	 */

	public function __construct($min = NULL, $max = NULL) {

	

		$this->line = new awVector(

			new awPoint(0, 0),

			new awPoint(0, 0)

		);

		

		$this->label = new awLabel;

		$this->padding = new awSide;

		

		$this->title = new awLabel(

			NULL,

			NULL,

			NULL,

			0

		);

		

		$this->setColor(new awBlack);

		

		if($min !== NULL and $max !== NULL) {

			$this->setRange($min, $max);

		}

	

	}

	

	/**

	 * Enable/disable auto-scaling mode

	 *

	 * @param bool $auto

	 */

	public function auto($auto) {

		$this->auto = (bool)$auto;

	}

	

	/**

	 * Get auto-scaling mode status

	 *

	 * @return bool

	 */

	public function isAuto() {

		return $this->auto;

	}

	

	/**

	 * Hide axis

	 *

	 * @param bool $hide

	 */

	public function hide($hide = TRUE) {

		$this->hide = (bool)$hide;

	}

	

	/**

	 * Show axis

	 *

	 * @param bool $show

	 */

	public function show($show = TRUE) {

		$this->hide = !(bool)$show;

	}

	

	/**

	 * Return a tick object from its name

	 *

	 * @param string $name Tick object name

	 * @return Tick

	 */

	public function tick($name) {

		/* <php5> */

		return array_key_exists($name, $this->ticks) ? $this->ticks[$name] : NULL;

		/* </php5> */

		/* <php4> --

		if(array_key_exists($name, $this->ticks)) {

			return $tick = &$this->ticks[$name];

		} else {

			return NULL;

		}

		-- </php4> */

	}

	

	/**

	 * Add a tick object

	 *

	 * @param string $name Tick object name

	 * @param awTick $tick Tick object

	 */

	public function addTick($name, awTick $tick) {

		/* <php5> */

		$this->ticks[$name] = $tick;

		/* </php5> */

		/* <php4> --

		$this->ticks[$name] = &$tick;

		-- </php4> */

	}

	

	/**

	 * Delete a tick object

	 *

	 * @param string $name Tick object name

	 */

	public function deleteTick($name) {

		if(array_key_exists($name, $this->ticks)) {

			unset($this->ticks[$name]);

		}

	}

	

	/**

	 * Hide all ticks

	 *

	 * @param bool $hide Hide or not ?

	 */

	public function hideTicks($hide = TRUE) {

		/* <php5> */

		foreach($this->ticks as $tick) {

			$tick->hide($hide);

		}

		/* </php5> */

		/* <php4> --

		foreach($this->ticks as $key => $tick) {

			$this->ticks[$key]->hide($hide);

		}

		-- </php4> */

	}

	

	/**

	 * Change ticks style

	 *

	 * @param int $style Ticks style

	 */

	public function setTickStyle($style) {

		/* <php5> */

		foreach($this->ticks as $tick) {

			$tick->setStyle($style);

		}

		/* </php5> */

		/* <php4> --

		foreach($this->ticks as $key => $tick) {

			$this->ticks[$key]->setStyle($style);

		}

		-- </php4> */

	}

	

	/**

	 * Change ticks interval

	 *

	 * @param int $interval Ticks interval

	 */

	public function setTickInterval($interval) {

		/* <php5> */

		foreach($this->ticks as $tick) {

			$tick->setInterval($interval);

		}

		/* </php5> */

		/* <php4> --

		foreach($this->ticks as $key => $tick) {

			$this->ticks[$key]->setInterval($interval);

		}

		-- </php4> */

	}

	

	/**

	 * Change number of ticks relative to others ticks

	 *

	 * @param awTick $to Change number of theses ticks

	 * @param awTick $from Ticks reference

	 * @param float $number Number of ticks by the reference

	 */

	public function setNumberByTick($to, $from, $number) {

		$this->ticks[$to]->setNumberByTick($this->ticks[$from], $number);

	}

	

	/**

	 * Reverse ticks style

	 */

	public function reverseTickStyle() {

		/* <php5> */

		foreach($this->ticks as $tick) {

			if($tick->getStyle() === awTick::IN) {

				$tick->setStyle(awTick::OUT);

			} else if($tick->getStyle() === awTick::OUT) {

				$tick->setStyle(awTick::IN);

			}

		}

		/* </php5> */

		/* <php4> --

		foreach($this->ticks as $key => $tick) {

			if($this->ticks[$key]->getStyle() === awTick::IN) {

				$this->ticks[$key]->setStyle(awTick::OUT);

			} else if($this->ticks[$key]->getStyle() === awTick::OUT) {

				$this->ticks[$key]->setStyle(awTick::IN);

			}

		}

		-- </php4> */

	}

	

	/**

	 * Change interval of labels

	 *

	 * @param int $interval Interval

	 */

	public function setLabelInterval($interval) {

		$this->auto(FALSE);

		$this->setTickInterval($interval);

		$this->label->setInterval($interval);

	}

	

	/**

	 * Change number of labels

	 *

	 * @param int $number Number of labels to display (can be NULL)

	 */

	public function setLabelNumber($number) {

		$this->auto(FALSE);

		$this->labelNumber = is_null($number) ? NULL : (int)$number;

	}

	

	/**

	 * Get number of labels

	 *

	 * @return int

	 */

	public function getLabelNumber() {

		return $this->labelNumber;

	}

	

	/**

	 * Change precision of labels

	 *

	 * @param int $precision Precision

	 */

	public function setLabelPrecision($precision) {

		$this->auto(FALSE);

		$function = 'axis'.time().'_'.(microtime() * 1000000);

		eval('function '.$function.'($value) {

			return sprintf("%.'.(int)$precision.'f", $value);

		}');

		$this->label->setCallbackFunction($function);

	}

	

	/**

	 * Change text of labels

	 *

	 * @param array $texts Some texts

	 */

	public function setLabelText($texts) {

		if(is_array($texts)) {

			$this->auto(FALSE);

			$function = 'axis'.time().'_'.(microtime() * 1000000);

			eval('function '.$function.'($value) {

				$texts = '.var_export($texts, TRUE).';

				return $texts[$value];

			}');

			$this->label->setCallbackFunction($function);

		}

	}



	/**

	 * Get the position of a point

	 *

	 * @param awAxis $xAxis X axis

	 * @param awAxis $yAxis Y axis

	 * @param awPoint $p Position of the point

	 * @return Point Position on the axis

	 */

	public static function toPosition(awAxis $xAxis, awAxis $yAxis, awPoint $p) {



		$p1 = $xAxis->getPointFromValue($p->x);

		$p2 = $yAxis->getPointFromValue($p->y);

		

		return new awPoint(

			round($p1->x),

			round($p2->y)

		);

		

	}

	

	/**

	 * Change title alignment

	 *

	 * @param int $alignment New Alignment

	 */

	public function setTitleAlignment($alignment) {

	

		switch($alignment) {

		

			case awLabel::TOP :

				$this->setTitlePosition(1);

				$this->title->setAlign(NULL, awLabel::BOTTOM);

				break;

		

			case awLabel::BOTTOM :

				$this->setTitlePosition(0);

				$this->title->setAlign(NULL, awLabel::TOP);

				break;

		

			case awLabel::LEFT :

				$this->setTitlePosition(0);

				$this->title->setAlign(awLabel::LEFT);

				break;

		

			case awLabel::RIGHT :

				$this->setTitlePosition(1);

				$this->title->setAlign(awLabel::RIGHT);

				break;

		

		}

	

	}

	

	/**

	 * Change title position on the axis

	 *

	 * @param float $position A new awposition between 0 and 1

	 */

	public function setTitlePosition($position) {

		$this->titlePosition = (float)$position;

	}

	

	/**

	 * Change axis and axis title color

	 *

	 * @param awColor $color

	 */

	public function setColor(awColor $color) {

		$this->color = $color;

		$this->title->setColor($color);

	}

	

	/**

	 * Change axis padding

	 *

	 * @param int $left Left padding in pixels

	 * @param int $right Right padding in pixels

	 */

	public function setPadding($left, $right) {

		$this->padding->set($left, $right);

	}

	

	/**

	 * Get axis padding

	 *

	 * @return Side

	 */

	public function getPadding() {

		return $this->padding;

	}

	

	/**

	 * Change axis range

	 *

	 * @param float $min

	 * @param float $max

	 */

	public function setRange($min, $max) {

		if($min !== NULL) {

			$this->range[0] = (float)$min;

		}

		if($max !== NULL) {

			$this->range[1] = (float)$max;

		}

	}

	

	/**

	 * Get axis range

	 *

	 * @return array

	 */

	public function getRange() {

		return $this->range;

	}

	

	/**

	 * Change axis range callback function

	 *

	 * @param string $toValue Transform a position between 0 and 1 to a value

	 * @param string $toPosition Transform a value to a position between 0 and 1 on the axis

	 */

	public function setRangeCallback($toValue, $toPosition) {

		$this->rangeCallback = array(

			'toValue' => (string)$toValue,

			'toPosition' => (string)$toPosition

		);

	}

	

	/**

	 * Center X values of the axis 

	 *

	 * @param awAxis $axis An axis

	 * @param float $value The reference value on the axis

	 */

	public function setXCenter(awAxis $axis, $value) {

		

		// Check vector angle

		if($this->line->isVertical() === FALSE) {

			trigger_error("setXCenter() can only be used on vertical axes", E_USER_ERROR);

		}

		

		$p = $axis->getPointFromValue($value);

		

		$this->line->setX(

			$p->x,

			$p->x

		);

		

	}

	

	/**

	 * Center Y values of the axis 

	 *

	 * @param awAxis $axis An axis

	 * @param float $value The reference value on the axis

	 */

	public function setYCenter(awAxis $axis, $value) {

		

		// Check vector angle

		if($this->line->isHorizontal() === FALSE) {

			trigger_error("setYCenter() can only be used on horizontal axes", E_USER_ERROR);

		}

		

		$p = $axis->getPointFromValue($value);

		

		$this->line->setY(

			$p->y,

			$p->y

		);

		

	}

	

	/**

	 * Get the distance between to values on the axis

	 *

	 * @param float $from The first value

	 * @param float $to The last value

	 * @return Point

	 */

	public function getDistance($from, $to) {

	

		$p1 = $this->getPointFromValue($from);

		$p2 = $this->getPointFromValue($to);

		

		return $p1->getDistance($p2);

	

	}

	

	/**

	 * Get a point on the axis from a value

	 *

	 * @param float $value

	 * @return Point

	 */

	protected function getPointFromValue($value) {

	

		$callback = $this->rangeCallback['toPosition'];

		

		list($min, $max) = $this->range;

		$position = $callback($value, $min, $max);

		

		return $this->getPointFromPosition($position);

		

	}

	

	/**

	 * Get a point on the axis from a position

	 *

	 * @param float $position A position between 0 and 1

	 * @return Point

	 */

	protected function getPointFromPosition($position) {

		

		$vector = $this->getVector();

		

		$angle = $vector->getAngle();

		$size = $vector->getSize();

		

		return $vector->p1->move(

			cos($angle) * $size * $position,

			-1 * sin($angle) * $size * $position

		);

		

	}

	

	/**

	 * Draw axis

	 *

	 * @param awDrawer $drawer A drawer

	 */

	public function draw(awDrawer $drawer) {

	

		if($this->hide) {

			return;

		}

	

		$vector = $this->getVector();

		

		// Draw axis ticks

		$this->drawTicks($drawer, $vector);

	

		// Draw axis line

		$this->line($drawer);

		

		// Draw labels

		$this->drawLabels($drawer);

		

		// Draw axis title

		$p = $this->getPointFromPosition($this->titlePosition);

		$this->title->draw($drawer, $p);

	

	}

	

	public function autoScale() {

	

		if($this->isAuto() === FALSE) {

			return;

		}

	

		list($min, $max) = $this->getRange();

		$interval = $max - $min;

		

		$partMax = $max / $interval;

		$partMin = $min / $interval;

		

		$difference = log($interval) / log(10);

		$difference = floor($difference);

		

		$pow = pow(10, $difference);

		

		$intervalNormalize = $interval / $pow;

		

		if($difference <= 0) {

		

			$precision = $difference * -1 + 1;

		

			if($intervalNormalize > 2) {

				$precision--;

			}

			

		} else {

			$precision = 0;

		}

		

		if($min != 0 and $max != 0) {

			$precision++;

		}

		

		$this->setLabelPrecision($precision);

		

		if($intervalNormalize <= 1.5) {

			$intervalReal = 1.5;

			$labelNumber = 4;

		} else if($intervalNormalize <= 2) {

			$intervalReal = 2;

			$labelNumber = 5;

		} else if($intervalNormalize <= 3) {

			$intervalReal = 3;

			$labelNumber = 4;

		} else if($intervalNormalize <= 4) {

			$intervalReal = 4;

			$labelNumber = 5;

		} else if($intervalNormalize <= 5) {

			$intervalReal = 5;

			$labelNumber = 6;

		} else if($intervalNormalize <= 8) {

			$intervalReal = 8;

			$labelNumber = 5;

		} else if($intervalNormalize <= 10) {

			$intervalReal = 10;

			$labelNumber = 6;

		}

		

		if($min == 0) {

		

			$this->setRange(

				$min,

				$intervalReal * $pow

			);

			

		} else if($max == 0) {

		

			$this->setRange(

				$intervalReal * $pow * -1,

				0

			);

			

		}

		

		$this->setLabelNumber($labelNumber);

	

	}

	

	protected function line(awDrawer $drawer) {

		

		$drawer->line(

			$this->color,

			$this->line

		);

		

	}

	

	protected function drawTicks(awDrawer $drawer, awVector $vector) {

		

		foreach($this->ticks as $tick) {

			$tick->setColor($this->color);

			$tick->draw($drawer, $vector);

		}

		

	}

	

	protected function drawLabels($drawer) {

		

		if($this->labelNumber !== NULL) {

			list($min, $max) = $this->range;

			$number = $this->labelNumber - 1;

			if($number < 1) {

				return;

			}

			$function = $this->rangeCallback['toValue'];

			$labels = array();

			for($i = 0; $i <= $number; $i++) {

				$labels[] = $function($i / $number, $min, $max);

			}

			$this->label->set($labels);

		}

		

		$labels = $this->label->count();

		

		for($i = 0; $i < $labels; $i++) {

		

			$p = $this->getPointFromValue($this->label->get($i));

			$this->label->draw($drawer, $p, $i);

		

		}

		

	}

	

	protected function getVector() {

	

		$angle = $this->line->getAngle();

		

		// Compute paddings

		$vector = new awVector(

			$this->line->p1->move(

				cos($angle) * $this->padding->left,

				-1 * sin($angle) * $this->padding->left

			),

			$this->line->p2->move(

				-1 * cos($angle) * $this->padding->right,

				-1 * -1 * sin($angle) * $this->padding->right

			)

		);

		

		return $vector;

		

	}

	

	public function __clone() {

	

		$this->label = clone $this->label;

		$this->line = clone $this->line;

		$this->title = clone $this->title;

		

		foreach($this->ticks as $name => $tick) {

			$this->ticks[$name] = clone $tick;

		}

	

	}



}



registerClass('Axis');



function toProportionalValue($position, $min, $max) {

	return $min + ($max - $min) * $position;

}



function toProportionalPosition($value, $min, $max) {

	if($max - $min == 0) {

		return 0;

	}

	return ($value - $min) / ($max - $min);

}

?>