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 */
27 class awPlot extends awComponent {
28
29	/**
30	 * Values for Y axis
31	 *
32	 * @var array
33	 */
34	var $datay;
35
36	/**
37	 * Values for X axis
38	 *
39	 * @var array
40	 */
41	var $datax;
42
43	/**
44	 * Grid properties
45	 *
46	 * @var Grid
47	 */
48	var $grid;
49
50	/**
51	 * X axis
52	 *
53	 * @var Axis
54	 */
55	var $xAxis;
56
57	/**
58	 * Y axis
59	 *
60	 * @var Axis
61	 */
62	var $yAxis;
63
64	/**
65	 * Position of X axis
66	 *
67	 * @var int
68	 */
69	var $xAxisPosition = PLOT_BOTTOM;
70
71	/**
72	 * Set X axis on zero ?
73	 *
74	 * @var bool
75	 */
76	var $xAxisZero = TRUE;
77
78	/**
79	 * Set Y axis on zero ?
80	 *
81	 * @var bool
82	 */
83	var $yAxisZero = FALSE;
84
85	/**
86	 * Position of Y axis
87	 *
88	 * @var int
89	 */
90	var $yAxisPosition = PLOT_LEFT;
91
92	/**
93	 * Change min value for Y axis
94	 *
95	 * @var mixed
96	 */
97	var $yMin = NULL;
98
99	/**
100	 * Change max value for Y axis
101	 *
102	 * @var mixed
103	 */
104	var $yMax = NULL;
105
106	/**
107	 * Change min value for X axis
108	 *
109	 * @var mixed
110	 */
111	var $xMin = NULL;
112
113	/**
114	 * Change max value for X axis
115	 *
116	 * @var mixed
117	 */
118	var $xMax = NULL;
119
120	/**
121	 * Left axis
122	 *
123	 * @var int
124	 */
125
126
127	/**
128	 * RIGHT axis
129	 *
130	 * @var int
131	 */
132
133
134	/**
135	 * Top axis
136	 *
137	 * @var int
138	 */
139
140
141	/**
142	 * Bottom axis
143	 *
144	 * @var int
145	 */
146
147
148	/**
149	 * Both left/right or top/bottom axis
150	 *
151	 * @var int
152	 */
153
154
155	/**
156	 * Build the plot
157	 *
158	 */
159	 function awPlot() {
160
161		parent::awComponent();
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(TICK_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(TICK_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	 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	 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	 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	 function setXAxis($axis) {
251		$this->xAxisPosition = $axis;
252	}
253
254	/**
255	 * Get X axis
256	 *
257	 * @return int
258	 */
259	 function getXAxis() {
260		return $this->xAxisPosition;
261	}
262
263	/**
264	 * Set X axis on zero
265	 *
266	 * @param bool $zero
267	 */
268	 function setXAxisZero($zero) {
269		$this->xAxisZero = (bool)$zero;
270	}
271
272	/**
273	 * Set Y axis on zero
274	 *
275	 * @param bool $zero
276	 */
277	 function setYAxisZero($zero) {
278		$this->yAxisZero = (bool)$zero;
279	}
280
281	/**
282	 * Change Y axis
283	 *
284	 * @param int $axis
285	 */
286	 function setYAxis($axis) {
287		$this->yAxisPosition = $axis;
288	}
289
290	/**
291	 * Get Y axis
292	 *
293	 * @return int
294	 */
295	 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	 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	 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	 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	 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	 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	 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	 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	 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	 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	 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	 function init($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
462		$this->xAxis->ticks['major']->setNumber($this->getXAxisNumber());
463		$this->yAxis->ticks['major']->setNumber($this->yAxis->getLabelNumber());
464
465
466		// Center X axis on zero
467		if($this->xAxisZero) {
468			$this->xAxis->setYCenter($this->yAxis, 0);
469		}
470
471		// Center Y axis on zero
472		if($this->yAxisZero) {
473			$this->yAxis->setXCenter($this->xAxis, 0);
474		}
475
476		// Set axis labels
477		$labels = array();
478		for($i = 0, $count = $this->getXAxisNumber(); $i < $count; $i++) {
479			$labels[] = $i;
480		}
481		$this->xAxis->label->set($labels);
482
483		parent::init($drawer);
484
485		list($x1, $y1, $x2, $y2) = $this->getPosition();
486
487		list($leftSpace, $rightSpace) = $this->getSpace($x2 - $x1, $y2 - $y1);
488
489		// Create the grid
490		$this->createGrid();
491
492		// Draw the grid
493		$this->grid->setSpace($leftSpace, $rightSpace, 0, 0);
494		$this->grid->draw($drawer, $x1, $y1, $x2, $y2);
495
496	}
497
498	 function drawEnvelope($drawer) {
499
500		list($x1, $y1, $x2, $y2) = $this->getPosition();
501
502		if($this->getXCenter()) {
503			$size = $this->xAxis->getDistance(0, 1);
504			$this->xAxis->label->move($size / 2, 0);
505			$this->xAxis->label->hideLast(TRUE);
506		}
507
508		// Draw top axis
509		if($this->xAxisPosition === PLOT_TOP or $this->xAxisPosition === PLOT_BOTH) {
510			$top = $this->xAxis;
511			if($this->xAxisZero === FALSE) {
512				$top->line->setY($y1, $y1);
513			}
514			$top->label->setAlign(NULL, LABEL_TOP);
515			$top->label->move(0, -3);
516			$top->title->move(0, -25);
517			$top->draw($drawer);
518		}
519
520		// Draw bottom axis
521		if($this->xAxisPosition === PLOT_BOTTOM or $this->xAxisPosition === PLOT_BOTH) {
522			$bottom = $this->xAxis;
523			if($this->xAxisZero === FALSE) {
524				$bottom->line->setY($y2, $y2);
525			}
526			$bottom->label->setAlign(NULL, LABEL_BOTTOM);
527			$bottom->label->move(0, 3);
528			$bottom->reverseTickStyle();
529			$bottom->title->move(0, 25);
530			$bottom->draw($drawer);
531		}
532
533		// Draw left axis
534		if($this->yAxisPosition === PLOT_LEFT or $this->yAxisPosition === PLOT_BOTH) {
535			$left = $this->yAxis;
536			if($this->yAxisZero === FALSE) {
537				$left->line->setX($x1, $x1);
538			}
539			$left->label->setAlign(LABEL_RIGHT);
540			$left->label->move(-6, 0);
541			$left->title->move(-25, 0);
542			$left->draw($drawer);
543		}
544
545		// Draw right axis
546		if($this->yAxisPosition === PLOT_RIGHT or $this->yAxisPosition === PLOT_BOTH) {
547			$right = $this->yAxis;
548			if($this->yAxisZero === FALSE) {
549				$right->line->setX($x2, $x2);
550			}
551			$right->label->setAlign(LABEL_LEFT);
552			$right->label->move(6, 0);
553			$right->reverseTickStyle();
554			$right->title->move(25, 0);
555			$right->draw($drawer);
556		}
557
558	}
559
560	 function createGrid() {
561
562		$max = $this->getRealYMax();
563		$min = $this->getRealYMin();
564
565		$number = $this->yAxis->getLabelNumber() - 1;
566
567		if($number < 1) {
568			return;
569		}
570
571		// Horizontal lines of the grid
572
573		$h = array();
574		for($i = 0; $i <= $number; $i++) {
575			$h[] = $i / $number;
576		}
577
578		// Vertical lines
579
580		$major = $this->yAxis->tick('major');
581		$interval = $major->getInterval();
582		$number = $this->getXAxisNumber() - 1;
583
584		$w = array();
585
586		if($number > 0) {
587
588			for($i = 0; $i <= $number; $i++) {
589				if($i%$interval === 0) {
590					$w[] = $i / $number;
591				}
592			}
593
594		}
595
596		$this->grid->setGrid($w, $h);
597
598	}
599
600	/**
601	 * Change values of Y axis
602	 * This method ignores not numeric values
603	 *
604	 * @param array $datay
605	 * @param array $datax
606	 */
607	 function setValues($datay, $datax = NULL) {
608
609		$this->checkArray($datay);
610
611		foreach($datay as $key => $value) {
612			unset($datay[$key]);
613			$datay[(int)$key] = $value;
614		}
615
616		if($datax === NULL) {
617			$datax = array();
618			for($i = 0; $i < count($datay); $i++) {
619				$datax[] = $i;
620			}
621		} else {
622			foreach($datax as $key => $value) {
623				unset($datax[$key]);
624				$datax[(int)$key] = $value;
625			}
626		}
627
628		$this->checkArray($datax);
629
630		if(count($datay) === count($datax)) {
631
632			// Set values
633			$this->datay = $datay;
634			$this->datax = $datax;
635			// Update axis with the new awvalues
636			$this->updateAxis();
637		} else {
638			trigger_error("Plots must have the same number of X and Y points", E_USER_ERROR);
639		}
640
641	}
642
643	/**
644	 * Return begin and end values
645	 *
646	 * @return array
647	 */
648	 function getLimit() {
649
650		$i = 0;
651		while(array_key_exists($i, $this->datay) and $this->datay[$i] === NULL) {
652			$i++;
653		}
654		$start = $i;
655		$i = count($this->datay) - 1;
656		while(array_key_exists($i, $this->datay) and $this->datay[$i] === NULL) {
657			$i--;
658		}
659		$stop = $i;
660
661		return array($start, $stop);
662
663	}
664
665	/**
666	 * Return TRUE if labels must be centered on X axis, FALSE otherwise
667	 *
668	 * @return bool
669	 */
670
671
672	 function updateAxis() {
673
674		$this->xAxis->setRange(
675			$this->getXMin(),
676			$this->getXMax()
677		);
678		$this->yAxis->setRange(
679			$this->getRealYMin(),
680			$this->getRealYMax()
681		);
682
683	}
684
685	 function checkArray(&$array) {
686
687		if(is_array($array) === FALSE) {
688			trigger_error("You tried to set a value that is not an array", E_USER_ERROR);
689		}
690
691		foreach($array as $key => $value) {
692			if(is_numeric($value) === FALSE and is_null($value) === FALSE) {
693				trigger_error("Expected numeric values for the plot", E_USER_ERROR);
694			}
695		}
696
697		if(count($array) < 1) {
698			trigger_error("Your plot must have at least 1 value", E_USER_ERROR);
699		}
700
701	}
702
703}
704
705registerClass('Plot', TRUE);
706
707class awPlotAxis {
708
709	/**
710	 * Left axis
711	 *
712	 * @var Axis
713	 */
714	var $left;
715
716	/**
717	 * Right axis
718	 *
719	 * @var Axis
720	 */
721	var $right;
722
723	/**
724	 * Top axis
725	 *
726	 * @var Axis
727	 */
728	var $top;
729
730	/**
731	 * Bottom axis
732	 *
733	 * @var Axis
734	 */
735	var $bottom;
736
737	/**
738	 * Build the group of axis
739	 */
740	 function awPlotAxis() {
741
742		$this->left = new awAxis;
743		$this->left->auto(TRUE);
744		$this->left->label->setAlign(LABEL_RIGHT);
745		$this->left->label->move(-6, 0);
746		$this->yAxis($this->left);
747		$this->left->setTickStyle(TICK_OUT);
748		$this->left->title->move(-25, 0);
749
750		$this->right = new awAxis;
751		$this->right->auto(TRUE);
752		$this->right->label->setAlign(LABEL_LEFT);
753		$this->right->label->move(6, 0);
754		$this->yAxis($this->right);
755		$this->right->setTickStyle(TICK_IN);
756		$this->right->title->move(25, 0);
757
758		$this->top = new awAxis;
759		$this->top->label->setAlign(NULL, LABEL_TOP);
760		$this->top->label->move(0, -3);
761		$this->xAxis($this->top);
762		$this->top->setTickStyle(TICK_OUT);
763		$this->top->title->move(0, -25);
764
765		$this->bottom = new awAxis;
766		$this->bottom->label->setAlign(NULL, LABEL_BOTTOM);
767		$this->bottom->label->move(0, 3);
768		$this->xAxis($this->bottom);
769		$this->bottom->setTickStyle(TICK_IN);
770		$this->bottom->title->move(0, 25);
771
772	}
773
774	 function xAxis(&$axis) {
775
776		$axis->addTick('major', new awTick(0, 5));
777		$axis->addTick('minor', new awTick(0, 3));
778		$axis->label->setFont(new awDejaVuSans(7));
779
780	}
781
782	 function yAxis(&$axis) {
783
784		$axis->addTick('major', new awTick(0, 5));
785		$axis->addTick('minor', new awTick(0, 3));
786		$axis->setNumberByTick('minor', 'major', 3);
787		$axis->label->setFont(new awDejaVuSans(7));
788		$axis->title->setAngle(90);
789
790	}
791
792}
793
794registerClass('PlotAxis');
795
796/**
797 * A graph with axis can contain some groups of components
798 *
799 * @package Artichow
800 */
801class awPlotGroup extends awComponentGroup {
802
803	/**
804	 * Grid properties
805	 *
806	 * @var Grid
807	 */
808	var $grid;
809
810	/**
811	 * Left, right, top and bottom axis
812	 *
813	 * @var PlotAxis
814	 */
815	var $axis;
816
817	/**
818	 * Set the X axis on zero
819	 *
820	 * @var bool
821	 */
822	var $xAxisZero = TRUE;
823
824	/**
825	 * Set the Y axis on zero
826	 *
827	 * @var bool
828	 */
829	var $yAxisZero = FALSE;
830
831	/**
832	 * Real axis used for Y axis
833	 *
834	 * @var string
835	 */
836	var $yRealAxis = PLOT_LEFT;
837
838	/**
839	 * Real axis used for X axis
840	 *
841	 * @var string
842	 */
843	var $xRealAxis = PLOT_BOTTOM;
844
845	/**
846	 * Change min value for Y axis
847	 *
848	 * @var mixed
849	 */
850	var $yMin = NULL;
851
852	/**
853	 * Change max value for Y axis
854	 *
855	 * @var mixed
856	 */
857	var $yMax = NULL;
858
859	/**
860	 * Change min value for X axis
861	 *
862	 * @var mixed
863	 */
864	var $xMin = NULL;
865
866	/**
867	 * Change max value for X axis
868	 *
869	 * @var mixed
870	 */
871	var $xMax = NULL;
872
873	/**
874	 * Build the PlotGroup
875	 *
876	 */
877	 function awPlotGroup() {
878
879		parent::awComponentGroup();
880
881		$this->grid = new awGrid;
882		$this->grid->setBackgroundColor(new awWhite);
883
884		$this->axis = new awPlotAxis;
885
886	}
887
888	/**
889	 * Set the X axis on zero or not
890	 *
891	 * @param bool $zero
892	 */
893	 function setXAxisZero($zero) {
894		$this->xAxisZero = (bool)$zero;
895	}
896
897	/**
898	 * Set the Y axis on zero or not
899	 *
900	 * @param bool $zero
901	 */
902	 function setYAxisZero($zero) {
903		$this->yAxisZero = (bool)$zero;
904	}
905
906	/**
907	 * Change min value for Y axis
908	 * Set NULL for auto selection.
909	 *
910	 * @param float $value
911	 */
912	 function setYMin($value) {
913		$this->axis->left->auto(FALSE);
914		$this->axis->right->auto(FALSE);
915		$this->yMin = $value;
916	}
917
918	/**
919	 * Change max value for Y axis
920	 * Set NULL for auto selection.
921	 *
922	 * @param float $value
923	 */
924	 function setYMax($value) {
925		$this->axis->left->auto(FALSE);
926		$this->axis->right->auto(FALSE);
927		$this->yMax = $value;
928	}
929
930	/**
931	 * Change min value for X axis
932	 * Set NULL for auto selection.
933	 *
934	 * @param float $value
935	 */
936	 function setXMin($value) {
937		$this->xMin = $value;
938	}
939
940	/**
941	 * Change max value for X axis
942	 * Set NULL for auto selection.
943	 *
944	 * @param float $value
945	 */
946	 function setXMax($value) {
947		$this->xMax = $value;
948	}
949
950	/**
951	 * Get min value for X axis
952	 *
953	 * @return float $value
954	 */
955	 function getXMin() {
956
957		return $this->getX('min');
958
959	}
960
961	/**
962	 * Get max value for X axis
963	 *
964	 * @return float $value
965	 */
966	 function getXMax() {
967
968		return $this->getX('max');
969
970	}
971
972	 function getX($type) {
973
974		switch($type) {
975			case 'max' :
976				if($this->xMax !== NULL) {
977					return $this->xMax;
978				}
979				break;
980			case 'min' :
981				if($this->xMin !== NULL) {
982					return $this->xMin;
983				}
984				break;
985		}
986
987		$value = NULL;
988		$get = 'getX'.ucfirst($type);
989
990		for($i = 0; $i < count($this->components); $i++) {
991
992			$component = $this->components[$i];
993
994			if($value === NULL) {
995				$value = $component->$get();
996			} else {
997				$value = $type($value, $component->$get());
998			}
999
1000		}
1001
1002		return $value;
1003
1004	}
1005
1006	/**
1007	 * Get min value with spaces for Y axis
1008	 *
1009	 * @param string $axis Axis name
1010	 * @return float $value
1011	 */
1012	 function getRealYMin($axis = NULL) {
1013
1014		if($axis === NULL) {
1015			return NULL;
1016		}
1017
1018		$min = $this->getRealY('min', $axis);
1019		$max = $this->getRealY('max', $axis);
1020
1021		if($this->space->bottom !== NULL) {
1022			$interval = ($min - $max) * $this->space->bottom / 100;
1023			return $min + $interval;
1024		} else {
1025			return $min;
1026		}
1027
1028	}
1029
1030	/**
1031	 * Get max value with spaces for Y axis
1032	 *
1033	 * @param string $axis Axis name
1034	 * @return float $value
1035	 */
1036	 function getRealYMax($axis = NULL) {
1037
1038		if($axis === NULL) {
1039			return NULL;
1040		}
1041
1042		$min = $this->getRealY('min', $axis);
1043		$max = $this->getRealY('max', $axis);
1044
1045		if($this->space->top !== NULL) {
1046			$interval = ($max - $min) * $this->space->top / 100;
1047			return $max + $interval;
1048		} else {
1049			return $max;
1050		}
1051
1052	}
1053
1054	 function getRealY($type, $axis) {
1055
1056		switch($type) {
1057			case 'max' :
1058				if($this->yMax !== NULL) {
1059					return $this->yMax;
1060				}
1061				break;
1062			case 'min' :
1063				if($this->yMin !== NULL) {
1064					return $this->yMin;
1065				}
1066				break;
1067		}
1068
1069		$value = NULL;
1070		$get = 'getY'.ucfirst($type);
1071
1072		for($i = 0; $i < count($this->components); $i++) {
1073
1074			$component = $this->components[$i];
1075
1076			switch($axis) {
1077
1078				case PLOT_LEFT :
1079				case PLOT_RIGHT :
1080					$test = ($component->getYAxis() === $axis);
1081					break;
1082				default :
1083					$test = FALSE;
1084
1085			}
1086
1087			if($test) {
1088				if($value === NULL) {
1089					$value = $component->$get();
1090				} else {
1091					$value = $type($value, $component->$get());
1092				}
1093			}
1094
1095		}
1096
1097		return $value;
1098
1099	}
1100
1101	 function init($drawer) {
1102
1103		list($x1, $y1, $x2, $y2) = $this->getPosition();
1104
1105		// Get PlotGroup space
1106		list($leftSpace, $rightSpace, $topSpace, $bottomSpace) = $this->getSpace($x2 - $x1, $y2 - $y1);
1107
1108		// Count values in the group
1109		$values = $this->getXAxisNumber();
1110
1111		// Init the PlotGroup
1112		$this->axis->top->line->setX($x1, $x2);
1113		$this->axis->bottom->line->setX($x1, $x2);
1114		$this->axis->left->line->setY($y2, $y1);
1115		$this->axis->right->line->setY($y2, $y1);
1116
1117		$this->axis->top->setPadding($leftSpace, $rightSpace);
1118		$this->axis->bottom->setPadding($leftSpace, $rightSpace);
1119
1120		$xMin = $this->getXMin();
1121		$xMax = $this->getXMax();
1122
1123		$this->axis->top->setRange($xMin, $xMax);
1124		$this->axis->bottom->setRange($xMin, $xMax);
1125
1126		for($i = 0; $i < count($this->components); $i++) {
1127
1128
1129			$component = &$this->components[$i];
1130
1131			$component->auto($this->auto);
1132
1133			// Copy space to the component
1134
1135			$component->setSpace($this->space->left, $this->space->right, $this->space->top, $this->space->bottom);
1136
1137			$component->xAxis->setPadding($leftSpace, $rightSpace);
1138			$component->xAxis->line->setX($x1, $x2);
1139
1140			$component->yAxis->line->setY($y2, $y1);
1141
1142		}
1143
1144		// Set Y axis range
1145		foreach(array('left', 'right') as $axis) {
1146
1147			if($this->isAxisUsed($axis)) {
1148
1149				$min = $this->getRealYMin($axis);
1150				$max = $this->getRealYMax($axis);
1151
1152				$interval = $max - $min;
1153
1154				$this->axis->{$axis}->setRange(
1155					$min - $interval * $this->space->bottom / 100,
1156					$max + $interval * $this->space->top / 100
1157				);
1158
1159				// Auto-scaling mode
1160				$this->axis->{$axis}->autoScale();
1161
1162			}
1163
1164		}
1165
1166		if($this->axis->left->getLabelNumber() === NULL) {
1167			$number = round(($y2 - $y1) / 75) + 2;
1168			$this->axis->left->setLabelNumber($number);
1169		}
1170
1171		if($this->axis->right->getLabelNumber() === NULL) {
1172			$number = round(($y2 - $y1) / 75) + 2;
1173			$this->axis->right->setLabelNumber($number);
1174		}
1175
1176		// Center labels on X axis if needed
1177		$test = array(PLOT_TOP => FALSE, PLOT_BOTTOM => FALSE);
1178
1179		for($i = 0; $i < count($this->components); $i++) {
1180
1181
1182			$component = &$this->components[$i];
1183
1184
1185			if($component->getValues() !== NULL) {
1186
1187				$axis = $component->getXAxis();
1188
1189				if($test[$axis] === FALSE) {
1190
1191					// Center labels for bar plots
1192					if($component->getXCenter()) {
1193						$size = $this->axis->{$axis}->getDistance(0, 1);
1194						$this->axis->{$axis}->label->move($size / 2, 0);
1195						$this->axis->{$axis}->label->hideLast(TRUE);
1196						$test[$axis] = TRUE;
1197					}
1198
1199				}
1200
1201			}
1202
1203
1204		}
1205
1206		// Set axis labels
1207		$labels = array();
1208		for($i = $xMin; $i <= $xMax; $i++) {
1209			$labels[] = $i;
1210		}
1211		if($this->axis->top->label->count() === 0) {
1212			$this->axis->top->label->set($labels);
1213		}
1214		if($this->axis->bottom->label->count() === 0) {
1215			$this->axis->bottom->label->set($labels);
1216		}
1217
1218		// Set ticks
1219
1220		$this->axis->top->ticks['major']->setNumber($values);
1221		$this->axis->bottom->ticks['major']->setNumber($values);
1222		$this->axis->left->ticks['major']->setNumber($this->axis->left->getLabelNumber());
1223		$this->axis->right->ticks['major']->setNumber($this->axis->right->getLabelNumber());
1224
1225
1226		// Set X axis on zero
1227		if($this->xAxisZero) {
1228			$axis = $this->selectYAxis();
1229			$this->axis->bottom->setYCenter($axis, 0);
1230			$this->axis->top->setYCenter($axis, 0);
1231		}
1232
1233		// Set Y axis on zero
1234		if($this->yAxisZero) {
1235			$axis = $this->selectXAxis();
1236			$this->axis->left->setXCenter($axis, 1);
1237			$this->axis->right->setXCenter($axis, 1);
1238		}
1239
1240		parent::init($drawer);
1241
1242		list($leftSpace, $rightSpace, $topSpace, $bottomSpace) = $this->getSpace($x2 - $x1, $y2 - $y1);
1243
1244		// Create the grid
1245		$this->createGrid();
1246
1247		// Draw the grid
1248		$this->grid->setSpace($leftSpace, $rightSpace, 0, 0);
1249		$this->grid->draw($drawer, $x1, $y1, $x2, $y2);
1250
1251	}
1252
1253	 function drawComponent($drawer, $x1, $y1, $x2, $y2, $aliasing) {
1254
1255		$xMin = $this->getXMin();
1256		$xMax = $this->getXMax();
1257
1258		$maxLeft = $this->getRealYMax(PLOT_LEFT);
1259		$maxRight = $this->getRealYMax(PLOT_RIGHT);
1260
1261		$minLeft = $this->getRealYMin(PLOT_LEFT);
1262		$minRight = $this->getRealYMin(PLOT_RIGHT);
1263
1264		foreach($this->components as $component) {
1265
1266			$min = $component->getYMin();
1267			$max = $component->getYMax();
1268
1269			// Set component minimum and maximum
1270			if($component->getYAxis() === PLOT_LEFT) {
1271
1272				list($min, $max) = $this->axis->left->getRange();
1273
1274				$component->setYMin($min);
1275				$component->setYMax($max);
1276
1277			} else {
1278
1279				list($min, $max) = $this->axis->right->getRange();
1280
1281				$component->setYMin($min);
1282				$component->setYMax($max);
1283
1284			}
1285
1286			$component->setXAxisZero($this->xAxisZero);
1287			$component->setYAxisZero($this->yAxisZero);
1288
1289			$component->xAxis->setRange($xMin, $xMax);
1290
1291			$component->drawComponent(
1292				$drawer,
1293				$x1, $y1,
1294				$x2, $y2,
1295				$aliasing
1296			);
1297
1298			$component->setYMin($min);
1299			$component->setYMax($max);
1300
1301		}
1302
1303	}
1304
1305	 function drawEnvelope($drawer) {
1306
1307		list($x1, $y1, $x2, $y2) = $this->getPosition();
1308
1309		// Hide unused axis
1310		foreach(array(PLOT_LEFT, PLOT_RIGHT, PLOT_TOP, PLOT_BOTTOM) as $axis) {
1311			if($this->isAxisUsed($axis) === FALSE) {
1312				$this->axis->{$axis}->hide(TRUE);
1313			}
1314		}
1315
1316		// Draw top axis
1317		$top = $this->axis->top;
1318		if($this->xAxisZero === FALSE) {
1319			$top->line->setY($y1, $y1);
1320		}
1321		$top->draw($drawer);
1322
1323		// Draw bottom axis
1324		$bottom = $this->axis->bottom;
1325		if($this->xAxisZero === FALSE) {
1326			$bottom->line->setY($y2, $y2);
1327		}
1328		$bottom->draw($drawer);
1329
1330		// Draw left axis
1331		$left = $this->axis->left;
1332		if($this->yAxisZero === FALSE) {
1333			$left->line->setX($x1, $x1);
1334		}
1335		$left->draw($drawer);
1336
1337		// Draw right axis
1338		$right = $this->axis->right;
1339		if($this->yAxisZero === FALSE) {
1340			$right->line->setX($x2, $x2);
1341		}
1342		$right->draw($drawer);
1343
1344	}
1345
1346	/**
1347	 * Is the specified axis used ?
1348	 *
1349	 * @param string $axis Axis name
1350	 * @return bool
1351	 */
1352	 function isAxisUsed($axis) {
1353
1354		for($i = 0; $i < count($this->components); $i++) {
1355
1356			$component = $this->components[$i];
1357
1358			switch($axis) {
1359
1360				case PLOT_LEFT :
1361				case PLOT_RIGHT :
1362					if($component->getYAxis() === $axis) {
1363						return TRUE;
1364					}
1365					break;
1366
1367				case PLOT_TOP :
1368				case PLOT_BOTTOM :
1369					if($component->getXAxis() === $axis) {
1370						return TRUE;
1371					}
1372					break;
1373
1374			}
1375
1376		}
1377
1378		return FALSE;
1379
1380	}
1381
1382	 function createGrid() {
1383
1384		$max = $this->getRealYMax(PLOT_LEFT);
1385		$min = $this->getRealYMin(PLOT_RIGHT);
1386
1387		// Select axis (left if possible, right otherwise)
1388		$axis = $this->selectYAxis();
1389
1390		$number = $axis->getLabelNumber() - 1;
1391
1392		if($number < 1) {
1393			return;
1394		}
1395
1396		// Horizontal lines of grid
1397
1398		$h = array();
1399		for($i = 0; $i <= $number; $i++) {
1400			$h[] = $i / $number;
1401		}
1402
1403		// Vertical lines
1404
1405		$major = $axis->tick('major');
1406		$interval = $major->getInterval();
1407		$number = $this->getXAxisNumber() - 1;
1408
1409		$w = array();
1410
1411		if($number > 0) {
1412
1413			for($i = 0; $i <= $number; $i++) {
1414				if($i%$interval === 0) {
1415					$w[] = $i / $number;
1416				}
1417			}
1418
1419		}
1420
1421		$this->grid->setGrid($w, $h);
1422
1423	}
1424
1425	 function selectYAxis(){
1426
1427		// Select axis (left if possible, right otherwise)
1428		if($this->isAxisUsed(PLOT_LEFT)) {
1429			$axis = $this->axis->left;
1430		} else {
1431			$axis = $this->axis->right;
1432		}
1433
1434		return $axis;
1435
1436	}
1437
1438	 function selectXAxis(){
1439
1440		// Select axis (bottom if possible, top otherwise)
1441		if($this->isAxisUsed(PLOT_BOTTOM)) {
1442			$axis = $this->axis->bottom;
1443		} else {
1444			$axis = $this->axis->top;
1445		}
1446
1447		return $axis;
1448
1449	}
1450
1451	 function getXAxisNumber() {
1452		$offset = $this->components[0];
1453		$max = $offset->getXAxisNumber();
1454		for($i = 1; $i < count($this->components); $i++) {
1455			$offset = $this->components[$i];
1456			$max = max($max, $offset->getXAxisNumber());
1457		}
1458		return $max;
1459	}
1460
1461}
1462
1463registerClass('PlotGroup');
1464?>