1<?php
2/*
3 * This work is hereby released into the Public Domain.
4 * To view a copy of the public domain dedication,
5 * visit http://creativecommons.org/licenses/publicdomain/ or send a letter to
6 * Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.
7 *
8 */
9
10require_once dirname(__FILE__)."/Component.class.php";
11
12/* <php4> */
13
14define("PLOT_LEFT", 'left');
15define("PLOT_RIGHT", 'right');
16define("PLOT_TOP", 'top');
17define("PLOT_BOTTOM", 'bottom');
18define("PLOT_BOTH", 'both');
19
20/* </php4> */
21
22/**
23 * Graph using X and Y axis
24 *
25 * @package Artichow
26 */
27abstract class awPlot extends awComponent {
28
29	/**
30	 * Values for Y axis
31	 *
32	 * @var array
33	 */
34	protected $datay;
35
36	/**
37	 * Values for X axis
38	 *
39	 * @var array
40	 */
41	protected $datax;
42
43	/**
44	 * Grid properties
45	 *
46	 * @var Grid
47	 */
48	public $grid;
49
50	/**
51	 * X axis
52	 *
53	 * @var Axis
54	 */
55	public $xAxis;
56
57	/**
58	 * Y axis
59	 *
60	 * @var Axis
61	 */
62	public $yAxis;
63
64	/**
65	 * Position of X axis
66	 *
67	 * @var int
68	 */
69	protected $xAxisPosition = awPlot::BOTTOM;
70
71	/**
72	 * Set X axis on zero ?
73	 *
74	 * @var bool
75	 */
76	protected $xAxisZero = TRUE;
77
78	/**
79	 * Set Y axis on zero ?
80	 *
81	 * @var bool
82	 */
83	protected $yAxisZero = FALSE;
84
85	/**
86	 * Position of Y axis
87	 *
88	 * @var int
89	 */
90	protected $yAxisPosition = awPlot::LEFT;
91
92	/**
93	 * Change min value for Y axis
94	 *
95	 * @var mixed
96	 */
97	private $yMin = NULL;
98
99	/**
100	 * Change max value for Y axis
101	 *
102	 * @var mixed
103	 */
104	private $yMax = NULL;
105
106	/**
107	 * Change min value for X axis
108	 *
109	 * @var mixed
110	 */
111	private $xMin = NULL;
112
113	/**
114	 * Change max value for X axis
115	 *
116	 * @var mixed
117	 */
118	private $xMax = NULL;
119
120	/**
121	 * Left axis
122	 *
123	 * @var int
124	 */
125	const LEFT = 'left';
126
127	/**
128	 * RIGHT axis
129	 *
130	 * @var int
131	 */
132	const RIGHT = 'right';
133
134	/**
135	 * Top axis
136	 *
137	 * @var int
138	 */
139	const TOP = 'top';
140
141	/**
142	 * Bottom axis
143	 *
144	 * @var int
145	 */
146	const BOTTOM = 'bottom';
147
148	/**
149	 * Both left/right or top/bottom axis
150	 *
151	 * @var int
152	 */
153	const BOTH = 'both';
154
155	/**
156	 * Build the plot
157	 *
158	 */
159	public function __construct() {
160
161		parent::__construct();
162
163		$this->grid = new awGrid;
164		$this->grid->setBackgroundColor(new awWhite);
165
166		$this->padding->add(20, 0, 0, 20);
167
168		$this->xAxis = new awAxis;
169		$this->xAxis->addTick('major', new awTick(0, 5));
170		$this->xAxis->addTick('minor', new awTick(0, 3));
171		$this->xAxis->setTickStyle(awTick::OUT);
172		$this->xAxis->label->setFont(new awDejaVuSans(7));
173
174		$this->yAxis = new awAxis;
175		$this->yAxis->auto(TRUE);
176		$this->yAxis->addTick('major', new awTick(0, 5));
177		$this->yAxis->addTick('minor', new awTick(0, 3));
178		$this->yAxis->setTickStyle(awTick::OUT);
179		$this->yAxis->setNumberByTick('minor', 'major', 3);
180		$this->yAxis->label->setFont(new awDejaVuSans(7));
181		$this->yAxis->title->setAngle(90);
182
183	}
184
185	/**
186	 * Get plot values
187	 *
188	 * @return array
189	 */
190	public function getValues() {
191		return $this->datay;
192	}
193
194	/**
195	 * Reduce number of values in the plot
196	 *
197	 * @param int $number Reduce number of values to $number
198	 */
199	public function reduce($number) {
200
201		$count = count($this->datay);
202		$ratio = ceil($count / $number);
203
204		if($ratio > 1) {
205
206			$tmpy = $this->datay;
207			$datay = array();
208
209			$datax = array();
210			$cbLabel = $this->xAxis->label->getCallbackFunction();
211
212			for($i = 0; $i < $count; $i += $ratio) {
213
214				$slice = array_slice($tmpy, $i, $ratio);
215				$datay[] = array_sum($slice) / count($slice);
216
217				// Reduce data on X axis if needed
218				if($cbLabel !== NULL) {
219					$datax[] = $cbLabel($i + round($ratio / 2));
220				}
221
222			}
223
224			$this->setValues($datay);
225
226			if($cbLabel !== NULL) {
227				$this->xAxis->setLabelText($datax);
228			}
229
230
231		}
232
233	}
234
235	/**
236	 * Count values in the plot
237	 *
238	 * @return int
239	 */
240	public function getXAxisNumber() {
241		list($min, $max) = $this->xAxis->getRange();
242		return ($max - $min + 1);
243	}
244
245	/**
246	 * Change X axis
247	 *
248	 * @param int $axis
249	 */
250	public function setXAxis($axis) {
251		$this->xAxisPosition = $axis;
252	}
253
254	/**
255	 * Get X axis
256	 *
257	 * @return int
258	 */
259	public function getXAxis() {
260		return $this->xAxisPosition;
261	}
262
263	/**
264	 * Set X axis on zero
265	 *
266	 * @param bool $zero
267	 */
268	public function setXAxisZero($zero) {
269		$this->xAxisZero = (bool)$zero;
270	}
271
272	/**
273	 * Set Y axis on zero
274	 *
275	 * @param bool $zero
276	 */
277	public function setYAxisZero($zero) {
278		$this->yAxisZero = (bool)$zero;
279	}
280
281	/**
282	 * Change Y axis
283	 *
284	 * @param int $axis
285	 */
286	public function setYAxis($axis) {
287		$this->yAxisPosition = $axis;
288	}
289
290	/**
291	 * Get Y axis
292	 *
293	 * @return int
294	 */
295	public function getYAxis() {
296		return $this->yAxisPosition;
297	}
298
299	/**
300	 * Change min value for Y axis
301	 * Set NULL for auto selection.
302	 *
303	 * @param float $value
304	 */
305	public function setYMin($value) {
306		$this->yMin = $value;
307		$this->yAxis->auto(FALSE);
308		$this->updateAxis();
309	}
310
311	/**
312	 * Change max value for Y axis
313	 * Set NULL for auto selection.
314	 *
315	 * @param float $value
316	 */
317	public function setYMax($value) {
318		$this->yMax = $value;
319		$this->yAxis->auto(FALSE);
320		$this->updateAxis();
321	}
322
323	/**
324	 * Change min value for X axis
325	 * Set NULL for auto selection.
326	 *
327	 * @param float $value
328	 */
329	public function setXMin($value) {
330		$this->xMin = $value;
331		$this->updateAxis();
332	}
333
334	/**
335	 * Change max value for X axis
336	 * Set NULL for auto selection.
337	 *
338	 * @param float $value
339	 */
340	public function setXMax($value) {
341		$this->xMax = $value;
342		$this->updateAxis();
343	}
344
345	/**
346	 * Get min value for Y axis
347	 *
348	 * @return float $value
349	 */
350	public function getYMin() {
351		if($this->auto) {
352			if(is_null($this->yMin)) {
353				$min = array_min($this->datay);
354				if($min > 0) {
355					return 0;
356				}
357			}
358		}
359		return is_null($this->yMin) ? array_min($this->datay) : (float)$this->yMin;
360	}
361
362	/**
363	 * Get max value for Y axis
364	 *
365	 * @return float $value
366	 */
367	public function getYMax() {
368		if($this->auto) {
369			if(is_null($this->yMax)) {
370				$max = array_max($this->datay);
371				if($max < 0) {
372					return 0;
373				}
374			}
375		}
376		return is_null($this->yMax) ? array_max($this->datay) : (float)$this->yMax;
377	}
378
379	/**
380	 * Get min value for X axis
381	 *
382	 * @return float $value
383	 */
384	public function getXMin() {
385		return floor(is_null($this->xMin) ? array_min($this->datax) : $this->xMin);
386	}
387
388	/**
389	 * Get max value for X axis
390	 *
391	 * @return float $value
392	 */
393	public function getXMax() {
394		return (ceil(is_null($this->xMax) ? array_max($this->datax) : (float)$this->xMax)) + ($this->getXCenter() ? 1 : 0);
395	}
396
397	/**
398	 * Get min value with spaces for Y axis
399	 *
400	 * @return float $value
401	 */
402	public function getRealYMin() {
403		$min = $this->getYMin();
404		if($this->space->bottom !== NULL) {
405			$interval = ($this->getYMax() - $min) * $this->space->bottom / 100;
406			return $min - $interval;
407		} else {
408			return is_null($this->yMin) ? $min : (float)$this->yMin;
409		}
410	}
411
412	/**
413	 * Get max value with spaces for Y axis
414	 *
415	 * @return float $value
416	 */
417	public function getRealYMax() {
418		$max = $this->getYMax();
419		if($this->space->top !== NULL) {
420			$interval = ($max - $this->getYMin()) * $this->space->top / 100;
421			return $max + $interval;
422		} else {
423			return is_null($this->yMax) ? $max : (float)$this->yMax;
424		}
425	}
426
427	public function init(awDrawer $drawer) {
428
429		list($x1, $y1, $x2, $y2) = $this->getPosition();
430
431		// Get space informations
432		list($leftSpace, $rightSpace, $topSpace, $bottomSpace) = $this->getSpace($x2 - $x1, $y2 - $y1);
433
434		$this->xAxis->setPadding($leftSpace, $rightSpace);
435
436		if($this->space->bottom > 0 or $this->space->top > 0) {
437
438			list($min, $max) = $this->yAxis->getRange();
439			$interval = $max - $min;
440
441			$this->yAxis->setRange(
442				$min - $interval * $this->space->bottom / 100,
443				$max + $interval * $this->space->top / 100
444			);
445
446		}
447
448		// Auto-scaling mode
449		$this->yAxis->autoScale();
450
451		// Number of labels is not specified
452		if($this->yAxis->getLabelNumber() === NULL) {
453			$number = round(($y2 - $y1) / 75) + 2;
454			$this->yAxis->setLabelNumber($number);
455		}
456
457		$this->xAxis->line->setX($x1, $x2);
458		$this->yAxis->line->setY($y2, $y1);
459
460		// Set ticks
461		/* <php5> */
462		$this->xAxis->tick('major')->setNumber($this->getXAxisNumber());
463		$this->yAxis->tick('major')->setNumber($this->yAxis->getLabelNumber());
464		/* </php5> */
465		/* <php4> --
466		$this->xAxis->ticks['major']->setNumber($this->getXAxisNumber());
467		$this->yAxis->ticks['major']->setNumber($this->yAxis->getLabelNumber());
468		-- </php4> */
469
470		// Center X axis on zero
471		if($this->xAxisZero) {
472			$this->xAxis->setYCenter($this->yAxis, 0);
473		}
474
475		// Center Y axis on zero
476		if($this->yAxisZero) {
477			$this->yAxis->setXCenter($this->xAxis, 0);
478		}
479
480		// Set axis labels
481		$labels = array();
482		for($i = 0, $count = $this->getXAxisNumber(); $i < $count; $i++) {
483			$labels[] = $i;
484		}
485		$this->xAxis->label->set($labels);
486
487		parent::init($drawer);
488
489		list($x1, $y1, $x2, $y2) = $this->getPosition();
490
491		list($leftSpace, $rightSpace) = $this->getSpace($x2 - $x1, $y2 - $y1);
492
493		// Create the grid
494		$this->createGrid();
495
496		// Draw the grid
497		$this->grid->setSpace($leftSpace, $rightSpace, 0, 0);
498		$this->grid->draw($drawer, $x1, $y1, $x2, $y2);
499
500	}
501
502	public function drawEnvelope(awDrawer $drawer) {
503
504		list($x1, $y1, $x2, $y2) = $this->getPosition();
505
506		if($this->getXCenter()) {
507			$size = $this->xAxis->getDistance(0, 1);
508			$this->xAxis->label->move($size / 2, 0);
509			$this->xAxis->label->hideLast(TRUE);
510		}
511
512		// Draw top axis
513		if($this->xAxisPosition === awPlot::TOP or $this->xAxisPosition === awPlot::BOTH) {
514			$top = clone $this->xAxis;
515			if($this->xAxisZero === FALSE) {
516				$top->line->setY($y1, $y1);
517			}
518			$top->label->setAlign(NULL, awLabel::TOP);
519			$top->label->move(0, -3);
520			$top->title->move(0, -25);
521			$top->draw($drawer);
522		}
523
524		// Draw bottom axis
525		if($this->xAxisPosition === awPlot::BOTTOM or $this->xAxisPosition === awPlot::BOTH) {
526			$bottom = clone $this->xAxis;
527			if($this->xAxisZero === FALSE) {
528				$bottom->line->setY($y2, $y2);
529			}
530			$bottom->label->setAlign(NULL, awLabel::BOTTOM);
531			$bottom->label->move(0, 3);
532			$bottom->reverseTickStyle();
533			$bottom->title->move(0, 25);
534			$bottom->draw($drawer);
535		}
536
537		// Draw left axis
538		if($this->yAxisPosition === awPlot::LEFT or $this->yAxisPosition === awPlot::BOTH) {
539			$left = clone $this->yAxis;
540			if($this->yAxisZero === FALSE) {
541				$left->line->setX($x1, $x1);
542			}
543			$left->label->setAlign(awLabel::RIGHT);
544			$left->label->move(-6, 0);
545			$left->title->move(-25, 0);
546			$left->draw($drawer);
547		}
548
549		// Draw right axis
550		if($this->yAxisPosition === awPlot::RIGHT or $this->yAxisPosition === awPlot::BOTH) {
551			$right = clone $this->yAxis;
552			if($this->yAxisZero === FALSE) {
553				$right->line->setX($x2, $x2);
554			}
555			$right->label->setAlign(awLabel::LEFT);
556			$right->label->move(6, 0);
557			$right->reverseTickStyle();
558			$right->title->move(25, 0);
559			$right->draw($drawer);
560		}
561
562	}
563
564	protected function createGrid() {
565
566		$max = $this->getRealYMax();
567		$min = $this->getRealYMin();
568
569		$number = $this->yAxis->getLabelNumber() - 1;
570
571		if($number < 1) {
572			return;
573		}
574
575		// Horizontal lines of the grid
576
577		$h = array();
578		for($i = 0; $i <= $number; $i++) {
579			$h[] = $i / $number;
580		}
581
582		// Vertical lines
583
584		$major = $this->yAxis->tick('major');
585		$interval = $major->getInterval();
586		$number = $this->getXAxisNumber() - 1;
587
588		$w = array();
589
590		if($number > 0) {
591
592			for($i = 0; $i <= $number; $i++) {
593				if($i%$interval === 0) {
594					$w[] = $i / $number;
595				}
596			}
597
598		}
599
600		$this->grid->setGrid($w, $h);
601
602	}
603
604	/**
605	 * Change values of Y axis
606	 * This method ignores not numeric values
607	 *
608	 * @param array $datay
609	 * @param array $datax
610	 */
611	public function setValues($datay, $datax = NULL) {
612
613		$this->checkArray($datay);
614
615		foreach($datay as $key => $value) {
616			unset($datay[$key]);
617			$datay[(int)$key] = $value;
618		}
619
620		if($datax === NULL) {
621			$datax = array();
622			for($i = 0; $i < count($datay); $i++) {
623				$datax[] = $i;
624			}
625		} else {
626			foreach($datax as $key => $value) {
627				unset($datax[$key]);
628				$datax[(int)$key] = $value;
629			}
630		}
631
632		$this->checkArray($datax);
633
634		if(count($datay) === count($datax)) {
635
636			// Set values
637			$this->datay = $datay;
638			$this->datax = $datax;
639			// Update axis with the new awvalues
640			$this->updateAxis();
641		} else {
642			trigger_error("Plots must have the same number of X and Y points", E_USER_ERROR);
643		}
644
645	}
646
647	/**
648	 * Return begin and end values
649	 *
650	 * @return array
651	 */
652	protected function getLimit() {
653
654		$i = 0;
655		while(array_key_exists($i, $this->datay) and $this->datay[$i] === NULL) {
656			$i++;
657		}
658		$start = $i;
659		$i = count($this->datay) - 1;
660		while(array_key_exists($i, $this->datay) and $this->datay[$i] === NULL) {
661			$i--;
662		}
663		$stop = $i;
664
665		return array($start, $stop);
666
667	}
668
669	/**
670	 * Return TRUE if labels must be centered on X axis, FALSE otherwise
671	 *
672	 * @return bool
673	 */
674	abstract public function getXCenter();
675
676	private function updateAxis() {
677
678		$this->xAxis->setRange(
679			$this->getXMin(),
680			$this->getXMax()
681		);
682		$this->yAxis->setRange(
683			$this->getRealYMin(),
684			$this->getRealYMax()
685		);
686
687	}
688
689	private function checkArray(&$array) {
690
691		if(is_array($array) === FALSE) {
692			trigger_error("You tried to set a value that is not an array", E_USER_ERROR);
693		}
694
695		foreach($array as $key => $value) {
696			if(is_numeric($value) === FALSE and is_null($value) === FALSE) {
697				trigger_error("Expected numeric values for the plot", E_USER_ERROR);
698			}
699		}
700
701		if(count($array) < 1) {
702			trigger_error("Your plot must have at least 1 value", E_USER_ERROR);
703		}
704
705	}
706
707}
708
709registerClass('Plot', TRUE);
710
711class awPlotAxis {
712
713	/**
714	 * Left axis
715	 *
716	 * @var Axis
717	 */
718	public $left;
719
720	/**
721	 * Right axis
722	 *
723	 * @var Axis
724	 */
725	public $right;
726
727	/**
728	 * Top axis
729	 *
730	 * @var Axis
731	 */
732	public $top;
733
734	/**
735	 * Bottom axis
736	 *
737	 * @var Axis
738	 */
739	public $bottom;
740
741	/**
742	 * Build the group of axis
743	 */
744	public function __construct() {
745
746		$this->left = new awAxis;
747		$this->left->auto(TRUE);
748		$this->left->label->setAlign(awLabel::RIGHT);
749		$this->left->label->move(-6, 0);
750		$this->yAxis($this->left);
751		$this->left->setTickStyle(awTick::OUT);
752		$this->left->title->move(-25, 0);
753
754		$this->right = new awAxis;
755		$this->right->auto(TRUE);
756		$this->right->label->setAlign(awLabel::LEFT);
757		$this->right->label->move(6, 0);
758		$this->yAxis($this->right);
759		$this->right->setTickStyle(awTick::IN);
760		$this->right->title->move(25, 0);
761
762		$this->top = new awAxis;
763		$this->top->label->setAlign(NULL, awLabel::TOP);
764		$this->top->label->move(0, -3);
765		$this->xAxis($this->top);
766		$this->top->setTickStyle(awTick::OUT);
767		$this->top->title->move(0, -25);
768
769		$this->bottom = new awAxis;
770		$this->bottom->label->setAlign(NULL, awLabel::BOTTOM);
771		$this->bottom->label->move(0, 3);
772		$this->xAxis($this->bottom);
773		$this->bottom->setTickStyle(awTick::IN);
774		$this->bottom->title->move(0, 25);
775
776	}
777
778	protected function xAxis(awAxis $axis) {
779
780		$axis->addTick('major', new awTick(0, 5));
781		$axis->addTick('minor', new awTick(0, 3));
782		$axis->label->setFont(new awDejaVuSans(7));
783
784	}
785
786	protected function yAxis(awAxis $axis) {
787
788		$axis->addTick('major', new awTick(0, 5));
789		$axis->addTick('minor', new awTick(0, 3));
790		$axis->setNumberByTick('minor', 'major', 3);
791		$axis->label->setFont(new awDejaVuSans(7));
792		$axis->title->setAngle(90);
793
794	}
795
796}
797
798registerClass('PlotAxis');
799
800/**
801 * A graph with axis can contain some groups of components
802 *
803 * @package Artichow
804 */
805class awPlotGroup extends awComponentGroup {
806
807	/**
808	 * Grid properties
809	 *
810	 * @var Grid
811	 */
812	public $grid;
813
814	/**
815	 * Left, right, top and bottom axis
816	 *
817	 * @var PlotAxis
818	 */
819	public $axis;
820
821	/**
822	 * Set the X axis on zero
823	 *
824	 * @var bool
825	 */
826	protected $xAxisZero = TRUE;
827
828	/**
829	 * Set the Y axis on zero
830	 *
831	 * @var bool
832	 */
833	protected $yAxisZero = FALSE;
834
835	/**
836	 * Real axis used for Y axis
837	 *
838	 * @var string
839	 */
840	private $yRealAxis = awPlot::LEFT;
841
842	/**
843	 * Real axis used for X axis
844	 *
845	 * @var string
846	 */
847	private $xRealAxis = awPlot::BOTTOM;
848
849	/**
850	 * Change min value for Y axis
851	 *
852	 * @var mixed
853	 */
854	private $yMin = NULL;
855
856	/**
857	 * Change max value for Y axis
858	 *
859	 * @var mixed
860	 */
861	private $yMax = NULL;
862
863	/**
864	 * Change min value for X axis
865	 *
866	 * @var mixed
867	 */
868	private $xMin = NULL;
869
870	/**
871	 * Change max value for X axis
872	 *
873	 * @var mixed
874	 */
875	private $xMax = NULL;
876
877	/**
878	 * Build the PlotGroup
879	 *
880	 */
881	public function __construct() {
882
883		parent::__construct();
884
885		$this->grid = new awGrid;
886		$this->grid->setBackgroundColor(new awWhite);
887
888		$this->axis = new awPlotAxis;
889
890	}
891
892	/**
893	 * Set the X axis on zero or not
894	 *
895	 * @param bool $zero
896	 */
897	public function setXAxisZero($zero) {
898		$this->xAxisZero = (bool)$zero;
899	}
900
901	/**
902	 * Set the Y axis on zero or not
903	 *
904	 * @param bool $zero
905	 */
906	public function setYAxisZero($zero) {
907		$this->yAxisZero = (bool)$zero;
908	}
909
910	/**
911	 * Change min value for Y axis
912	 * Set NULL for auto selection.
913	 *
914	 * @param float $value
915	 */
916	public function setYMin($value) {
917		$this->axis->left->auto(FALSE);
918		$this->axis->right->auto(FALSE);
919		$this->yMin = $value;
920	}
921
922	/**
923	 * Change max value for Y axis
924	 * Set NULL for auto selection.
925	 *
926	 * @param float $value
927	 */
928	public function setYMax($value) {
929		$this->axis->left->auto(FALSE);
930		$this->axis->right->auto(FALSE);
931		$this->yMax = $value;
932	}
933
934	/**
935	 * Change min value for X axis
936	 * Set NULL for auto selection.
937	 *
938	 * @param float $value
939	 */
940	public function setXMin($value) {
941		$this->xMin = $value;
942	}
943
944	/**
945	 * Change max value for X axis
946	 * Set NULL for auto selection.
947	 *
948	 * @param float $value
949	 */
950	public function setXMax($value) {
951		$this->xMax = $value;
952	}
953
954	/**
955	 * Get min value for X axis
956	 *
957	 * @return float $value
958	 */
959	public function getXMin() {
960
961		return $this->getX('min');
962
963	}
964
965	/**
966	 * Get max value for X axis
967	 *
968	 * @return float $value
969	 */
970	public function getXMax() {
971
972		return $this->getX('max');
973
974	}
975
976	private function getX($type) {
977
978		switch($type) {
979			case 'max' :
980				if($this->xMax !== NULL) {
981					return $this->xMax;
982				}
983				break;
984			case 'min' :
985				if($this->xMin !== NULL) {
986					return $this->xMin;
987				}
988				break;
989		}
990
991		$value = NULL;
992		$get = 'getX'.ucfirst($type);
993
994		for($i = 0; $i < count($this->components); $i++) {
995
996			$component = $this->components[$i];
997
998			if($value === NULL) {
999				$value = $component->$get();
1000			} else {
1001				$value = $type($value, $component->$get());
1002			}
1003
1004		}
1005
1006		return $value;
1007
1008	}
1009
1010	/**
1011	 * Get min value with spaces for Y axis
1012	 *
1013	 * @param string $axis Axis name
1014	 * @return float $value
1015	 */
1016	public function getRealYMin($axis = NULL) {
1017
1018		if($axis === NULL) {
1019			return NULL;
1020		}
1021
1022		$min = $this->getRealY('min', $axis);
1023		$max = $this->getRealY('max', $axis);
1024
1025		if($this->space->bottom !== NULL) {
1026			$interval = ($min - $max) * $this->space->bottom / 100;
1027			return $min + $interval;
1028		} else {
1029			return $min;
1030		}
1031
1032	}
1033
1034	/**
1035	 * Get max value with spaces for Y axis
1036	 *
1037	 * @param string $axis Axis name
1038	 * @return float $value
1039	 */
1040	public function getRealYMax($axis = NULL) {
1041
1042		if($axis === NULL) {
1043			return NULL;
1044		}
1045
1046		$min = $this->getRealY('min', $axis);
1047		$max = $this->getRealY('max', $axis);
1048
1049		if($this->space->top !== NULL) {
1050			$interval = ($max - $min) * $this->space->top / 100;
1051			return $max + $interval;
1052		} else {
1053			return $max;
1054		}
1055
1056	}
1057
1058	private function getRealY($type, $axis) {
1059
1060		switch($type) {
1061			case 'max' :
1062				if($this->yMax !== NULL) {
1063					return $this->yMax;
1064				}
1065				break;
1066			case 'min' :
1067				if($this->yMin !== NULL) {
1068					return $this->yMin;
1069				}
1070				break;
1071		}
1072
1073		$value = NULL;
1074		$get = 'getY'.ucfirst($type);
1075
1076		for($i = 0; $i < count($this->components); $i++) {
1077
1078			$component = $this->components[$i];
1079
1080			switch($axis) {
1081
1082				case awPlot::LEFT :
1083				case awPlot::RIGHT :
1084					$test = ($component->getYAxis() === $axis);
1085					break;
1086				default :
1087					$test = FALSE;
1088
1089			}
1090
1091			if($test) {
1092				if($value === NULL) {
1093					$value = $component->$get();
1094				} else {
1095					$value = $type($value, $component->$get());
1096				}
1097			}
1098
1099		}
1100
1101		return $value;
1102
1103	}
1104
1105	public function init(awDrawer $drawer) {
1106
1107		list($x1, $y1, $x2, $y2) = $this->getPosition();
1108
1109		// Get PlotGroup space
1110		list($leftSpace, $rightSpace, $topSpace, $bottomSpace) = $this->getSpace($x2 - $x1, $y2 - $y1);
1111
1112		// Count values in the group
1113		$values = $this->getXAxisNumber();
1114
1115		// Init the PlotGroup
1116		$this->axis->top->line->setX($x1, $x2);
1117		$this->axis->bottom->line->setX($x1, $x2);
1118		$this->axis->left->line->setY($y2, $y1);
1119		$this->axis->right->line->setY($y2, $y1);
1120
1121		$this->axis->top->setPadding($leftSpace, $rightSpace);
1122		$this->axis->bottom->setPadding($leftSpace, $rightSpace);
1123
1124		$xMin = $this->getXMin();
1125		$xMax = $this->getXMax();
1126
1127		$this->axis->top->setRange($xMin, $xMax);
1128		$this->axis->bottom->setRange($xMin, $xMax);
1129
1130		for($i = 0; $i < count($this->components); $i++) {
1131
1132			/* <php5> */
1133			$component = $this->components[$i];
1134			/* </php5> */
1135			/* <php4> --
1136			$component = &$this->components[$i];
1137			-- </php4> */
1138			$component->auto($this->auto);
1139
1140			// Copy space to the component
1141
1142			$component->setSpace($this->space->left, $this->space->right, $this->space->top, $this->space->bottom);
1143
1144			$component->xAxis->setPadding($leftSpace, $rightSpace);
1145			$component->xAxis->line->setX($x1, $x2);
1146
1147			$component->yAxis->line->setY($y2, $y1);
1148
1149		}
1150
1151		// Set Y axis range
1152		foreach(array('left', 'right') as $axis) {
1153
1154			if($this->isAxisUsed($axis)) {
1155
1156				$min = $this->getRealYMin($axis);
1157				$max = $this->getRealYMax($axis);
1158
1159				$interval = $max - $min;
1160
1161				$this->axis->{$axis}->setRange(
1162					$min - $interval * $this->space->bottom / 100,
1163					$max + $interval * $this->space->top / 100
1164				);
1165
1166				// Auto-scaling mode
1167				$this->axis->{$axis}->autoScale();
1168
1169			}
1170
1171		}
1172
1173		if($this->axis->left->getLabelNumber() === NULL) {
1174			$number = round(($y2 - $y1) / 75) + 2;
1175			$this->axis->left->setLabelNumber($number);
1176		}
1177
1178		if($this->axis->right->getLabelNumber() === NULL) {
1179			$number = round(($y2 - $y1) / 75) + 2;
1180			$this->axis->right->setLabelNumber($number);
1181		}
1182
1183		// Center labels on X axis if needed
1184		$test = array(awPlot::TOP => FALSE, awPlot::BOTTOM => FALSE);
1185
1186		for($i = 0; $i < count($this->components); $i++) {
1187
1188			/* <php5> */
1189			$component = $this->components[$i];
1190			/* </php5> */
1191			/* <php4> --
1192			$component = &$this->components[$i];
1193			-- </php4> */
1194
1195			if($component->getValues() !== NULL) {
1196
1197				$axis = $component->getXAxis();
1198
1199				if($test[$axis] === FALSE) {
1200
1201					// Center labels for bar plots
1202					if($component->getXCenter()) {
1203						$size = $this->axis->{$axis}->getDistance(0, 1);
1204						$this->axis->{$axis}->label->move($size / 2, 0);
1205						$this->axis->{$axis}->label->hideLast(TRUE);
1206						$test[$axis] = TRUE;
1207					}
1208
1209				}
1210
1211			}
1212
1213
1214		}
1215
1216		// Set axis labels
1217		$labels = array();
1218		for($i = $xMin; $i <= $xMax; $i++) {
1219			$labels[] = $i;
1220		}
1221		if($this->axis->top->label->count() === 0) {
1222			$this->axis->top->label->set($labels);
1223		}
1224		if($this->axis->bottom->label->count() === 0) {
1225			$this->axis->bottom->label->set($labels);
1226		}
1227
1228		// Set ticks
1229		/* <php5> */
1230		$this->axis->top->tick('major')->setNumber($values);
1231		$this->axis->bottom->tick('major')->setNumber($values);
1232		$this->axis->left->tick('major')->setNumber($this->axis->left->getLabelNumber());
1233		$this->axis->right->tick('major')->setNumber($this->axis->right->getLabelNumber());
1234		/* </php5> */
1235		/* <php4> --
1236		$this->axis->top->ticks['major']->setNumber($values);
1237		$this->axis->bottom->ticks['major']->setNumber($values);
1238		$this->axis->left->ticks['major']->setNumber($this->axis->left->getLabelNumber());
1239		$this->axis->right->ticks['major']->setNumber($this->axis->right->getLabelNumber());
1240		-- </php4> */
1241
1242		// Set X axis on zero
1243		if($this->xAxisZero) {
1244			$axis = $this->selectYAxis();
1245			$this->axis->bottom->setYCenter($axis, 0);
1246			$this->axis->top->setYCenter($axis, 0);
1247		}
1248
1249		// Set Y axis on zero
1250		if($this->yAxisZero) {
1251			$axis = $this->selectXAxis();
1252			$this->axis->left->setXCenter($axis, 1);
1253			$this->axis->right->setXCenter($axis, 1);
1254		}
1255
1256		parent::init($drawer);
1257
1258		list($leftSpace, $rightSpace, $topSpace, $bottomSpace) = $this->getSpace($x2 - $x1, $y2 - $y1);
1259
1260		// Create the grid
1261		$this->createGrid();
1262
1263		// Draw the grid
1264		$this->grid->setSpace($leftSpace, $rightSpace, 0, 0);
1265		$this->grid->draw($drawer, $x1, $y1, $x2, $y2);
1266
1267	}
1268
1269	public function drawComponent(awDrawer $drawer, $x1, $y1, $x2, $y2, $aliasing) {
1270
1271		$xMin = $this->getXMin();
1272		$xMax = $this->getXMax();
1273
1274		$maxLeft = $this->getRealYMax(awPlot::LEFT);
1275		$maxRight = $this->getRealYMax(awPlot::RIGHT);
1276
1277		$minLeft = $this->getRealYMin(awPlot::LEFT);
1278		$minRight = $this->getRealYMin(awPlot::RIGHT);
1279
1280		foreach($this->components as $component) {
1281
1282			$min = $component->getYMin();
1283			$max = $component->getYMax();
1284
1285			// Set component minimum and maximum
1286			if($component->getYAxis() === awPlot::LEFT) {
1287
1288				list($min, $max) = $this->axis->left->getRange();
1289
1290				$component->setYMin($min);
1291				$component->setYMax($max);
1292
1293			} else {
1294
1295				list($min, $max) = $this->axis->right->getRange();
1296
1297				$component->setYMin($min);
1298				$component->setYMax($max);
1299
1300			}
1301
1302			$component->setXAxisZero($this->xAxisZero);
1303			$component->setYAxisZero($this->yAxisZero);
1304
1305			$component->xAxis->setRange($xMin, $xMax);
1306
1307			$component->drawComponent(
1308				$drawer,
1309				$x1, $y1,
1310				$x2, $y2,
1311				$aliasing
1312			);
1313
1314			$component->setYMin($min);
1315			$component->setYMax($max);
1316
1317		}
1318
1319	}
1320
1321	public function drawEnvelope(awDrawer $drawer) {
1322
1323		list($x1, $y1, $x2, $y2) = $this->getPosition();
1324
1325		// Hide unused axis
1326		foreach(array(awPlot::LEFT, awPlot::RIGHT, awPlot::TOP, awPlot::BOTTOM) as $axis) {
1327			if($this->isAxisUsed($axis) === FALSE) {
1328				$this->axis->{$axis}->hide(TRUE);
1329			}
1330		}
1331
1332		// Draw top axis
1333		$top = $this->axis->top;
1334		if($this->xAxisZero === FALSE) {
1335			$top->line->setY($y1, $y1);
1336		}
1337		$top->draw($drawer);
1338
1339		// Draw bottom axis
1340		$bottom = $this->axis->bottom;
1341		if($this->xAxisZero === FALSE) {
1342			$bottom->line->setY($y2, $y2);
1343		}
1344		$bottom->draw($drawer);
1345
1346		// Draw left axis
1347		$left = $this->axis->left;
1348		if($this->yAxisZero === FALSE) {
1349			$left->line->setX($x1, $x1);
1350		}
1351		$left->draw($drawer);
1352
1353		// Draw right axis
1354		$right = $this->axis->right;
1355		if($this->yAxisZero === FALSE) {
1356			$right->line->setX($x2, $x2);
1357		}
1358		$right->draw($drawer);
1359
1360	}
1361
1362	/**
1363	 * Is the specified axis used ?
1364	 *
1365	 * @param string $axis Axis name
1366	 * @return bool
1367	 */
1368	protected function isAxisUsed($axis) {
1369
1370		for($i = 0; $i < count($this->components); $i++) {
1371
1372			$component = $this->components[$i];
1373
1374			switch($axis) {
1375
1376				case awPlot::LEFT :
1377				case awPlot::RIGHT :
1378					if($component->getYAxis() === $axis) {
1379						return TRUE;
1380					}
1381					break;
1382
1383				case awPlot::TOP :
1384				case awPlot::BOTTOM :
1385					if($component->getXAxis() === $axis) {
1386						return TRUE;
1387					}
1388					break;
1389
1390			}
1391
1392		}
1393
1394		return FALSE;
1395
1396	}
1397
1398	protected function createGrid() {
1399
1400		$max = $this->getRealYMax(awPlot::LEFT);
1401		$min = $this->getRealYMin(awPlot::RIGHT);
1402
1403		// Select axis (left if possible, right otherwise)
1404		$axis = $this->selectYAxis();
1405
1406		$number = $axis->getLabelNumber() - 1;
1407
1408		if($number < 1) {
1409			return;
1410		}
1411
1412		// Horizontal lines of grid
1413
1414		$h = array();
1415		for($i = 0; $i <= $number; $i++) {
1416			$h[] = $i / $number;
1417		}
1418
1419		// Vertical lines
1420
1421		$major = $axis->tick('major');
1422		$interval = $major->getInterval();
1423		$number = $this->getXAxisNumber() - 1;
1424
1425		$w = array();
1426
1427		if($number > 0) {
1428
1429			for($i = 0; $i <= $number; $i++) {
1430				if($i%$interval === 0) {
1431					$w[] = $i / $number;
1432				}
1433			}
1434
1435		}
1436
1437		$this->grid->setGrid($w, $h);
1438
1439	}
1440
1441	protected function selectYAxis(){
1442
1443		// Select axis (left if possible, right otherwise)
1444		if($this->isAxisUsed(awPlot::LEFT)) {
1445			$axis = $this->axis->left;
1446		} else {
1447			$axis = $this->axis->right;
1448		}
1449
1450		return $axis;
1451
1452	}
1453
1454	protected function selectXAxis(){
1455
1456		// Select axis (bottom if possible, top otherwise)
1457		if($this->isAxisUsed(awPlot::BOTTOM)) {
1458			$axis = $this->axis->bottom;
1459		} else {
1460			$axis = $this->axis->top;
1461		}
1462
1463		return $axis;
1464
1465	}
1466
1467	protected function getXAxisNumber() {
1468		$offset = $this->components[0];
1469		$max = $offset->getXAxisNumber();
1470		for($i = 1; $i < count($this->components); $i++) {
1471			$offset = $this->components[$i];
1472			$max = max($max, $offset->getXAxisNumber());
1473		}
1474		return $max;
1475	}
1476
1477}
1478
1479registerClass('PlotGroup');
1480?>