1<?php
2/**
3 *    pChart - a PHP class to build charts!
4 *    Copyright (C) 2008 Jean-Damien POGOLOTTI
5 *    Version 2.0
6 *
7 *    http://pchart.sourceforge.net
8 *
9 *    This program is free software: you can redistribute it and/or modify
10 *    it under the terms of the GNU General Public License as published by
11 *    the Free Software Foundation, either version 1,2,3 of the License, or
12 *    (at your option) any later version.
13 *
14 *    This program is distributed in the hope that it will be useful,
15 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
16 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 *    GNU General Public License for more details.
18 *
19 *    You should have received a copy of the GNU General Public License
20 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 */
22
23require_once(dirname(__FILE__).'/ConversionHelpers.php');
24require_once(dirname(__FILE__).'/ShadowProperties.php');
25require_once(dirname(__FILE__).'/Color.php');
26require_once(dirname(__FILE__).'/Palette.php');
27require_once(dirname(__FILE__).'/ICanvas.php');
28require_once(dirname(__FILE__).'/GridStyle.php');
29require_once(dirname(__FILE__).'/ScaleStyle.php');
30
31/* Declare some script wide constants */
32define ( "SCALE_NORMAL", 1 );
33define ( "SCALE_ADDALL", 2 );
34define ( "SCALE_START0", 3 );
35define ( "SCALE_ADDALLSTART0", 4 );
36define ( "TARGET_GRAPHAREA", 1 );
37define ( "TARGET_BACKGROUND", 2 );
38define ( "ALIGN_TOP_LEFT", 1 );
39define ( "ALIGN_TOP_CENTER", 2 );
40define ( "ALIGN_TOP_RIGHT", 3 );
41define ( "ALIGN_LEFT", 4 );
42define ( "ALIGN_CENTER", 5 );
43define ( "ALIGN_RIGHT", 6 );
44define ( "ALIGN_BOTTOM_LEFT", 7 );
45define ( "ALIGN_BOTTOM_CENTER", 8 );
46define ( "ALIGN_BOTTOM_RIGHT", 9 );
47
48/**
49 * pChart class definition
50 */
51class pChart {
52	protected $palette;
53
54	/* Some static vars used in the class */
55	protected $XSize = NULL;
56	protected $YSize = NULL;
57	protected $Picture = NULL;
58	protected $ImageMap = NULL;
59
60	/* Error management */
61	protected $ErrorReporting = FALSE;
62	protected $ErrorInterface = "CLI";
63	protected $Errors = NULL;
64	protected $ErrorFontName = "Fonts/pf_arma_five.ttf";
65	protected $ErrorFontSize = 6;
66
67	/* vars related to the graphing area */
68	protected $GArea_X1 = NULL;
69	protected $GArea_Y1 = NULL;
70	protected $GArea_X2 = NULL;
71	protected $GArea_Y2 = NULL;
72	protected $GAreaXOffset = NULL;
73	protected $VMax = NULL;
74	protected $VMin = NULL;
75	protected $VXMax = NULL;
76	protected $VXMin = NULL;
77	protected $Divisions = NULL;
78	protected $XDivisions = NULL;
79	protected $DivisionHeight = NULL;
80	protected $XDivisionHeight = NULL;
81	protected $DivisionCount = NULL;
82	protected $XDivisionCount = NULL;
83	protected $DivisionRatio = NULL;
84	protected $XDivisionRatio = NULL;
85	protected $DivisionWidth = NULL;
86	protected $DataCount = NULL;
87
88	/* Text format related vars */
89	protected $FontName = NULL;
90	protected $FontSize = NULL;
91	protected $DateFormat = "d/m/Y";
92
93	/* Lines format related vars */
94	protected $LineWidth = 1;
95	protected $LineDotSize = 0;
96
97	/* Shadow settings */
98	private $shadowProperties;
99
100	/* Image Map settings */
101	protected $BuildMap = FALSE;
102	protected $MapFunction = NULL;
103	protected $tmpFolder = "tmp/";
104	protected $MapID = NULL;
105
106	/**
107	 * @brief An abstract ICanvas onto which we draw the chart
108	 *
109	 * @todo This probably shouldn't be protected, I'm still working
110	 * on how the modules are going to break down between the various
111	 * chart types.
112	 */
113	protected $canvas = null;
114
115	/**
116	 * This function create the background picture
117	 */
118	function __construct($XSize, $YSize, ICanvas $canvas) {
119		$this->palette = Palette::defaultPalette();
120
121		$this->XSize = $XSize;
122		$this->YSize = $YSize;
123
124		$this->setFontProperties ( "tahoma.ttf", 8 );
125
126		$this->shadowProperties = ShadowProperties::FromDefaults();
127
128		$this->canvas = $canvas;
129	}
130
131	/**
132	 * Set if warnings should be reported
133	 */
134	function reportWarnings($Interface = "CLI") {
135		$this->ErrorReporting = TRUE;
136		$this->ErrorInterface = $Interface;
137	}
138
139	/**
140	 * Set the font properties
141	 */
142	function setFontProperties($FontName, $FontSize) {
143		$this->FontName = $FontName;
144		$this->FontSize = $FontSize;
145	}
146
147	public function setPalette(Palette $newPalette) {
148		$this->palette = $newPalette;
149	}
150
151	/**
152	 * Set the shadow properties
153	 */
154	function setShadowProperties($XDistance = 1, $YDistance = 1, Color $color = null, $Alpha = 50, $Blur = 0) {
155		if ($color == null) {
156			$color = new Color(60, 60, 60);
157		}
158
159		$this->shadowProperties = ShadowProperties::FromSettings($XDistance,
160																 $YDistance,
161																 $color,
162																 $Alpha,
163																 $Blur);
164	}
165
166	/**
167	 * Remove shadow option
168	 */
169	function clearShadow() {
170		$this->shadowProperties = ShadowProperties::FromDefaults();
171	}
172
173	/**
174	 * Load Color Palette from file
175	 */
176	function loadColorPalette($FileName, $Delimiter = ",") {
177		$handle = @fopen ( $FileName, "r" );
178
179		if ($handle == null) {
180			throw new Exception("Failed to open file in loadColorPalette");
181		}
182
183		$ColorID = 0;
184		if ($handle) {
185			while ( ! feof ( $handle ) ) {
186				$buffer = fgets ( $handle, 4096 );
187				$buffer = str_replace ( chr ( 10 ), "", $buffer );
188				$buffer = str_replace ( chr ( 13 ), "", $buffer );
189				$Values = explode ( $Delimiter, $buffer );
190				if (count ( $Values ) == 3) {
191					$this->palette->colors[$ColorID] = new Color($Values[0],
192																 $Values[1],
193																 $Values[2]);
194					$ColorID ++;
195				}
196			}
197		}
198	}
199
200	/**
201	 * Set line style
202	 */
203	function setLineStyle($Width = 1, $DotSize = 0) {
204		$this->LineWidth = $Width;
205		$this->LineDotSize = $DotSize;
206	}
207
208	/**
209	 * Set the graph area location
210	 */
211	function setGraphArea($X1, $Y1, $X2, $Y2) {
212		$this->GArea_X1 = $X1;
213		$this->GArea_Y1 = $Y1;
214		$this->GArea_X2 = $X2;
215		$this->GArea_Y2 = $Y2;
216	}
217
218	/**
219	 * Prepare the graph area
220	 */
221	private function drawGraphArea(BackgroundStyle $style) {
222		$this->canvas->drawFilledRectangle(new Point($this->GArea_X1, $this->GArea_Y1),
223										   new Point($this->GArea_X2, $this->GArea_Y2),
224										   $style->getBackgroundColor(),
225										   $this->shadowProperties, FALSE );
226		$this->canvas->drawRectangle(new Point($this->GArea_X1, $this->GArea_Y1),
227									 new Point($this->GArea_X2, $this->GArea_Y2),
228									 $style->getBackgroundColor()->addRGBIncrement(-40),
229									 $style->getBorderWidth(),
230									 $style->getBorderDotSize(),
231									 $this->shadowProperties);
232
233		if ($style->useStripe()) {
234			$color2 = $style->getBackgroundColor()->addRGBIncrement(-15);
235
236			$SkewWidth = $this->GArea_Y2 - $this->GArea_Y1 - 1;
237
238			for($i = $this->GArea_X1 - $SkewWidth; $i <= $this->GArea_X2; $i = $i + 4) {
239				$X1 = $i;
240				$Y1 = $this->GArea_Y2;
241				$X2 = $i + $SkewWidth;
242				$Y2 = $this->GArea_Y1;
243
244				if ($X1 < $this->GArea_X1) {
245					$X1 = $this->GArea_X1;
246					$Y1 = $this->GArea_Y1 + $X2 - $this->GArea_X1 + 1;
247				}
248
249				if ($X2 >= $this->GArea_X2) {
250					$Y2 = $this->GArea_Y1 + $X2 - $this->GArea_X2 + 1;
251					$X2 = $this->GArea_X2 - 1;
252				}
253
254				$this->canvas->drawLine(new Point($X1, $Y1),
255										new Point($X2, $Y2 + 1),
256										$color2,
257										1,
258										0,
259										ShadowProperties::NoShadow());
260			}
261		}
262	}
263
264	public function drawGraphBackground(BackgroundStyle $style) {
265		$this->drawGraphArea($style);
266		$this->drawGraphAreaGradient($style);
267	}
268
269	/**
270	 * Allow you to clear the scale : used if drawing multiple charts
271	 */
272	function clearScale() {
273		$this->VMin = NULL;
274		$this->VMax = NULL;
275		$this->VXMin = NULL;
276		$this->VXMax = NULL;
277		$this->Divisions = NULL;
278		$this->XDivisions = NULL;
279	}
280
281	/**
282	 * Allow you to fix the scale, use this to bypass the automatic scaling
283	 */
284	function setFixedScale($VMin, $VMax, $Divisions = 5, $VXMin = 0, $VXMax = 0, $XDivisions = 5) {
285		$this->VMin = $VMin;
286		$this->VMax = $VMax;
287		$this->Divisions = $Divisions;
288
289		if (! $VXMin == 0) {
290			$this->VXMin = $VXMin;
291			$this->VXMax = $VXMax;
292			$this->XDivisions = $XDivisions;
293		}
294	}
295
296	/**
297	 * Wrapper to the drawScale() function allowing a second scale to
298	 * be drawn
299	 */
300	function drawRightScale(pData $data, ScaleStyle $style, $Angle = 0, $Decimals = 1, $WithMargin = FALSE, $SkipLabels = 1) {
301		$this->drawScale($data, $style, $Angle, $Decimals, $WithMargin, $SkipLabels, TRUE );
302	}
303
304	/**
305	 * Compute and draw the scale
306	 */
307	function drawScale(pData $Data, ScaleStyle $style, $Angle = 0, $Decimals = 1, $WithMargin = FALSE, $SkipLabels = 1, $RightScale = FALSE) {
308		/* Validate the Data and DataDescription array */
309		$this->validateData ( "drawScale", $Data->getData() );
310
311		$this->canvas->drawLine(new Point($this->GArea_X1, $this->GArea_Y1),
312								new Point($this->GArea_X1, $this->GArea_Y2),
313								$style->getColor(),
314								$style->getLineWidth(),
315								$style->getLineDotSize(),
316								$this->shadowProperties);
317		$this->canvas->drawLine(new Point($this->GArea_X1, $this->GArea_Y2),
318								new Point($this->GArea_X2, $this->GArea_Y2),
319								$style->getColor(),
320								$style->getLineWidth(),
321								$style->getLineDotSize(),
322								$this->shadowProperties);
323
324		if ($this->VMin == NULL && $this->VMax == NULL) {
325			$Divisions = $this->calculateDivisions($Data, $style);
326		} else
327			$Divisions = $this->Divisions;
328
329		$this->DivisionCount = $Divisions;
330
331		$DataRange = $this->VMax - $this->VMin;
332		if ($DataRange == 0) {
333			$DataRange = .1;
334		}
335
336		$this->DivisionHeight = ($this->GArea_Y2 - $this->GArea_Y1) / $Divisions;
337		$this->DivisionRatio = ($this->GArea_Y2 - $this->GArea_Y1) / $DataRange;
338
339		$this->GAreaXOffset = 0;
340		if (count ( $Data->getData() ) > 1) {
341			if ($WithMargin == FALSE)
342				$this->DivisionWidth = ($this->GArea_X2 - $this->GArea_X1) / (count ( $Data->getData() ) - 1);
343			else {
344				$this->DivisionWidth = ($this->GArea_X2 - $this->GArea_X1) / (count ( $Data->getData() ));
345				$this->GAreaXOffset = $this->DivisionWidth / 2;
346			}
347		} else {
348			$this->DivisionWidth = $this->GArea_X2 - $this->GArea_X1;
349			$this->GAreaXOffset = $this->DivisionWidth / 2;
350		}
351
352		$this->DataCount = count ( $Data->getData() );
353
354		if ($style->getDrawTicks() == FALSE)
355			return (0);
356
357		$YPos = $this->GArea_Y2;
358		$XMin = NULL;
359		for($i = 1; $i <= $Divisions + 1; $i ++) {
360			if ($RightScale)
361				$this->canvas->drawLine(new Point($this->GArea_X2, $YPos),
362										new Point($this->GArea_X2 + 5, $YPos),
363										$style->getColor(),
364										$style->getLineWidth(),
365										$style->getLineDotSize(),
366										$this->shadowProperties);
367			else
368				$this->canvas->drawLine(new Point($this->GArea_X1, $YPos),
369										new Point($this->GArea_X1 - 5, $YPos),
370										$style->getColor(),
371										$style->getLineWidth(),
372										$style->getLineDotSize(),
373										$this->shadowProperties);
374
375			$Value = $this->VMin + ($i - 1) * (($this->VMax - $this->VMin) / $Divisions);
376			$Value = round ( $Value * pow ( 10, $Decimals ) ) / pow ( 10, $Decimals );
377			$Value = $this->convertValueForDisplay($Value,
378												   $Data->getDataDescription()->getYFormat(),
379												   $Data->getDataDescription()->getYUnit());
380
381			$Position = imageftbbox ( $this->FontSize, 0, $this->FontName, $Value );
382			$TextWidth = $Position [2] - $Position [0];
383
384			if ($RightScale) {
385				$this->canvas->drawText($this->FontSize, 0,
386										new Point($this->GArea_X2 + 10,
387												  $YPos + ($this->FontSize / 2)),
388										$style->getColor(),
389										$this->FontName,
390										$Value,
391										ShadowProperties::NoShadow());
392				if ($XMin < $this->GArea_X2 + 15 + $TextWidth || $XMin == NULL) {
393					$XMin = $this->GArea_X2 + 15 + $TextWidth;
394				}
395			} else {
396				$this->canvas->drawText($this->FontSize,
397										0,
398										new Point($this->GArea_X1 - 10 - $TextWidth,
399												  $YPos + ($this->FontSize / 2)),
400										$style->getColor(),
401										$this->FontName,
402										$Value,
403										ShadowProperties::NoShadow());
404				if ($XMin > $this->GArea_X1 - 10 - $TextWidth || $XMin == NULL) {
405					$XMin = $this->GArea_X1 - 10 - $TextWidth;
406				}
407			}
408
409			$YPos = $YPos - $this->DivisionHeight;
410		}
411
412		/* Write the Y Axis caption if set */
413		if ($Data->getDataDescription()->getYAxisName() != '') {
414			$Position = imageftbbox ( $this->FontSize, 90, $this->FontName, $Data->getDataDescription()->getYAxisName() );
415			$TextHeight = abs ( $Position [1] ) + abs ( $Position [3] );
416			$TextTop = (($this->GArea_Y2 - $this->GArea_Y1) / 2) + $this->GArea_Y1 + ($TextHeight / 2);
417
418			if ($RightScale) {
419				$this->canvas->drawText($this->FontSize, 90,
420										new Point($XMin + $this->FontSize,
421												  $TextTop),
422										$style->getColor(), $this->FontName,
423										$Data->getDataDescription()->getYAxisName(),
424										ShadowProperties::NoShadow());
425			}
426			else {
427				$this->canvas->drawText($this->FontSize, 90,
428										new Point($XMin - $this->FontSize,
429												  $TextTop),
430										$style->getColor(), $this->FontName,
431										$Data->getDataDescription()->getYAxisName(),
432										ShadowProperties::NoShadow());
433			}
434		}
435
436		/* Horizontal Axis */
437		$XPos = $this->GArea_X1 + $this->GAreaXOffset;
438		$ID = 1;
439		$YMax = NULL;
440		foreach ( $Data->getData() as $Values ) {
441			if ($ID % $SkipLabels == 0) {
442				$this->canvas->drawLine(new Point(floor($XPos), $this->GArea_Y2),
443										new Point(floor($XPos), $this->GArea_Y2 + 5),
444										$style->getColor(),
445										$style->getLineWidth(),
446										$style->getLineDotSize(),
447										$this->shadowProperties);
448				$Value = $Values[$Data->getDataDescription()->getPosition()];
449				$Value = $this->convertValueForDisplay($Value,
450													   $Data->getDataDescription()->getXFormat(),
451													   $Data->getDataDescription()->getXUnit());
452
453				$Position = imageftbbox ( $this->FontSize, $Angle, $this->FontName, $Value );
454				$TextWidth = abs ( $Position [2] ) + abs ( $Position [0] );
455				$TextHeight = abs ( $Position [1] ) + abs ( $Position [3] );
456
457				if ($Angle == 0) {
458					$YPos = $this->GArea_Y2 + 18;
459					$this->canvas->drawText($this->FontSize,
460											$Angle,
461											new Point(floor ( $XPos ) - floor ( $TextWidth / 2 ),
462													  $YPos),
463											$style->getColor(),
464											$this->FontName,
465											$Value,
466											ShadowProperties::NoShadow());
467				} else {
468					$YPos = $this->GArea_Y2 + 10 + $TextHeight;
469					if ($Angle <= 90) {
470						$this->canvas->drawText($this->FontSize,
471												$Angle,
472												new Point(floor($XPos) - $TextWidth + 5,
473														  $YPos),
474												$style->getColor(),
475												$this->FontName,
476												$Value,
477												ShadowProperties::NoShadow());
478					}
479					else {
480						$this->canvas->drawText($this->FontSize,
481												$Angle,
482												new Point(floor ( $XPos ) + $TextWidth + 5,
483														  $YPos),
484												$style->getColor(),
485												$this->FontName,
486												$Value,
487												ShadowProperties::NoShadow());
488					}
489				}
490				if ($YMax < $YPos || $YMax == NULL) {
491					$YMax = $YPos;
492				}
493			}
494
495			$XPos = $XPos + $this->DivisionWidth;
496			$ID ++;
497		}
498
499		/* Write the X Axis caption if set */
500		if ($Data->getDataDescription()->getXAxisName() != '') {
501			$Position = imageftbbox ( $this->FontSize, 90,
502									  $this->FontName,
503									  $Data->getDataDescription()->getXAxisName());
504			$TextWidth = abs ( $Position [2] ) + abs ( $Position [0] );
505			$TextLeft = (($this->GArea_X2 - $this->GArea_X1) / 2) + $this->GArea_X1 + ($TextWidth / 2);
506			$this->canvas->drawText($this->FontSize, 0,
507									new Point($TextLeft,
508											  $YMax + $this->FontSize + 5),
509									$style->getColor(), $this->FontName,
510									$Data->getDataDescription()->getXAxisName(),
511									ShadowProperties::NoShadow());
512		}
513	}
514
515	/**
516	 * Calculate the number of divisions that the Y axis will be
517	 * divided into. This is a function of the range of Y values the
518	 * data covers, as well as the scale style. Divisions should have
519	 * some minimum size in screen coordinates in order that the
520	 * divisions are clearly visible, so this is also a function of
521	 * the graph size in screen coordinates.
522	 *
523	 * This method returns the number of divisions, but it also has
524	 * side-effects on some class data members. This needs to be
525	 * refactored to make it clearer what is and isn't affected.
526	 */
527	private function calculateDivisions(pData $Data, ScaleStyle $style) {
528		if (isset ( $Data->getDataDescription()->values[0] )) {
529			/* Pointless temporary is necessary because you can't
530			 * directly apply an array index to the return value
531			 * of a function in PHP */
532			$dataArray = $Data->getData();
533			$this->VMin = $dataArray[0] [$Data->getDataDescription()->values[0]];
534			$this->VMax = $dataArray[0] [$Data->getDataDescription()->values[0]];
535		} else {
536			$this->VMin = 2147483647;
537			$this->VMax = - 2147483647;
538		}
539
540		/* Compute Min and Max values */
541		if ($style->getScaleMode() == SCALE_NORMAL
542			|| $style->getScaleMode() == SCALE_START0) {
543			if ($style->getScaleMode() == SCALE_START0) {
544				$this->VMin = 0;
545			}
546
547			foreach ( $Data->getData() as $Values ) {
548				foreach ( $Data->getDataDescription()->values as $ColName ) {
549					if (isset ( $Values[$ColName] )) {
550						$Value = $Values[$ColName];
551
552						if (is_numeric ( $Value )) {
553							if ($this->VMax < $Value) {
554								$this->VMax = $Value;
555							}
556							if ($this->VMin > $Value) {
557								$this->VMin = $Value;
558							}
559						}
560					}
561				}
562			}
563		} elseif ($style->getScaleMode() == SCALE_ADDALL || $style->getScaleMode() == SCALE_ADDALLSTART0 ) /* Experimental */ {
564			if ($style->getScaleMode() == SCALE_ADDALLSTART0) {
565				$this->VMin = 0;
566			}
567
568			foreach ( $Data->getData() as $Values ) {
569				$Sum = 0;
570				foreach ( $Data->getDataDescription()->values as $ColName ) {
571					$dataArray = $Data->getData();
572					if (isset ( $Values[$ColName] )) {
573						$Value = $Values[$ColName];
574						if (is_numeric ( $Value ))
575							$Sum += $Value;
576					}
577				}
578				if ($this->VMax < $Sum) {
579					$this->VMax = $Sum;
580				}
581				if ($this->VMin > $Sum) {
582					$this->VMin = $Sum;
583				}
584			}
585		}
586
587		$this->VMax = ceil($this->VMax);
588
589		/* If all values are the same */
590		if ($this->VMax == $this->VMin) {
591			if ($this->VMax >= 0) {
592				$this->VMax ++;
593			} else {
594				$this->VMin --;
595			}
596		}
597
598		$DataRange = $this->VMax - $this->VMin;
599		if ($DataRange == 0) {
600			$DataRange = .1;
601		}
602
603		$this->calculateScales($Scale, $Divisions);
604
605		if (! isset ( $Divisions ))
606			$Divisions = 2;
607
608		if ($Scale == 1 && $Divisions % 2 == 1)
609			$Divisions --;
610
611		return $Divisions;
612	}
613
614	/**
615	 * Compute and draw the scale for X/Y charts
616	 */
617	function drawXYScale(pData $Data, ScaleStyle $style, $YSerieName, $XSerieName, $Angle = 0, $Decimals = 1) {
618		/* Validate the Data and DataDescription array */
619		$this->validateData ( "drawScale", $Data->getData());
620
621		$this->canvas->drawLine(new Point($this->GArea_X1, $this->GArea_Y1),
622								new Point($this->GArea_X1, $this->GArea_Y2),
623								$style->getColor(),
624								$style->getLineWidth(),
625								$style->getLineDotSize(),
626								$this->shadowProperties);
627		$this->canvas->drawLine(new Point($this->GArea_X1, $this->GArea_Y2),
628								new Point($this->GArea_X2, $this->GArea_Y2),
629								$style->getColor(),
630								$style->getLineWidth(),
631								$style->getLineDotSize(),
632								$this->shadowProperties);
633
634		/* Process Y scale */
635		if ($this->VMin == NULL && $this->VMax == NULL) {
636			$this->VMin = $Data->getSeriesMin($YSerieName);
637			$this->VMax = $Data->getSeriesMax($YSerieName);
638
639			/** @todo The use of ceil() here is questionable if all
640			 * the values are much less than 1, AIUI */
641			$this->VMax = ceil($this->VMax);
642
643			$DataRange = $this->VMax - $this->VMin;
644			if ($DataRange == 0) {
645				$DataRange = .1;
646			}
647
648			self::computeAutomaticScaling($this->GArea_Y1,
649										  $this->GArea_Y2,
650										  $this->VMin,
651										  $this->VMax,
652										  $Divisions);
653		} else
654			$Divisions = $this->Divisions;
655
656		$this->DivisionCount = $Divisions;
657
658		$DataRange = $this->VMax - $this->VMin;
659		if ($DataRange == 0) {
660			$DataRange = .1;
661		}
662
663		$this->DivisionHeight = ($this->GArea_Y2 - $this->GArea_Y1) / $Divisions;
664		$this->DivisionRatio = ($this->GArea_Y2 - $this->GArea_Y1) / $DataRange;
665
666		$YPos = $this->GArea_Y2;
667		$XMin = NULL;
668		for($i = 1; $i <= $Divisions + 1; $i ++) {
669			$this->canvas->drawLine(new Point($this->GArea_X1, $YPos),
670									new Point($this->GArea_X1 - 5, $YPos),
671									$style->getColor(),
672									$style->getLineWidth(),
673									$style->getLineDotSize(),
674									$this->shadowProperties);
675			$Value = $this->VMin + ($i - 1) * (($this->VMax - $this->VMin) / $Divisions);
676			$Value = round ( $Value * pow ( 10, $Decimals ) ) / pow ( 10, $Decimals );
677			$Value = $this->convertValueForDisplay($Value,
678												   $Data->getDataDescription()->getYFormat(),
679												   $Data->getDataDescription()->getYUnit());
680
681			$Position = imageftbbox ( $this->FontSize, 0, $this->FontName, $Value );
682			$TextWidth = $Position [2] - $Position [0];
683			$this->canvas->drawText($this->FontSize,
684									0,
685									new Point($this->GArea_X1 - 10 - $TextWidth,
686											  $YPos + ($this->FontSize / 2)),
687									$style->getColor(),
688									$this->FontName,
689									$Value,
690									$this->shadowProperties);
691
692			if ($XMin > $this->GArea_X1 - 10 - $TextWidth || $XMin == NULL) {
693				$XMin = $this->GArea_X1 - 10 - $TextWidth;
694			}
695
696			$YPos = $YPos - $this->DivisionHeight;
697		}
698
699		/* Process X scale */
700		if ($this->VXMin == NULL && $this->VXMax == NULL) {
701			$this->VXMax = $Data->getSeriesMax($XSerieName);
702			$this->VXMin = $Data->getSeriesMin($XSerieName);
703
704			$this->VXMax = ceil($this->VXMax);
705
706			$DataRange = $this->VMax - $this->VMin;
707			if ($DataRange == 0) {
708				$DataRange = .1;
709			}
710
711			/* Compute automatic scaling */
712			self::computeAutomaticScaling($this->GArea_X1, $this->GArea_X2,
713										  $this->VXMin, $this->VXMax,
714										  $XDivisions);
715		} else
716			$XDivisions = $this->XDivisions;
717
718		$this->XDivisionCount = $Divisions;
719		$this->DataCount = $Divisions + 2;
720
721		$XDataRange = $this->VXMax - $this->VXMin;
722		if ($XDataRange == 0) {
723			$XDataRange = .1;
724		}
725
726		$this->DivisionWidth = ($this->GArea_X2 - $this->GArea_X1) / $XDivisions;
727		$this->XDivisionRatio = ($this->GArea_X2 - $this->GArea_X1) / $XDataRange;
728
729		$XPos = $this->GArea_X1;
730		$YMax = NULL;
731		for($i = 1; $i <= $XDivisions + 1; $i ++) {
732			$this->canvas->drawLine(new Point($XPos, $this->GArea_Y2),
733									new Point($XPos, $this->GArea_Y2 + 5),
734									$style->getColor(),
735									$style->getLineWidth(),
736									$style->getLineDotSize(),
737									$this->shadowProperties);
738
739			$Value = $this->VXMin + ($i - 1) * (($this->VXMax - $this->VXMin) / $XDivisions);
740			$Value = round ( $Value * pow ( 10, $Decimals ) ) / pow ( 10, $Decimals );
741
742			$Value = $this->convertValueForDisplay($Value,
743												   $Data->getDataDescription()->getYFormat(),
744												   $Data->getDataDescription()->getYUnit());
745
746			$Position = imageftbbox ( $this->FontSize, $Angle, $this->FontName, $Value );
747			$TextWidth = abs ( $Position [2] ) + abs ( $Position [0] );
748			$TextHeight = abs ( $Position [1] ) + abs ( $Position [3] );
749
750			if ($Angle == 0) {
751				$YPos = $this->GArea_Y2 + 18;
752				$this->canvas->drawText($this->FontSize,
753										$Angle,
754										new Point(floor ( $XPos ) - floor ( $TextWidth / 2 ),
755												  $YPos),
756										$style->getColor(),
757										$this->FontName,
758										$Value,
759										$this->shadowProperties);
760			} else {
761				$YPos = $this->GArea_Y2 + 10 + $TextHeight;
762				if ($Angle <= 90) {
763					$this->canvas->drawText($this->FontSize,
764											$Angle,
765											new Point(floor ( $XPos ) - $TextWidth + 5,
766													  $YPos),
767											$style->getColor(),
768											$this->FontName,
769											$Value,
770											$this->shadowProperties);
771				}
772				else {
773					$this->canvas->drawText($this->FontSize,
774											$Angle,
775											new Point(floor ( $XPos ) + $TextWidth + 5,
776													  $YPos),
777											$style->getColor(),
778											$this->FontName,
779											$Value,
780											$this->shadowProperties);
781				}
782			}
783
784			if ($YMax < $YPos || $YMax == NULL) {
785				$YMax = $YPos;
786			}
787
788			$XPos = $XPos + $this->DivisionWidth;
789		}
790
791		/* Write the Y Axis caption if set */
792		if ($Data->getDataDescription()->getYAxisName() != '') {
793			$Position = imageftbbox ( $this->FontSize, 90, $this->FontName,
794									  $Data->getDataDescription()->getYAxisName());
795			$TextHeight = abs ( $Position [1] ) + abs ( $Position [3] );
796			$TextTop = (($this->GArea_Y2 - $this->GArea_Y1) / 2) + $this->GArea_Y1 + ($TextHeight / 2);
797			$this->canvas->drawText($this->FontSize,
798									90,
799									new Point($XMin - $this->FontSize,
800											  $TextTop),
801									$style->getColor(),
802									$this->FontName,
803									$Data->getDataDescription()->getYAxisName(),
804									$this->shadowProperties);
805		}
806
807		/* Write the X Axis caption if set */
808		$this->writeScaleXAxisCaption($Data, $style, $YMax);
809	}
810
811	private function drawGridMosaic(GridStyle $style, $divisionCount, $divisionHeight) {
812		$LayerHeight = $this->GArea_Y2 - $this->GArea_Y1;
813
814		$YPos = $LayerHeight - 1; //$this->GArea_Y2-1;
815		$LastY = $YPos;
816
817		for($i = 0; $i < $divisionCount; $i ++) {
818			$LastY = $YPos;
819			$YPos = $YPos - $divisionHeight;
820
821			if ($YPos <= 0) {
822				$YPos = 1;
823			}
824
825			if ($i % 2 == 0) {
826				$this->canvas->drawFilledRectangle(new Point($this->GArea_X1 + 1,
827															 $this->GArea_Y1 + $YPos),
828												   new Point($this->GArea_X2 - 1,
829															 $this->GArea_Y1 + $LastY),
830												   new Color(250, 250, 250),
831												   ShadowProperties::NoShadow(),
832												   false,
833												   $style->getAlpha());
834			}
835		}
836	}
837
838	/**
839	 * Write the X Axis caption on the scale, if set
840	 */
841	private function writeScaleXAxisCaption(pData $data, ScaleStyle $style, $YMax) {
842		if ($data->getDataDescription()->getXAxisName() != '') {
843			$Position = imageftbbox ( $this->FontSize, 90, $this->FontName, $data->getDataDescription()->getXAxisName());
844			$TextWidth = abs ( $Position [2] ) + abs ( $Position [0] );
845			$TextLeft = (($this->GArea_X2 - $this->GArea_X1) / 2) + $this->GArea_X1 + ($TextWidth / 2);
846			$this->canvas->drawText($this->FontSize,
847									0,
848									new Point($TextLeft,
849											  $YMax + $this->FontSize + 5),
850									$style->getColor(),
851									$this->FontName,
852									$data->getDataDescription()->getXAxisName(),
853									$this->shadowProperties);
854		}
855
856	}
857
858	/**
859	 * Compute and draw the scale
860	 */
861	function drawGrid(GridStyle $style) {
862		/* Draw mosaic */
863		if ($style->getMosaic()) {
864			$this->drawGridMosaic($style, $this->DivisionCount, $this->DivisionHeight);
865		}
866
867		/* Horizontal lines */
868		$YPos = $this->GArea_Y2 - $this->DivisionHeight;
869		for($i = 1; $i <= $this->DivisionCount; $i ++) {
870			if ($YPos > $this->GArea_Y1 && $YPos < $this->GArea_Y2)
871				$this->canvas->drawDottedLine(new Point($this->GArea_X1, $YPos),
872											  new Point($this->GArea_X2, $YPos),
873											  $style->getLineWidth(),
874											  $this->LineWidth,
875											  $style->getColor(),
876											  ShadowProperties::NoShadow());
877			/** @todo There's some inconsistency here. The parameter
878			 * $lineWidth appears to be used to control the dot size,
879			 * not the line width?  This is the same way it's always
880			 * been done, although now it's more obvious that there's
881			 * a problem. */
882
883			$YPos = $YPos - $this->DivisionHeight;
884		}
885
886		/* Vertical lines */
887		if ($this->GAreaXOffset == 0) {
888			$XPos = $this->GArea_X1 + $this->DivisionWidth + $this->GAreaXOffset;
889			$ColCount = $this->DataCount - 2;
890		} else {
891			$XPos = $this->GArea_X1 + $this->GAreaXOffset;
892			$ColCount = floor ( ($this->GArea_X2 - $this->GArea_X1) / $this->DivisionWidth );
893		}
894
895		for($i = 1; $i <= $ColCount; $i ++) {
896			if ($XPos > $this->GArea_X1 && $XPos < $this->GArea_X2)
897				$this->canvas->drawDottedLine(new Point(floor($XPos), $this->GArea_Y1),
898											  new Point(floor($XPos), $this->GArea_Y2),
899											  $style->getLineWidth(),
900											  $this->LineWidth,
901											  $style->getcolor(),
902											  $this->shadowProperties);
903			$XPos = $XPos + $this->DivisionWidth;
904		}
905	}
906
907	/**
908	 * retrieve the legends size
909	 */
910	public function getLegendBoxSize($DataDescription) {
911		if (! isset ( $DataDescription->description))
912			return (- 1);
913
914		/* <-10->[8]<-4->Text<-10-> */
915		$MaxWidth = 0;
916		$MaxHeight = 8;
917		foreach ( $DataDescription->description as $Value ) {
918			$Position = imageftbbox ( $this->FontSize, 0, $this->FontName, $Value );
919			$TextWidth = $Position [2] - $Position [0];
920			$TextHeight = $Position [1] - $Position [7];
921			if ($TextWidth > $MaxWidth) {
922				$MaxWidth = $TextWidth;
923			}
924			$MaxHeight = $MaxHeight + $TextHeight + 4;
925		}
926		$MaxHeight = $MaxHeight - 3;
927		$MaxWidth = $MaxWidth + 32;
928
929		return (array ($MaxWidth, $MaxHeight ));
930	}
931
932	/**
933	 * Draw the data legends
934	 */
935	public function drawLegend($XPos, $YPos, $DataDescription, Color $color, Color $color2 = null, Color $color3 = null, $Border = TRUE) {
936		if ($color2 == null) {
937			$color2 = $color->addRGBIncrement(-30);
938		}
939
940		if ($color3 == null) {
941			$color3 = new Color(0, 0, 0);
942		}
943
944		/* Validate the Data and DataDescription array */
945		$this->validateDataDescription("drawLegend", $DataDescription);
946
947		if (! isset ( $DataDescription->description))
948			return (- 1);
949
950		/* <-10->[8]<-4->Text<-10-> */
951		$MaxWidth = 0;
952		$MaxHeight = 8;
953		foreach ( $DataDescription->description as $Key => $Value ) {
954			$Position = imageftbbox ( $this->FontSize, 0, $this->FontName, $Value );
955			$TextWidth = $Position [2] - $Position [0];
956			$TextHeight = $Position [1] - $Position [7];
957			if ($TextWidth > $MaxWidth) {
958				$MaxWidth = $TextWidth;
959			}
960			$MaxHeight = $MaxHeight + $TextHeight + 4;
961		}
962		$MaxHeight = $MaxHeight - 5;
963		$MaxWidth = $MaxWidth + 32;
964
965		if ($Border) {
966			$this->canvas->drawFilledRoundedRectangle(new Point($XPos + 1, $YPos + 1),
967													  new Point($XPos + $MaxWidth + 1,
968																$YPos + $MaxHeight + 1),
969													  5, $color2,
970													  $this->LineWidth,
971													  $this->LineDotSize,
972													  $this->shadowProperties);
973
974			$this->canvas->drawFilledRoundedRectangle(new Point($XPos, $YPos),
975													  new Point($XPos + $MaxWidth,
976																$YPos + $MaxHeight),
977													  5, $color,
978													  $this->LineWidth,
979													  $this->LineDotSize,
980													  $this->shadowProperties);
981		}
982
983		$YOffset = 4 + $this->FontSize;
984		$ID = 0;
985		foreach ( $DataDescription->description as $Key => $Value ) {
986			$this->canvas->drawFilledRoundedRectangle(new Point($XPos + 10,
987																$YPos + $YOffset - 4),
988													  new Point($XPos + 14,
989																$YPos + $YOffset - 4),
990													  2,
991													  $this->palette->colors[$ID],
992													  $this->LineWidth,
993													  $this->LineDotSize,
994													  $this->shadowProperties);
995			$this->canvas->drawText($this->FontSize,
996									0,
997									new Point($XPos + 22,
998											  $YPos + $YOffset),
999									$color3,
1000									$this->FontName,
1001									$Value,
1002									$this->shadowProperties);
1003
1004			$Position = imageftbbox ( $this->FontSize, 0, $this->FontName, $Value );
1005			$TextHeight = $Position [1] - $Position [7];
1006
1007			$YOffset = $YOffset + $TextHeight + 4;
1008			$ID ++;
1009		}
1010	}
1011
1012	/**
1013	 * Draw the graph title
1014	 *
1015	 * @todo Should we pass in a ShadowProperties object here? Or is
1016	 * this a public function?
1017	 */
1018	public function drawTitle($XPos, $YPos, $Value, Color $color, $XPos2 = -1, $YPos2 = -1, ShadowProperties $shadowProperties = null) {
1019		if ($shadowProperties == null) {
1020			$shadowProperties = ShadowProperties::NoShadow();
1021		}
1022
1023		if ($XPos2 != - 1) {
1024			$Position = imageftbbox ( $this->FontSize, 0, $this->FontName, $Value );
1025			$TextWidth = $Position [2] - $Position [0];
1026			$XPos = floor ( ($XPos2 - $XPos - $TextWidth) / 2 ) + $XPos;
1027		}
1028
1029		if ($YPos2 != - 1) {
1030			$Position = imageftbbox ( $this->FontSize, 0, $this->FontName, $Value );
1031			$TextHeight = $Position [5] - $Position [3];
1032			$YPos = floor ( ($YPos2 - $YPos - $TextHeight) / 2 ) + $YPos;
1033		}
1034
1035		$this->canvas->drawText($this->FontSize,
1036								0,
1037								new Point($XPos, $YPos),
1038								$color,
1039								$this->FontName,
1040								$Value,
1041								$shadowProperties);
1042	}
1043
1044	/**
1045	 * Draw a text box with text align & alpha properties
1046	 *
1047	 * @param $point1 Minimum corner of the box
1048	 * @param $point2 Maximum corner of the box
1049	 *
1050	 * @todo This should probably be a method on the ICanvas
1051	 * interface, since it doesn't have anything specifically to do
1052	 * with graphs
1053	 */
1054	public function drawTextBox(Point $point1, Point $point2, $Text, $Angle = 0, Color $color = null, $Align = ALIGN_LEFT, ShadowProperties $shadowProperties = null, Color $backgroundColor = null, $Alpha = 100) {
1055		if ($color == null) {
1056			$color = new Color(255, 255, 255);
1057		}
1058
1059		if ($shadowProperties == null) {
1060			$shadowProperties = ShadowProperties::NoShadow();
1061		}
1062
1063		$Position = imageftbbox ( $this->FontSize, $Angle, $this->FontName, $Text );
1064		$TextWidth = $Position [2] - $Position [0];
1065		$TextHeight = $Position [5] - $Position [3];
1066		$AreaWidth = $point2->getX() - $point1->getX();
1067		$AreaHeight = $point2->getY() - $point1->getY();
1068
1069		if ($backgroundColor != null)
1070			$this->canvas->drawFilledRectangle($point1,
1071											   $point2,
1072											   $backgroundColor,
1073											   $shadowProperties, FALSE, $Alpha );
1074
1075		if ($Align == ALIGN_TOP_LEFT) {
1076			$newPosition = $point1->addIncrement(1, $this->FontSize + 1);
1077		}
1078		if ($Align == ALIGN_TOP_CENTER) {
1079			$newPosition = $point1->addIncrement(($AreaWidth / 2) - ($TextWidth / 2),
1080												 $this->FontSize + 1);
1081		}
1082		if ($Align == ALIGN_TOP_RIGHT) {
1083			$newPosition = new Point($point2->getX() - $TextWidth - 1,
1084									 $point1->getY() + $this->FontSize + 1);
1085		}
1086		if ($Align == ALIGN_LEFT) {
1087			$newPosition = $point1->addIncrement(1,
1088												 ($AreaHeight / 2) - ($TextHeight / 2));
1089		}
1090		if ($Align == ALIGN_CENTER) {
1091			$newPosition = $point1->addIncrement(($AreaWidth / 2) - ($TextWidth / 2),
1092												 ($AreaHeight / 2) - ($TextHeight / 2));
1093		}
1094		if ($Align == ALIGN_RIGHT) {
1095			$newPosition = new Point($point2->getX() - $TextWidth - 1,
1096									 $point1->getY() + ($AreaHeight / 2) - ($TextHeight / 2));
1097		}
1098		if ($Align == ALIGN_BOTTOM_LEFT) {
1099			$newPosition = new Point($point1->getX() + 1,
1100									 $point2->getY() - 1);
1101		}
1102		if ($Align == ALIGN_BOTTOM_CENTER) {
1103			$newPosition = new Point($point1->getX() + ($AreaWidth / 2) - ($TextWidth / 2),
1104									 $point2->getY() - 1);
1105		}
1106		if ($Align == ALIGN_BOTTOM_RIGHT) {
1107			$newPosition = $point2->addIncrement(- $TextWidth - 1,
1108												 -1);
1109		}
1110
1111		$this->canvas->drawText($this->FontSize, $Angle, $newPosition, $color, $this->FontName, $Text, $shadowProperties);
1112	}
1113
1114	/**
1115	 * Compute and draw the scale
1116	 *
1117	 * @todo What is the method name a typo for? Threshold?
1118	 */
1119	function drawTreshold($Value, Color $color, $ShowLabel = FALSE, $ShowOnRight = FALSE, $TickWidth = 4, $FreeText = NULL) {
1120		$Y = $this->GArea_Y2 - ($Value - $this->VMin) * $this->DivisionRatio;
1121
1122		if ($Y <= $this->GArea_Y1 || $Y >= $this->GArea_Y2)
1123			return (- 1);
1124
1125		if ($TickWidth == 0)
1126			$this->canvas->drawLine(new Point($this->GArea_X1, $Y),
1127									new Point($this->GArea_X2, $Y),
1128									$color,
1129									$this->LineWidth,
1130									$this->LineDotSize,
1131									$this->shadowProperties);
1132		else
1133			$this->canvas->drawDottedLine(new Point($this->GArea_X1, $Y),
1134										  new Point($this->GArea_X2, $Y),
1135										  $TickWidth,
1136										  $this->LineWidth,
1137										  $color,
1138										  $this->shadowProperties);
1139
1140		if ($ShowLabel) {
1141			if ($FreeText == NULL) {
1142				$Label = $Value;
1143			} else {
1144				$Label = $FreeText;
1145			}
1146
1147			if ($ShowOnRight) {
1148				$position = new Point($this->GArea_X2 + 2,
1149									  $Y + ($this->FontSize/ 2));
1150			}
1151			else {
1152				$position = new Point($this->GArea_X1 + 2,
1153									  $Y - ($this->FontSize / 2));
1154			}
1155
1156			$this->canvas->drawText($this->FontSize, 0,
1157									$position,
1158									$color,
1159									$this->FontName,
1160									$Label,
1161									ShadowProperties::NoShadow());
1162		}
1163	}
1164
1165	/**
1166	 * This function put a label on a specific point
1167	 */
1168	function setLabel($Data, $DataDescription, $SerieName, $ValueName, $Caption, Color $color = null) {
1169		if ($color == null) {
1170			$color = new Color(210, 210, 210);
1171		}
1172
1173		/* Validate the Data and DataDescription array */
1174		$this->validateDataDescription ( "setLabel", $DataDescription );
1175		$this->validateData ( "setLabel", $Data );
1176		$ShadowFactor = 100;
1177
1178		$Cp = 0;
1179		$Found = FALSE;
1180		foreach ( $Data as $Value ) {
1181			if ($Value[$DataDescription->getPosition()] == $ValueName) {
1182				$NumericalValue = $Value[$SerieName];
1183				$Found = TRUE;
1184			}
1185			if (! $Found)
1186				$Cp ++;
1187		}
1188
1189		$XPos = $this->GArea_X1 + $this->GAreaXOffset + ($this->DivisionWidth * $Cp) + 2;
1190		$YPos = $this->GArea_Y2 - ($NumericalValue - $this->VMin) * $this->DivisionRatio;
1191
1192		$Position = imageftbbox ( $this->FontSize, 0, $this->FontName, $Caption );
1193		$TextHeight = $Position [3] - $Position [5];
1194		$TextWidth = $Position [2] - $Position [0] + 2;
1195		$TextOffset = floor ( $TextHeight / 2 );
1196
1197		// Shadow
1198		$Poly = array ($XPos + 1, $YPos + 1, $XPos + 9, $YPos - $TextOffset, $XPos + 8, $YPos + $TextOffset + 2 );
1199
1200		$this->canvas->drawFilledPolygon($Poly,
1201										 3,
1202										 $color->addRGBIncrement(-$ShadowFactor));
1203
1204		$this->canvas->drawLine(new Point($XPos, $YPos + 1),
1205								new Point($XPos + 9, $YPos - $TextOffset - .2),
1206								$color->addRGBIncrement(-$ShadowFactor),
1207								$this->LineWidth,
1208								$this->LineDotSize,
1209								$this->shadowProperties);
1210
1211		$this->canvas->drawLine(new Point($XPos, $YPos + 1),
1212								new Point($XPos + 9, $YPos + $TextOffset + 2.2),
1213								$color->addRGBIncrement(-$ShadowFactor),
1214								$this->LineWidth,
1215								$this->LineDotSize,
1216								$this->shadowProperties);
1217
1218		$this->canvas->drawFilledRectangle(new Point($XPos + 9,
1219													 $YPos - $TextOffset - .2),
1220										   new Point($XPos + 13 + $TextWidth,
1221													 $YPos + $TextOffset + 2.2),
1222									 $color->addRGBIncrement(-$ShadowFactor),
1223									 $this->shadowProperties);
1224
1225		// Label background
1226		$Poly = array ($XPos, $YPos, $XPos + 8, $YPos - $TextOffset - 1, $XPos + 8, $YPos + $TextOffset + 1 );
1227
1228		$this->canvas->drawFilledPolygon($Poly, 3, $color);
1229
1230		/** @todo We draw exactly the same line twice, with the same settings.
1231		 * Surely this is pointless? */
1232		$this->canvas->drawLine(new Point($XPos - 1, $YPos),
1233								new Point($XPos + 8, $YPos - $TextOffset - 1.2),
1234								$color,
1235								$this->LineWidth,
1236								$this->LineDotSize,
1237								$this->shadowProperties);
1238
1239		$this->canvas->drawLine(new Point($XPos - 1, $YPos),
1240								new Point($XPos + 8, $YPos + $TextOffset + 1.2),
1241								$color,
1242								$this->LineWidth,
1243								$this->LineDotSize,
1244								$this->shadowProperties);
1245		$this->canvas->drawFilledRectangle(new Point($XPos + 8,
1246													 $YPos - $TextOffset - 1.2),
1247										   new Point($XPos + 12 + $TextWidth,
1248													 $YPos + $TextOffset + 1.2),
1249										   $color,
1250										   $this->shadowProperties);
1251
1252		$this->canvas->drawText($this->FontSize,
1253								0,
1254								new Point($XPos + 10, $YPos + $TextOffset),
1255								new Color(0, 0, 0),
1256								$this->FontName,
1257								$Caption,
1258								ShadowProperties::NoShadow());
1259	}
1260
1261	/**
1262	 * This function draw a plot graph
1263	 */
1264	function drawPlotGraph($Data, $DataDescription, $BigRadius = 5, $SmallRadius = 2, Color $color2 = null, $Shadow = FALSE) {
1265		/* Validate the Data and DataDescription array */
1266		$this->validateDataDescription ( "drawPlotGraph", $DataDescription );
1267		$this->validateData ( "drawPlotGraph", $Data );
1268
1269		$GraphID = 0;
1270		$colorO = $color2;
1271
1272		foreach ( $DataDescription->values as $ColName ) {
1273			$ColorID = $DataDescription->getColumnIndex($ColName);
1274
1275			$color = $this->palette->colors[$ColorID];
1276			$color2 = $colorO;
1277
1278			if (isset ( $DataDescription->seriesSymbols[$ColName] )) {
1279				$Infos = getimagesize ( $DataDescription->seriesSymbols[$ColName] );
1280				$ImageWidth = $Infos [0];
1281				$ImageHeight = $Infos [1];
1282				$Symbol = imagecreatefromgif ( $DataDescription->seriesSymbols[$ColName] );
1283			}
1284
1285			$XPos = $this->GArea_X1 + $this->GAreaXOffset;
1286			$Hsize = round ( $BigRadius / 2 );
1287
1288			$color3 = null;
1289			foreach ( $Data as $Values ) {
1290				$Value = $Values[$ColName];
1291				$YPos = $this->GArea_Y2 - (($Value - $this->VMin) * $this->DivisionRatio);
1292
1293				/* Save point into the image map if option activated */
1294				if ($this->BuildMap)
1295					$this->addToImageMap ( $XPos - $Hsize, $YPos - $Hsize, $XPos + 1 + $Hsize, $YPos + $Hsize + 1, $DataDescription->description[$ColName], $Values[$ColName] . $DataDescription->getYUnit(), "Plot" );
1296
1297				if (is_numeric ( $Value )) {
1298					if (! isset ( $DataDescription->seriesSymbols[$ColName] )) {
1299
1300						if ($Shadow) {
1301							if ($color3 != null) {
1302								$this->canvas->drawFilledCircle(new Point($XPos + 2,
1303																		  $YPos + 2),
1304																$BigRadius,
1305																$color3,
1306																$this->shadowProperties);
1307							}
1308							else {
1309								$color3 = $this->palette->colors[$ColorID]->addRGBIncrement(-20);
1310
1311								$this->canvas->drawFilledCircle(new Point($XPos + 2,
1312																		  $YPos + 2),
1313																$BigRadius,
1314																$color3,
1315																$this->shadowProperties);
1316							}
1317						}
1318
1319						$this->canvas->drawFilledCircle(new Point($XPos + 1,
1320																  $YPos + 1),
1321														$BigRadius,
1322														$color,
1323														$this->shadowProperties);
1324
1325						if ($SmallRadius != 0) {
1326							if ($color2 != null) {
1327								$this->canvas->drawFilledCircle(new Point($XPos + 1,
1328																		  $YPos + 1),
1329																$SmallRadius,
1330																$color2,
1331																$this->shadowProperties);
1332							}
1333							else {
1334								$color2 = $this->palette->colors[$ColorID]->addRGBIncrement(-15);
1335
1336								$this->canvas->drawFilledCircle(new Point($XPos + 1,
1337																		  $YPos + 1),
1338																$SmallRadius,
1339																$color2,
1340																$this->shadowProperties);
1341							}
1342						}
1343					} else {
1344						imagecopymerge ( $this->canvas->getPicture(), $Symbol, $XPos + 1 - $ImageWidth / 2, $YPos + 1 - $ImageHeight / 2, 0, 0, $ImageWidth, $ImageHeight, 100 );
1345					}
1346				}
1347
1348				$XPos = $XPos + $this->DivisionWidth;
1349			}
1350			$GraphID ++;
1351		}
1352	}
1353
1354
1355    /**
1356     * Linearly Scale a given value
1357     *
1358     * using it's own minima/maxima and the desired output minima/maxima
1359     */
1360    function linearScale($value, $istart, $istop, $ostart, $ostop){
1361        $div = ($istop - $istart);
1362        if($div == 0.0) $div = 1;
1363        return $ostart + ($ostop - $ostart) * (($value - $istart) / $div);
1364    }
1365
1366	/**
1367	 * @brief This function draw a plot graph in an X/Y space
1368	 */
1369	function drawXYPlotGraph(pData $DataSet, $YSerieName, $XSerieName, $PaletteID = 0, $BigRadius = 5, $SmallRadius = 2, Color $color2 = null, $Shadow = TRUE, $SizeSerieName = '') {
1370		$color = $this->palette->colors[$PaletteID];
1371
1372		$color3 = null;
1373
1374        $Data = $DataSet->getData();
1375		foreach ( $Data as $Values ) {
1376			if (isset($Values[$YSerieName]) && isset ($Values[$XSerieName])) {
1377				$X = $Values[$XSerieName];
1378				$Y = $Values[$YSerieName];
1379
1380				$Y = $this->GArea_Y2 - (($Y - $this->VMin) * $this->DivisionRatio);
1381				$X = $this->GArea_X1 + (($X - $this->VXMin) * $this->XDivisionRatio);
1382
1383                if(isset($Values[$SizeSerieName])){
1384                    $br = $this->linearScale(
1385                            $Values[$SizeSerieName],
1386                            $DataSet->getSeriesMin($SizeSerieName),
1387                            $DataSet->getSeriesMax($SizeSerieName),
1388                            $SmallRadius,
1389                            $BigRadius
1390                    );
1391                    $sr = $br;
1392                }else{
1393                    $br = $BigRadius;
1394                    $sr = $SmallRadius;
1395                }
1396
1397				if ($Shadow) {
1398					if ($color3 != null) {
1399						$this->canvas->drawFilledCircle(new Point($X + 2, $Y + 2),
1400														$br,
1401														$color3,
1402														$this->shadowProperties);
1403					}
1404					else {
1405						$color3 = $this->palette->colors[$PaletteID]->addRGBIncrement(-20);
1406						$this->canvas->drawFilledCircle(new Point($X + 2, $Y + 2),
1407														$br,
1408														$color3,
1409														$this->shadowProperties);
1410					}
1411				}
1412
1413				$this->canvas->drawFilledCircle(new Point($X + 1, $Y + 1),
1414												$br,
1415												$color,
1416												$this->shadowProperties);
1417
1418				if ($color2 != null) {
1419					$this->canvas->drawFilledCircle(new Point($X + 1, $Y + 1),
1420													$sr,
1421													$color2,
1422													$this->shadowProperties);
1423				}
1424				else {
1425					$color2 = $this->palette->colors[$PaletteID]->addRGBIncrement(20);
1426
1427					$this->canvas->drawFilledCircle(new Point($X + 1, $Y + 1),
1428													$sr,
1429													$color2,
1430													$this->shadowProperties);
1431				}
1432			}
1433		}
1434
1435	}
1436
1437	/**
1438	 * This function draw an area between two series
1439	 */
1440	function drawArea($Data, $Serie1, $Serie2, Color $color, $Alpha = 50) {
1441		/* Validate the Data and DataDescription array */
1442		$this->validateData ( "drawArea", $Data );
1443
1444		$LayerHeight = $this->GArea_Y2 - $this->GArea_Y1;
1445
1446		$XPos = $this->GAreaXOffset;
1447		$LastXPos = - 1;
1448		foreach ( $Data as $Values ) {
1449			$Value1 = $Values[$Serie1];
1450			$Value2 = $Values[$Serie2];
1451			$YPos1 = $LayerHeight - (($Value1 - $this->VMin) * $this->DivisionRatio);
1452			$YPos2 = $LayerHeight - (($Value2 - $this->VMin) * $this->DivisionRatio);
1453
1454			if ($LastXPos != - 1) {
1455				$Points = array();
1456				$Points [] = $LastXPos + $this->GArea_X1;
1457				$Points [] = $LastYPos1 + $this->GArea_Y1;
1458				$Points [] = $LastXPos + $this->GArea_X1;
1459				$Points [] = $LastYPos2 + $this->GArea_Y1;
1460				$Points [] = $XPos + $this->GArea_X1;
1461				$Points [] = $YPos2 + $this->GArea_Y1;
1462				$Points [] = $XPos + $this->GArea_X1;
1463				$Points [] = $YPos1 + $this->GArea_Y1;
1464
1465				$this->canvas->drawFilledPolygon($Points,
1466												 4,
1467												 $color,
1468												 $Alpha);
1469			}
1470
1471			$LastYPos1 = $YPos1;
1472			$LastYPos2 = $YPos2;
1473			$LastXPos = $XPos;
1474
1475			$XPos = $XPos + $this->DivisionWidth;
1476		}
1477	}
1478
1479	/**
1480	 * This function write the values of the specified series
1481	 */
1482	function writeValues($Data, $DataDescription, $Series) {
1483		/* Validate the Data and DataDescription array */
1484		$this->validateDataDescription ( "writeValues", $DataDescription );
1485		$this->validateData ( "writeValues", $Data );
1486
1487		if (! is_array ( $Series )) {
1488			$Series = array ($Series );
1489		}
1490
1491		foreach ( $Series as $Serie ) {
1492			$ColorID = $DataDescription->getColumnIndex($Serie);
1493
1494			$XPos = $this->GArea_X1 + $this->GAreaXOffset;
1495
1496			foreach ( $Data as $Values ) {
1497				if (isset ( $Values[$Serie] ) && is_numeric ( $Values[$Serie] )) {
1498					$Value = $Values[$Serie];
1499					$YPos = $this->GArea_Y2 - (($Value - $this->VMin) * $this->DivisionRatio);
1500
1501					$Positions = imagettfbbox ( $this->FontSize, 0, $this->FontName, $Value );
1502					$Width = $Positions [2] - $Positions [6];
1503					$XOffset = $XPos - ($Width / 2);
1504					$YOffset = $YPos - 4;
1505
1506					$this->canvas->drawText($this->FontSize,
1507											0,
1508											new Point($XOffset, $YOffset),
1509											$this->palette->colors[$ColorID],
1510											$this->FontName,
1511											$Value,
1512											ShadowProperties::NoShadow());
1513				}
1514				$XPos = $XPos + $this->DivisionWidth;
1515			}
1516
1517		}
1518	}
1519
1520	/**
1521	 * @brief Draws a line graph where the data gives Y values for a
1522	 * series of regular positions along the X axis
1523	 */
1524	function drawLineGraph($Data, $DataDescription, $SerieName = "") {
1525		/* Validate the Data and DataDescription array */
1526		$this->validateDataDescription ( "drawLineGraph", $DataDescription );
1527		$this->validateData ( "drawLineGraph", $Data );
1528
1529		$GraphID = 0;
1530		foreach ( $DataDescription->values as $ColName ) {
1531			$ColorID = $DataDescription->getColumnIndex($ColName);
1532
1533			if ($SerieName == "" || $SerieName == $ColName) {
1534				$XPos = $this->GArea_X1 + $this->GAreaXOffset;
1535				$XLast = - 1;
1536				foreach ( $Data as $Values ) {
1537					if (isset ( $Values[$ColName] )) {
1538						$Value = $Values[$ColName];
1539						$YPos = $this->GArea_Y2 - (($Value - $this->VMin) * $this->DivisionRatio);
1540
1541						/* Save point into the image map if option activated */
1542						if ($this->BuildMap)
1543							$this->addToImageMap ( $XPos - 3, $YPos - 3, $XPos + 3, $YPos + 3, $DataDescription->description[$ColName], $Values[$ColName] . $DataDescription->getYUnit(), "Line" );
1544
1545						if (! is_numeric ( $Value )) {
1546							$XLast = - 1;
1547						}
1548						if ($XLast != - 1)
1549							$this->canvas->drawLine(new Point($XLast, $YLast),
1550													new Point($XPos, $YPos),
1551													$this->palette->colors[$ColorID],
1552													$this->LineWidth,
1553													$this->LineDotSize,
1554													$this->shadowProperties,
1555													new Point($this->GArea_X1,
1556															  $this->GArea_Y1),
1557													new Point($this->GArea_X2,
1558															  $this->GArea_Y2));
1559
1560						$XLast = $XPos;
1561						$YLast = $YPos;
1562						if (! is_numeric ( $Value )) {
1563							$XLast = - 1;
1564						}
1565					}
1566					$XPos = $XPos + $this->DivisionWidth;
1567				}
1568				$GraphID ++;
1569			}
1570		}
1571	}
1572
1573	/**
1574	 * @brief Draws a line graph where one series of data defines the
1575	 * X position and another the Y position
1576	 */
1577	function drawXYGraph($Data, $YSerieName, $XSerieName, $PaletteID = 0) {
1578		$graphAreaMin = new Point($this->GArea_X1, $this->GArea_Y1);
1579		$graphAreaMax = new Point($this->GArea_X2, $this->GArea_Y2);
1580
1581		$lastPoint = null;
1582
1583		foreach ($Data as $Values) {
1584			if (isset ( $Values[$YSerieName] ) && isset ( $Values[$XSerieName] )) {
1585				$X = $Values[$XSerieName];
1586				$Y = $Values[$YSerieName];
1587
1588				$Y = $this->GArea_Y2 - (($Y - $this->VMin) * $this->DivisionRatio);
1589				$X = $this->GArea_X1 + (($X - $this->VXMin) * $this->XDivisionRatio);
1590
1591				$currentPoint = new Point($X, $Y);
1592
1593				if ($lastPoint != null) {
1594					$this->canvas->drawLine($lastPoint,
1595											$currentPoint,
1596											$this->palette->colors[$PaletteID],
1597											$this->LineWidth,
1598											$this->LineDotSize,
1599											$this->shadowProperties,
1600											$graphAreaMin,
1601											$graphAreaMax);
1602				}
1603
1604				$lastPoint = $currentPoint;
1605			}
1606		}
1607	}
1608
1609	/**
1610	 * This function draw a cubic curve
1611	 */
1612	function drawCubicCurve(pData $data, $Accuracy = .1, $SerieName = "") {
1613		/* Validate the Data and DataDescription array */
1614		$this->validateDataDescription("drawCubicCurve",
1615									   $data->getDataDescription());
1616		$this->validateData ( "drawCubicCurve", $data->getData() );
1617
1618		$graphAreaMin = new Point($this->GArea_X1, $this->GArea_Y1);
1619		$graphAreaMax = new Point($this->GArea_X2, $this->GArea_Y2);
1620
1621		$GraphID = 0;
1622		foreach ( $data->getDataDescription()->values as $ColName ) {
1623			if ($SerieName == "" || $SerieName == $ColName) {
1624				/** @todo The next section of code has been duplicated by
1625				 * copy & paste */
1626				$XIn = array();
1627				$YIn = array();
1628				$Yt = "";
1629				$U = "";
1630
1631				$ColorID = $data->getDataDescription()->getColumnIndex($ColName);
1632
1633				$Index = 1;
1634				$XLast = - 1;
1635				$Missing = array();
1636
1637				$data->getXYMap($ColName, $XIn, $YIn, $Missing, $Index);
1638
1639				assert(count($XIn) == count($YIn));
1640				assert($Index + 1 >= count($XIn));
1641
1642				$Yt [0] = 0;
1643				$Yt [1] = 0;
1644				$U [1] = 0;
1645
1646				$this->calculateCubicCurve($Yt, $XIn, $YIn, $U, $Index);
1647
1648				$Yt [$Index] = 0;
1649
1650				for($k = $Index - 1; $k >= 1; $k --)
1651					$Yt [$k] = $Yt [$k] * $Yt [$k + 1] + $U [$k];
1652
1653				$XPos = $this->GArea_X1 + $this->GAreaXOffset;
1654				for($X = 1; $X <= $Index; $X = $X + $Accuracy) {
1655
1656					/* I believe here we're searching for the integral
1657					 * value k such that $X lies between $XIn[k] and
1658					 * $XIn[k-1] */
1659					$klo = 1;
1660					$khi = $Index;
1661					$k = $khi - $klo;
1662					while ( $k > 1 ) {
1663						$k = $khi - $klo;
1664						If ($XIn [$k] >= $X)
1665							$khi = $k;
1666						else
1667							$klo = $k;
1668					}
1669					$klo = $khi - 1;
1670
1671					/* These assertions are to check my understanding
1672					 * of the code. If they fail, it is my fault and
1673					 * not a bug */
1674					assert($khi = $klo + 1);
1675					assert($XIn[$klo] < $X);
1676					assert($X <= $XIn[$khi]);
1677
1678					$h = $XIn [$khi] - $XIn [$klo];
1679					$a = ($XIn [$khi] - $X) / $h;
1680					$b = ($X - $XIn [$klo]) / $h;
1681
1682					/**
1683					 * I believe this is the actual cubic Bezier
1684					 * calculation. In parametric form:
1685					 *
1686					 * B(t) = (1-t)^3 * B0
1687					 *        + 3 (1-t)^2 * t * B1
1688					 *        + 3 (1-t) * t^2 * B2
1689					 *        + t^3 B3
1690					 */
1691					$Value = $a * $YIn [$klo] + $b * $YIn [$khi] + (($a * $a * $a - $a) * $Yt [$klo] + ($b * $b * $b - $b) * $Yt [$khi]) * ($h * $h) / 6;
1692
1693					$YPos = $this->GArea_Y2 - (($Value - $this->VMin) * $this->DivisionRatio);
1694
1695					if ($XLast != - 1 && ! isset ( $Missing [floor ( $X )] ) && ! isset ( $Missing [floor ( $X + 1 )] ))
1696						$this->canvas->drawLine(new Point($XLast,
1697														  $YLast),
1698												new Point($XPos,
1699														  $YPos),
1700												$this->palette->colors[$ColorID],
1701												$this->LineWidth,
1702												$this->LineDotSize,
1703												$this->shadowProperties,
1704												$graphAreaMin,
1705												$graphAreaMax);
1706
1707					$XLast = $XPos;
1708					$YLast = $YPos;
1709					$XPos = $XPos + $this->DivisionWidth * $Accuracy;
1710				}
1711
1712				// Add potentialy missing values
1713				$XPos = $XPos - $this->DivisionWidth * $Accuracy;
1714				if ($XPos < ($this->GArea_X2 - $this->GAreaXOffset)) {
1715					$YPos = $this->GArea_Y2 - (($YIn [$Index] - $this->VMin) * $this->DivisionRatio);
1716					$this->canvas->drawLine(new Point($XLast,
1717													  $YLast),
1718											new Point($this->GArea_X2 - $this->GAreaXOffset,
1719													  $YPos),
1720											$this->palette->colors[$ColorID],
1721											$this->LineWidth,
1722											$this->LineDotSize,
1723											$this->shadowProperties,
1724											$graphAreaMin,
1725											$graphAreaMax);
1726				}
1727
1728				$GraphID ++;
1729			}
1730		}
1731	}
1732
1733	/**
1734	 * @todo I haven't figured out exactly what this bit of code does,
1735	 * it's just an attempt to reduce code duplication
1736	 */
1737	private function calculateCubicCurve(array & $Yt, array $XIn, array $YIn, array & $U, $Index) {
1738		for($i = 2; $i <= $Index - 1; $i ++) {
1739			/* Typically $Sig will be 0.5, since each X value will be
1740			 * one unit past the last. If there is missing data then
1741			 * this ratio will change */
1742			$Sig = ($XIn [$i] - $XIn [$i - 1]) / ($XIn [$i + 1] - $XIn [$i - 1]);
1743
1744			$p = $Sig * $Yt [$i - 1] + 2;
1745
1746			/* This Y value will nearly always be negative, thanks to
1747			 * $Sig being 0.5 */
1748			$Yt [$i] = ($Sig - 1) / $p;
1749
1750			/** @todo No idea what the following code is doing */
1751			$U [$i] = ($YIn [$i + 1] - $YIn [$i]) / ($XIn [$i + 1] - $XIn [$i])
1752				- ($YIn [$i] - $YIn [$i - 1]) / ($XIn [$i] - $XIn [$i - 1]);
1753			$U [$i] = (6 * $U [$i] / ($XIn [$i + 1] - $XIn [$i - 1]) - $Sig * $U [$i - 1]) / $p;
1754		}
1755	}
1756
1757	/**
1758	 * This function draw a filled cubic curve
1759	 */
1760	function drawFilledCubicCurve(pData $data, $Accuracy = .1, $Alpha = 100, $AroundZero = FALSE) {
1761		/* Validate the Data and DataDescription array */
1762		$this->validateDataDescription("drawFilledCubicCurve",
1763									   $data->getDataDescription());
1764		$this->validateData ( "drawFilledCubicCurve", $data->getData() );
1765
1766		$LayerWidth = $this->GArea_X2 - $this->GArea_X1;
1767		$LayerHeight = $this->GArea_Y2 - $this->GArea_Y1;
1768		$YZero = $LayerHeight - ((0 - $this->VMin) * $this->DivisionRatio);
1769		if ($YZero > $LayerHeight) {
1770			$YZero = $LayerHeight;
1771		}
1772
1773		$GraphID = 0;
1774		foreach ($data->getDataDescription()->values as $ColName) {
1775			$XIn = array();
1776			$YIn = array();
1777			$Yt = array();
1778			$U = array();
1779
1780			$ColorID = $data->getDataDescription()->getColumnIndex($ColName);
1781
1782			$numElements = 1;
1783			$XLast = - 1;
1784			$Missing = array();
1785
1786			$data->getXYMap($ColName, $XIn, $YIn, $Missing, $numElements);
1787
1788			$Yt [0] = 0;
1789			$Yt [1] = 0;
1790			$U [1] = 0;
1791
1792			$this->calculateCubicCurve($Yt, $XIn, $YIn, $U, $numElements);
1793
1794			$Yt [$numElements] = 0;
1795
1796			for($k = $numElements - 1; $k >= 1; $k --)
1797				$Yt [$k] = $Yt [$k] * $Yt [$k + 1] + $U [$k];
1798
1799			$Points = "";
1800			$Points [] = $this->GAreaXOffset + $this->GArea_X1;
1801			$Points [] = $LayerHeight + $this->GArea_Y1;
1802
1803			$YLast = NULL;
1804			$XPos = $this->GAreaXOffset;
1805			$PointsCount = 2;
1806			for($X = 1; $X <= $numElements; $X = $X + $Accuracy) {
1807				$klo = 1;
1808				$khi = $numElements;
1809				$k = $khi - $klo;
1810				while ( $k > 1 ) {
1811					$k = $khi - $klo;
1812					If ($XIn [$k] >= $X)
1813						$khi = $k;
1814					else
1815						$klo = $k;
1816				}
1817				$klo = $khi - 1;
1818
1819				$h = $XIn [$khi] - $XIn [$klo];
1820				$a = ($XIn [$khi] - $X) / $h;
1821				$b = ($X - $XIn [$klo]) / $h;
1822				$Value = $a * $YIn [$klo] + $b * $YIn [$khi] + (($a * $a * $a - $a) * $Yt [$klo] + ($b * $b * $b - $b) * $Yt [$khi]) * ($h * $h) / 6;
1823
1824				$YPos = $LayerHeight - (($Value - $this->VMin) * $this->DivisionRatio);
1825
1826				if ($YLast != NULL && $AroundZero && ! isset ( $Missing [floor ( $X )] ) && ! isset ( $Missing [floor ( $X + 1 )] )) {
1827					$aPoints = "";
1828
1829					$aPoints [] = $XLast + $this->GArea_X1;
1830					$aPoints [] = min($YLast + $this->GArea_Y1, $this->GArea_Y2);
1831					$aPoints [] = $XPos + $this->GArea_X1;
1832					$aPoints [] = min($YPos + $this->GArea_Y1, $this->GArea_Y2);
1833					$aPoints [] = $XPos + $this->GArea_X1;
1834					$aPoints [] = $YZero + $this->GArea_Y1;
1835					$aPoints [] = $XLast + $this->GArea_X1;
1836					$aPoints [] = $YZero + $this->GArea_Y1;
1837
1838					$this->canvas->drawFilledPolygon($aPoints,
1839													 4,
1840													 $this->palette->colors[$ColorID],
1841													 $alpha);
1842				}
1843
1844				if (! isset ( $Missing [floor ( $X )] ) || $YLast == NULL) {
1845					$PointsCount ++;
1846					$Points [] = $XPos + $this->GArea_X1;
1847					$Points [] = min($YPos + $this->GArea_Y1, $this->GArea_Y2);
1848				} else {
1849					$PointsCount ++;
1850					$Points [] = $XLast + $this->GArea_X1;
1851					$Points [] = min($LayerHeight + $this->GArea_Y1,
1852									 $this->GArea_Y2);;
1853				}
1854
1855				$YLast = $YPos;
1856				$XLast = $XPos;
1857				$XPos = $XPos + $this->DivisionWidth * $Accuracy;
1858			}
1859
1860			// Add potentialy missing values
1861			$XPos = $XPos - $this->DivisionWidth * $Accuracy;
1862			if ($XPos < ($LayerWidth - $this->GAreaXOffset)) {
1863				$YPos = $LayerHeight - (($YIn [$numElements] - $this->VMin) * $this->DivisionRatio);
1864				if ($YLast != NULL && $AroundZero) {
1865					$aPoints = "";
1866					$aPoints [] = $XLast + $this->GArea_X1;
1867					$aPoints [] = max($YLast + $this->GArea_Y1, $this->GArea_Y1);
1868					$aPoints [] = $LayerWidth - $this->GAreaXOffset + $this->GArea_X1;
1869					$aPoints [] = max($YPos + $this->GArea_Y1, $this->GArea_Y1);
1870					$aPoints [] = $LayerWidth - $this->GAreaXOffset + $this->GArea_X1;
1871					$aPoints [] = max($YZero + $this->GArea_Y1, $this->GArea_Y1);
1872					$aPoints [] = $XLast + $this->GArea_X1;
1873					$aPoints [] = max($YZero + $this->GArea_Y1, $this->GArea_Y1);
1874
1875					$this->canvas->drawFilledPolygon($aPoints,
1876													 4,
1877													 $this->palette->colors[$ColorID],
1878													 $alpha);
1879				}
1880
1881				if ($YIn [$klo] != "" && $YIn [$khi] != "" || $YLast == NULL) {
1882					$PointsCount ++;
1883					$Points [] = $LayerWidth
1884						- $this->GAreaXOffset
1885						+ $this->GArea_X1;
1886					$Points [] = $YPos + $this->GArea_Y1;
1887				}
1888			}
1889
1890			$Points [] = $LayerWidth - $this->GAreaXOffset + $this->GArea_X1;
1891			$Points [] = $LayerHeight + $this->GArea_Y1;
1892
1893			if (! $AroundZero) {
1894				$this->canvas->drawFilledPolygon($Points,
1895												 $PointsCount,
1896												 $this->palette->colors[$ColorID],
1897												 $Alpha);
1898			}
1899
1900			$this->drawCubicCurve($data, $Accuracy, $ColName );
1901
1902			$GraphID ++;
1903		}
1904	}
1905
1906	/**
1907	 * This function draw a filled line graph
1908	 */
1909	function drawFilledLineGraph($Data, $DataDescription, $Alpha = 100, $AroundZero = FALSE) {
1910		$Empty = - 2147483647;
1911
1912		/* Validate the Data and DataDescription array */
1913		$this->validateDataDescription ( "drawFilledLineGraph", $DataDescription );
1914		$this->validateData ( "drawFilledLineGraph", $Data );
1915
1916		$LayerWidth = $this->GArea_X2 - $this->GArea_X1;
1917		$LayerHeight = $this->GArea_Y2 - $this->GArea_Y1;
1918
1919		$GraphID = 0;
1920		foreach ( $DataDescription->values as $ColName ) {
1921			$ColorID = $DataDescription->getColumnIndex($ColName);
1922
1923			$aPoints = array();
1924			$aPoints [] = $this->GAreaXOffset + $this->GArea_X1;
1925			$aPoints [] = $LayerHeight + $this->GArea_Y1;
1926
1927			$XPos = $this->GAreaXOffset;
1928			$XLast = - 1;
1929			$PointsCount = 2;
1930			$YZero = $LayerHeight - ((0 - $this->VMin) * $this->DivisionRatio);
1931			if ($YZero > $LayerHeight) {
1932				$YZero = $LayerHeight;
1933			}
1934
1935			$YLast = $Empty;
1936			foreach (array_keys($Data) as $Key) {
1937				$Value = $Data [$Key] [$ColName];
1938				$YPos = $LayerHeight - (($Value - $this->VMin) * $this->DivisionRatio);
1939
1940				/* Save point into the image map if option activated */
1941				if ($this->BuildMap)
1942					$this->addToImageMap ( $XPos - 3, $YPos - 3, $XPos + 3, $YPos + 3, $DataDescription->description[$ColName], $Data [$Key] [$ColName] . $DataDescription->getYUnit(), "FLine" );
1943
1944				if (! is_numeric ( $Value )) {
1945					$PointsCount ++;
1946					$aPoints [] = $XLast + $this->GArea_X1;
1947					$aPoints [] = $LayerHeight + $this->GArea_Y1;
1948
1949					$YLast = $Empty;
1950				} else {
1951					$PointsCount ++;
1952					if ($YLast != $Empty) {
1953						$aPoints [] = $XPos + $this->GArea_X1;
1954						$aPoints [] = $YPos + $this->GArea_Y1;
1955					} else {
1956						$PointsCount ++;
1957
1958						$aPoints [] = $XPos + $this->GArea_X1;
1959						$aPoints [] = $LayerHeight + $this->GArea_Y1;
1960						$aPoints [] = $XPos + $this->GArea_X1;
1961						$aPoints [] = $YPos + $this->GArea_Y1;
1962					}
1963
1964					if ($YLast != $Empty && $AroundZero) {
1965						$Points = "";
1966						$Points [] = $XLast + $this->GArea_X1;
1967						$Points [] = $YLast + $this->GArea_Y1;
1968						$Points [] = $XPos + $this->GArea_X1;
1969						$Points [] = $YPos + $this->GArea_Y1;
1970						$Points [] = $XPos + $this->GArea_X1;
1971						$Points [] = $YZero + $this->GArea_Y1;
1972						$Points [] = $XLast + $this->GArea_X1;
1973						$Points [] = $YZero + $this->GArea_Y1;
1974
1975						$this->canvas->drawFilledPolygon($Points,
1976														 4,
1977														 $this->palette->colors[$ColorID],
1978														 $Alpha);
1979					}
1980					$YLast = $YPos;
1981				}
1982
1983				$XLast = $XPos;
1984				$XPos = $XPos + $this->DivisionWidth;
1985			}
1986			$aPoints [] = $LayerWidth - $this->GAreaXOffset + $this->GArea_X1;
1987			$aPoints [] = $LayerHeight + $this->GArea_Y1;
1988
1989			if ($AroundZero == FALSE) {
1990				$this->canvas->drawFilledPolygon($aPoints,
1991												 $PointsCount,
1992												 $this->palette->colors[$ColorID],
1993												 $Alpha);
1994			}
1995
1996			$GraphID ++;
1997			$this->drawLineGraph ( $Data, $DataDescription, $ColName );
1998		}
1999	}
2000
2001	/**
2002	 * This function draws a bar graph
2003	 */
2004	function drawOverlayBarGraph($Data, $DataDescription, $Alpha = 50) {
2005		/* Validate the Data and DataDescription array */
2006		$this->validateDataDescription ( "drawOverlayBarGraph", $DataDescription );
2007		$this->validateData ( "drawOverlayBarGraph", $Data );
2008
2009		$LayerHeight = $this->GArea_Y2 - $this->GArea_Y1;
2010
2011		$GraphID = 0;
2012		foreach ( $DataDescription->values as $ColName ) {
2013			$ColorID = $DataDescription->getColumnIndex($ColName);
2014
2015			$XWidth = $this->DivisionWidth / 4;
2016			$XPos = $this->GAreaXOffset;
2017			$YZero = $LayerHeight - ((0 - $this->VMin) * $this->DivisionRatio);
2018			foreach (array_keys($Data) as $Key) {
2019				if (isset ( $Data [$Key] [$ColName] )) {
2020					$Value = $Data [$Key] [$ColName];
2021					if (is_numeric ( $Value )) {
2022						$YPos = $LayerHeight - (($Value - $this->VMin) * $this->DivisionRatio);
2023
2024						$this->canvas->drawFilledRectangle(new Point(floor($XPos - $XWidth + $this->GArea_X1),
2025																	 floor($YPos + $this->GArea_Y1)),
2026														   new Point(floor($XPos + $XWidth + $this->GArea_X1),
2027																	 floor($YZero + $this->GArea_Y1)),
2028														   $this->palette->colors[$GraphID],
2029														   ShadowProperties::NoShadow(),
2030														   false,
2031														   $Alpha);
2032
2033						$X1 = floor ( $XPos - $XWidth + $this->GArea_X1 );
2034						$Y1 = floor ( $YPos + $this->GArea_Y1 ) + .2;
2035						$X2 = floor ( $XPos + $XWidth + $this->GArea_X1 );
2036						$Y2 = $this->GArea_Y2 - ((0 - $this->VMin) * $this->DivisionRatio);
2037						if ($X1 <= $this->GArea_X1) {
2038							$X1 = $this->GArea_X1 + 1;
2039						}
2040						if ($X2 >= $this->GArea_X2) {
2041							$X2 = $this->GArea_X2 - 1;
2042						}
2043
2044						/* Save point into the image map if option activated */
2045						if ($this->BuildMap)
2046							$this->addToImageMap ( $X1, min ( $Y1, $Y2 ), $X2, max ( $Y1, $Y2 ), $DataDescription->description[$ColName], $Data [$Key] [$ColName] . $DataDescription->getYUnit(), "oBar" );
2047
2048						$this->canvas->drawLine(new Point($X1,
2049														  $Y1),
2050												new Point($X2,
2051														  $Y1),
2052												$this->palette->colors[$ColorID],
2053												$this->LineWidth,
2054												$this->LineDotSize,
2055												$this->shadowProperties,
2056												new Point($this->GArea_X1,
2057														  $this->GArea_Y1),
2058												new Point($this->GArea_X2,
2059														  $this->GArea_Y2));
2060					}
2061				}
2062				$XPos = $XPos + $this->DivisionWidth;
2063			}
2064
2065			$GraphID ++;
2066		}
2067	}
2068
2069	/**
2070	 * This function draw a bar graph
2071	 */
2072	function drawBarGraph($Data, $DataDescription, $Alpha = 100) {
2073		/* Validate the Data and DataDescription array */
2074		$this->validateDataDescription ( "drawBarGraph", $DataDescription );
2075		$this->validateData ( "drawBarGraph", $Data );
2076
2077		$Series = count ( $DataDescription->values);
2078		$SeriesWidth = $this->DivisionWidth / ($Series + 1);
2079		$SerieXOffset = $this->DivisionWidth / 2 - $SeriesWidth / 2;
2080
2081		$YZero = $this->GArea_Y2 - ((0 - $this->VMin) * $this->DivisionRatio);
2082		if ($YZero > $this->GArea_Y2) {
2083			$YZero = $this->GArea_Y2;
2084		}
2085
2086		$SerieID = 0;
2087
2088		foreach ( $DataDescription->values as $ColName ) {
2089			$ColorID = $DataDescription->getColumnIndex($ColName);
2090
2091			$XPos = $this->GArea_X1 + $this->GAreaXOffset - $SerieXOffset + $SeriesWidth * $SerieID;
2092			foreach (array_keys($Data) as $Key) {
2093				if (isset ( $Data [$Key] [$ColName] )) {
2094					if (is_numeric ( $Data [$Key] [$ColName] )) {
2095						$Value = $Data [$Key] [$ColName];
2096						$YPos = $this->GArea_Y2 - (($Value - $this->VMin) * $this->DivisionRatio);
2097
2098						/* Save point into the image map if option activated */
2099						if ($this->BuildMap) {
2100							$this->addToImageMap ( $XPos + 1, min ( $YZero, $YPos ), $XPos + $SeriesWidth - 1, max ( $YZero, $YPos ), $DataDescription->description[$ColName], $Data [$Key] [$ColName] . $DataDescription->getYUnit(), "Bar" );
2101						}
2102
2103						if ($Alpha == 100) {
2104							$this->canvas->drawRectangle(new Point($XPos + 1, $YZero),
2105														 new Point($XPos + $SeriesWidth - 1,
2106																   $YPos),
2107														 new Color(25, 25, 25),
2108														 $this->LineWidth,
2109														 $this->LineDotSize,
2110														 $this->shadowProperties);
2111						}
2112
2113						$this->canvas->drawFilledRectangle(new Point($XPos + 1,
2114																	 $YZero),
2115														   new Point($XPos + $SeriesWidth - 1,
2116																	 $YPos),
2117														   $this->palette->colors[$ColorID],
2118														   $this->shadowProperties,
2119														   TRUE, $Alpha);
2120					}
2121				}
2122				$XPos = $XPos + $this->DivisionWidth;
2123			}
2124			$SerieID ++;
2125		}
2126	}
2127
2128	/**
2129	 * This function draw a stacked bar graph
2130	 */
2131	function drawStackedBarGraph($Data, $DataDescription, $Alpha = 50, $Contiguous = FALSE) {
2132		/* Validate the Data and DataDescription array */
2133		$this->validateDataDescription ( "drawBarGraph", $DataDescription );
2134		$this->validateData ( "drawBarGraph", $Data );
2135
2136		if ($Contiguous)
2137			$SeriesWidth = $this->DivisionWidth;
2138		else
2139			$SeriesWidth = $this->DivisionWidth * .8;
2140
2141		$YZero = $this->GArea_Y2 - ((0 - $this->VMin) * $this->DivisionRatio);
2142		if ($YZero > $this->GArea_Y2) {
2143			$YZero = $this->GArea_Y2;
2144		}
2145
2146		$SerieID = 0;
2147		$LastValue = "";
2148		foreach ( $DataDescription->values as $ColName ) {
2149			$ColorID = $DataDescription->getColumnIndex($ColName);
2150
2151			$XPos = $this->GArea_X1 + $this->GAreaXOffset - $SeriesWidth / 2;
2152			foreach (array_keys($Data) as $Key) {
2153				if (isset ( $Data [$Key] [$ColName] )) {
2154					if (is_numeric ( $Data [$Key] [$ColName] )) {
2155						$Value = $Data [$Key] [$ColName];
2156
2157						if (isset ( $LastValue [$Key] )) {
2158							$YPos = $this->GArea_Y2 - ((($Value + $LastValue [$Key]) - $this->VMin) * $this->DivisionRatio);
2159							$YBottom = $this->GArea_Y2 - (($LastValue [$Key] - $this->VMin) * $this->DivisionRatio);
2160							$LastValue [$Key] += $Value;
2161						} else {
2162							$YPos = $this->GArea_Y2 - (($Value - $this->VMin) * $this->DivisionRatio);
2163							$YBottom = $YZero;
2164							$LastValue [$Key] = $Value;
2165						}
2166
2167						/* Save point into the image map if option activated */
2168						if ($this->BuildMap)
2169							$this->addToImageMap ( $XPos + 1, min ( $YBottom, $YPos ), $XPos + $SeriesWidth - 1, max ( $YBottom, $YPos ), $DataDescription->description[$ColName], $Data [$Key] [$ColName] . $DataDescription->getYUnit(), "sBar" );
2170
2171						$this->canvas->drawFilledRectangle(new Point($XPos + 1,
2172																	 $YBottom),
2173														   new Point($XPos + $SeriesWidth - 1,
2174																	 $YPos),
2175														   $this->palette->colors[$ColorID],
2176														   $this->shadowProperties,
2177														   TRUE,
2178														   $Alpha);
2179					}
2180				}
2181				$XPos = $XPos + $this->DivisionWidth;
2182			}
2183			$SerieID ++;
2184		}
2185	}
2186
2187	/**
2188	 * This function draw a limits bar graphs
2189	 */
2190	function drawLimitsGraph($Data, $DataDescription, Color $color = null) {
2191		if ($color == null) {
2192			$color = new Color(0, 0, 0);
2193		}
2194
2195		/* Validate the Data and DataDescription array */
2196		$this->validateDataDescription ( "drawLimitsGraph", $DataDescription );
2197		$this->validateData ( "drawLimitsGraph", $Data );
2198
2199		$XWidth = $this->DivisionWidth / 4;
2200		$XPos = $this->GArea_X1 + $this->GAreaXOffset;
2201
2202		$graphAreaMin = new Point($this->GArea_X1, $this->GArea_Y1);
2203		$graphAreaMax = new Point($this->GArea_X2, $this->GArea_Y2);
2204
2205		foreach (array_keys($Data) as $Key) {
2206			$Min = $Data [$Key] [$DataDescription->values[0]];
2207			$Max = $Data [$Key] [$DataDescription->values[0]];
2208			$GraphID = 0;
2209			$MaxID = 0;
2210			$MinID = 0;
2211			foreach ( $DataDescription->values as $ColName ) {
2212				if (isset ( $Data [$Key] [$ColName] )) {
2213					if ($Data [$Key] [$ColName] > $Max && is_numeric ( $Data [$Key] [$ColName] )) {
2214						$Max = $Data [$Key] [$ColName];
2215						$MaxID = $GraphID;
2216					}
2217				}
2218				if (isset ( $Data [$Key] [$ColName] ) && is_numeric ( $Data [$Key] [$ColName] )) {
2219					if ($Data [$Key] [$ColName] < $Min) {
2220						$Min = $Data [$Key] [$ColName];
2221						$MinID = $GraphID;
2222					}
2223					$GraphID ++;
2224				}
2225			}
2226
2227			$YPos = $this->GArea_Y2 - (($Max - $this->VMin) * $this->DivisionRatio);
2228			$X1 = floor ( $XPos - $XWidth );
2229			$Y1 = floor ( $YPos ) - .2;
2230			$X2 = floor ( $XPos + $XWidth );
2231			if ($X1 <= $this->GArea_X1) {
2232				$X1 = $this->GArea_X1 + 1;
2233			}
2234			if ($X2 >= $this->GArea_X2) {
2235				$X2 = $this->GArea_X2 - 1;
2236			}
2237
2238			$YPos = $this->GArea_Y2 - (($Min - $this->VMin) * $this->DivisionRatio);
2239			$Y2 = floor ( $YPos ) + .2;
2240
2241			$this->canvas->drawLine(new Point(floor ( $XPos ) - .2, $Y1 + 1),
2242									new Point(floor ( $XPos ) - .2, $Y2 - 1),
2243									$color,
2244									$this->LineWidth,
2245									$this->LineDotSize,
2246									$this->shadowProperties,
2247									$graphAreaMin,
2248									$graphAreaMax);
2249			$this->canvas->drawLine(new Point(floor ( $XPos ) + .2, $Y1 + 1),
2250									new Point(floor ( $XPos ) + .2, $Y2 - 1),
2251									$color,
2252									$this->LineWidth,
2253									$this->LineDotSize,
2254									$this->shadowProperties,
2255									$graphAreaMin,
2256									$graphAreaMax);
2257			$this->canvas->drawLine(new Point($X1,
2258											  $Y1),
2259									new Point($X2,
2260											  $Y1),
2261									$this->palette->colors[$MaxID],
2262									$this->LineWidth,
2263									$this->LineDotSize,
2264									$this->shadowProperties);
2265			$this->canvas->drawLine(new Point($X1,
2266											  $Y2),
2267									new Point($X2,
2268											  $Y2),
2269									$this->palette->colors[$MinID],
2270									$this->LineWidth,
2271									$this->LineDotSize,
2272									$this->shadowProperties);
2273
2274			$XPos = $XPos + $this->DivisionWidth;
2275		}
2276	}
2277
2278	/**
2279	 * This function draw radar axis centered on the graph area
2280	 */
2281	function drawRadarAxis($Data, $DataDescription, $Mosaic = TRUE, $BorderOffset = 10, Color $colorA = null, Color $colorS = null, $MaxValue = -1) {
2282		if ($colorA == null) {
2283			$colorA = new Color(60, 60, 60);
2284		}
2285
2286		if ($colorS == null) {
2287			$colorS = new Color(200, 200, 200);
2288		}
2289
2290		/* Validate the Data and DataDescription array */
2291		$this->validateDataDescription ( "drawRadarAxis", $DataDescription );
2292		$this->validateData ( "drawRadarAxis", $Data );
2293
2294		/* Draw radar axis */
2295		$Points = count ( $Data );
2296		$Radius = ($this->GArea_Y2 - $this->GArea_Y1) / 2 - $BorderOffset;
2297		$XCenter = ($this->GArea_X2 - $this->GArea_X1) / 2 + $this->GArea_X1;
2298		$YCenter = ($this->GArea_Y2 - $this->GArea_Y1) / 2 + $this->GArea_Y1;
2299
2300		/* Search for the max value */
2301		if ($MaxValue == - 1) {
2302			foreach ( $DataDescription->values as $ColName ) {
2303				foreach (array_keys($Data) as $Key) {
2304					if (isset ( $Data [$Key] [$ColName] ))
2305						if ($Data [$Key] [$ColName] > $MaxValue) {
2306							$MaxValue = $Data [$Key] [$ColName];
2307						}
2308				}
2309			}
2310		}
2311
2312		/* Draw the mosaic */
2313		if ($Mosaic) {
2314			$RadiusScale = $Radius / $MaxValue;
2315			for($t = 1; $t <= $MaxValue - 1; $t ++) {
2316				$TRadius = $RadiusScale * $t;
2317				$LastX1 = - 1;
2318
2319				for($i = 0; $i <= $Points; $i ++) {
2320					$Angle = - 90 + $i * 360 / $Points;
2321					$X1 = cos ( $Angle * M_PI / 180 ) * $TRadius + $XCenter;
2322					$Y1 = sin ( $Angle * M_PI / 180 ) * $TRadius + $YCenter;
2323					$X2 = cos ( $Angle * M_PI / 180 ) * ($TRadius + $RadiusScale) + $XCenter;
2324					$Y2 = sin ( $Angle * M_PI / 180 ) * ($TRadius + $RadiusScale) + $YCenter;
2325
2326					if ($t % 2 == 1 && $LastX1 != - 1) {
2327						$Plots = "";
2328						$Plots [] = $X1;
2329						$Plots [] = $Y1;
2330						$Plots [] = $X2;
2331						$Plots [] = $Y2;
2332						$Plots [] = $LastX2;
2333						$Plots [] = $LastY2;
2334						$Plots [] = $LastX1;
2335						$Plots [] = $LastY1;
2336
2337						$this->canvas->drawFilledPolygon($Plots,
2338														 (count ( $Plots ) + 1) / 2,
2339														 new Color(250, 250, 250));
2340					}
2341
2342					$LastX1 = $X1;
2343					$LastY1 = $Y1;
2344					$LastX2 = $X2;
2345					$LastY2 = $Y2;
2346				}
2347			}
2348		}
2349
2350		/* Draw the spider web */
2351		for($t = 1; $t <= $MaxValue; $t ++) {
2352			$TRadius = ($Radius / $MaxValue) * $t;
2353			$LastX = - 1;
2354
2355			for($i = 0; $i <= $Points; $i ++) {
2356				$Angle = - 90 + $i * 360 / $Points;
2357				$X = cos ( $Angle * M_PI / 180 ) * $TRadius + $XCenter;
2358				$Y = sin ( $Angle * M_PI / 180 ) * $TRadius + $YCenter;
2359
2360				if ($LastX != - 1)
2361					$this->canvas->drawDottedLine(new Point($LastX, $LastY),
2362												  new Point($X, $Y),
2363												  4, 1, $colorS,
2364												  $this->shadowProperties);
2365
2366				$LastX = $X;
2367				$LastY = $Y;
2368			}
2369		}
2370
2371		/* Draw the axis */
2372		for($i = 0; $i <= $Points; $i ++) {
2373			$Angle = - 90 + $i * 360 / $Points;
2374			$X = cos ( $Angle * M_PI / 180 ) * $Radius + $XCenter;
2375			$Y = sin ( $Angle * M_PI / 180 ) * $Radius + $YCenter;
2376
2377			$this->canvas->drawLine(new Point($XCenter, $YCenter),
2378									new Point($X, $Y),
2379									$colorA,
2380									$this->LineWidth,
2381									$this->LineDotSize,
2382									$this->shadowProperties);
2383
2384			$XOffset = 0;
2385			$YOffset = 0;
2386			if (isset ( $Data [$i] [$DataDescription->getPosition()] )) {
2387				$Label = $Data [$i] [$DataDescription->getPosition()];
2388
2389				$Positions = imagettfbbox ( $this->FontSize, 0, $this->FontName, $Label );
2390				$Width = $Positions [2] - $Positions [6];
2391				$Height = $Positions [3] - $Positions [7];
2392
2393				if ($Angle >= 0 && $Angle <= 90)
2394					$YOffset = $Height;
2395
2396				if ($Angle > 90 && $Angle <= 180) {
2397					$YOffset = $Height;
2398					$XOffset = - $Width;
2399				}
2400
2401				if ($Angle > 180 && $Angle <= 270) {
2402					$XOffset = - $Width;
2403				}
2404
2405				$this->canvas->drawText($this->FontSize,
2406										0,
2407										new Point($X + $XOffset, $Y + $YOffset),
2408										$colorA,
2409										$this->FontName,
2410										$Label,
2411										ShadowProperties::NoShadow());
2412			}
2413		}
2414
2415		/* Write the values */
2416		for($t = 1; $t <= $MaxValue; $t ++) {
2417			$TRadius = ($Radius / $MaxValue) * $t;
2418
2419			$Angle = - 90 + 360 / $Points;
2420			$X1 = $XCenter;
2421			$Y1 = $YCenter - $TRadius;
2422			$X2 = cos ( $Angle * M_PI / 180 ) * $TRadius + $XCenter;
2423			$Y2 = sin ( $Angle * M_PI / 180 ) * $TRadius + $YCenter;
2424
2425			$XPos = floor ( ($X2 - $X1) / 2 ) + $X1;
2426			$YPos = floor ( ($Y2 - $Y1) / 2 ) + $Y1;
2427
2428			$Positions = imagettfbbox ( $this->FontSize, 0, $this->FontName, $t );
2429			$X = $XPos - ($X + $Positions [2] - $X + $Positions [6]) / 2;
2430			$Y = $YPos + $this->FontSize;
2431
2432			$this->canvas->drawFilledRoundedRectangle(new Point($X + $Positions [6] - 2,
2433																$Y + $Positions [7] - 1),
2434													  new Point($X + $Positions [2] + 4,
2435																$Y + $Positions [3] + 1),
2436													  2,
2437													  new Color(240, 240, 240),
2438													  $this->LineWidth,
2439													  $this->LineDotSize,
2440													  $this->shadowProperties);
2441
2442			$this->canvas->drawRoundedRectangle(new Point($X + $Positions [6] - 2,
2443														  $Y + $Positions [7] - 1),
2444												new Point($X + $Positions [2] + 4,
2445														  $Y + $Positions [3] + 1),
2446												2,
2447												new Color(220, 220, 220),
2448												$this->LineWidth,
2449												$this->LineDotSize,
2450												$this->shadowProperties);
2451			$this->canvas->drawText($this->FontSize,
2452									0,
2453									new Point($X, $Y),
2454									$colorA,
2455									$this->FontName,
2456									$t,
2457									ShadowProperties::NoShadow());
2458		}
2459	}
2460
2461	private function calculateMaxValue($Data, $DataDescription) {
2462		$MaxValue = -1;
2463		foreach ( $DataDescription->values as $ColName ) {
2464			foreach (array_keys($Data) as $Key) {
2465				if (isset ( $Data [$Key] [$ColName] ))
2466					if ($Data [$Key] [$ColName] > $MaxValue && is_numeric($Data[$Key][$ColName])) {
2467						$MaxValue = $Data [$Key] [$ColName];
2468					}
2469			}
2470		}
2471		return $MaxValue;
2472	}
2473
2474	/**
2475	 * This function draw a radar graph centered on the graph area
2476	 */
2477	function drawRadar($Data, $DataDescription, $BorderOffset = 10, $MaxValue = -1) {
2478		/* Validate the Data and DataDescription array */
2479		$this->validateDataDescription ( "drawRadar", $DataDescription );
2480		$this->validateData ( "drawRadar", $Data );
2481
2482		$Points = count ( $Data );
2483		$Radius = ($this->GArea_Y2 - $this->GArea_Y1) / 2 - $BorderOffset;
2484		$XCenter = ($this->GArea_X2 - $this->GArea_X1) / 2 + $this->GArea_X1;
2485		$YCenter = ($this->GArea_Y2 - $this->GArea_Y1) / 2 + $this->GArea_Y1;
2486
2487		/* Search for the max value */
2488		if ($MaxValue == - 1) {
2489			$MaxValue = $this->calculateMaxValue($Data, $DataDescription);
2490		}
2491
2492		$GraphID = 0;
2493		foreach ( $DataDescription->values as $ColName ) {
2494			$ColorID = $DataDescription->getColumnIndex($ColName);
2495
2496			$Angle = - 90;
2497			$XLast = - 1;
2498			foreach (array_keys($Data) as $Key) {
2499				if (isset ( $Data [$Key] [$ColName] )) {
2500					$Value = $Data [$Key] [$ColName];
2501					$Strength = ($Radius / $MaxValue) * $Value;
2502
2503					$XPos = cos ( $Angle * M_PI / 180 ) * $Strength + $XCenter;
2504					$YPos = sin ( $Angle * M_PI / 180 ) * $Strength + $YCenter;
2505
2506					if ($XLast != - 1)
2507						$this->canvas->drawLine(new Point($XLast,
2508														  $YLast),
2509												new Point($XPos,
2510														  $YPos),
2511												$this->palette->colors[$ColorID],
2512												$this->LineWidth,
2513												$this->LineDotSize,
2514												$this->shadowProperties);
2515
2516					if ($XLast == - 1) {
2517						$FirstX = $XPos;
2518						$FirstY = $YPos;
2519					}
2520
2521					$Angle = $Angle + (360 / $Points);
2522					$XLast = $XPos;
2523					$YLast = $YPos;
2524				}
2525			}
2526			$this->canvas->drawLine(new Point($XPos,
2527											  $YPos),
2528									new Point($FirstX,
2529											  $FirstY),
2530									$this->palette->colors[$ColorID],
2531									$this->LineWidth,
2532									$this->LineDotSize,
2533									$this->shadowProperties);
2534			$GraphID ++;
2535		}
2536	}
2537
2538	/**
2539	 * This function draw a radar graph centered on the graph area
2540	 */
2541	function drawFilledRadar($Data, $DataDescription, $Alpha = 50, $BorderOffset = 10, $MaxValue = -1) {
2542		/* Validate the Data and DataDescription array */
2543		$this->validateDataDescription ( "drawFilledRadar", $DataDescription );
2544		$this->validateData ( "drawFilledRadar", $Data );
2545
2546		$Points = count ( $Data );
2547		$Radius = ($this->GArea_Y2 - $this->GArea_Y1) / 2 - $BorderOffset;
2548		$XCenter = ($this->GArea_X2 - $this->GArea_X1) / 2;
2549		$YCenter = ($this->GArea_Y2 - $this->GArea_Y1) / 2;
2550
2551		/* Search for the max value */
2552		if ($MaxValue == - 1) {
2553			$MaxValue = $this->calculateMaxValue($Data, $DataDescription);
2554		}
2555
2556		$GraphID = 0;
2557		foreach ( $DataDescription->values as $ColName ) {
2558			$ColorID = $DataDescription->getColumnIndex($ColName);
2559
2560			$Angle = - 90;
2561			$XLast = - 1;
2562			$Plots = array();
2563			foreach (array_keys($Data) as $Key) {
2564				if (isset ( $Data [$Key] [$ColName] )) {
2565					$Value = $Data [$Key] [$ColName];
2566					if (! is_numeric ( $Value )) {
2567						$Value = 0;
2568					}
2569					$Strength = ($Radius / $MaxValue) * $Value;
2570
2571					$XPos = cos ( $Angle * M_PI / 180 ) * $Strength + $XCenter;
2572					$YPos = sin ( $Angle * M_PI / 180 ) * $Strength + $YCenter;
2573
2574					$Plots [] = $XPos + $this->GArea_X1;
2575					$Plots [] = $YPos + $this->GArea_Y1;
2576
2577					$Angle = $Angle + (360 / $Points);
2578					$XLast = $XPos;
2579				}
2580			}
2581
2582			if (isset ( $Plots [0] )) {
2583				$Plots [] = $Plots [0];
2584				$Plots [] = $Plots [1];
2585
2586				$this->canvas->drawFilledPolygon($Plots,
2587												 (count ( $Plots ) + 1) / 2,
2588												 $this->palette->colors[$ColorID],
2589												 $Alpha);
2590
2591				for($i = 0; $i <= count ( $Plots ) - 4; $i = $i + 2)
2592					$this->canvas->drawLine(new Point($Plots [$i],
2593													  $Plots [$i + 1]),
2594											new Point($Plots [$i + 2],
2595													  $Plots [$i + 3]),
2596											$this->palette->colors[$ColorID],
2597											$this->LineWidth,
2598											$this->LineDotSize,
2599											$this->shadowProperties);
2600			}
2601
2602			$GraphID ++;
2603		}
2604	}
2605
2606	/**
2607	 * This function can be used to set the background color
2608	 */
2609	function drawBackground(Color $color) {
2610		$C_Background = $this->canvas->allocateColor($color);
2611		imagefilledrectangle ( $this->canvas->getPicture(), 0, 0, $this->XSize, $this->YSize, $C_Background );
2612	}
2613
2614	private function drawGradient(Point $point1, Point $point2, Color $color, $decay) {
2615		/* Positive gradient */
2616		if ($decay > 0) {
2617			$YStep = ($point2->getY() - $point1->getY() - 2) / $decay;
2618			for($i = 0; $i <= $decay; $i ++) {
2619				$color = $color->addRGBIncrement(-1);
2620				$Yi1 = $point1->getY() + ($i * $YStep);
2621				$Yi2 = ceil ( $Yi1 + ($i * $YStep) + $YStep );
2622				if ($Yi2 >= $Yi2) {
2623					$Yi2 = $point2->getY() - 1;
2624				}
2625
2626				$this->canvas->drawFilledRectangle(new Point($point1->getX(), $Yi1),
2627												   new Point($point2->getX(), $Yi2),
2628												   $color,
2629												   ShadowProperties::NoShadow());
2630			}
2631		}
2632
2633		/* Negative gradient */
2634		if ($decay < 0) {
2635			$YStep = ($point2->getY() - $point1->getY() - 2) / - $decay;
2636			$Yi1 = $point1->getY();
2637			$Yi2 = $point1->getY() + $YStep;
2638			for($i = - $decay; $i >= 0; $i --) {
2639				$color = $color->addRGBIncrement(1);
2640
2641				$this->canvas->drawFilledRectangle(new Point($point1->getX(), $Yi1),
2642												   new Point($point2->getX(), $Yi2),
2643												   $color,
2644												   ShadowProperties::NoShadow());
2645
2646				$Yi1 += $YStep;
2647				$Yi2 += $YStep;
2648				if ($Yi2 >= $Yi2) {
2649					$Yi2 = $point2->getY() - 1;
2650				}
2651			}
2652		}
2653	}
2654
2655	/**
2656	 * This function can be used to set the background color
2657	 */
2658	private function drawGraphAreaGradient(BackgroundStyle $style) {
2659		if (!$style->useGradient()) {
2660			return;
2661		}
2662
2663		$this->drawGradient(new Point($this->GArea_X1 + 1, $this->GArea_Y1 + 1),
2664							new Point($this->GArea_X2 - 1, $this->GArea_Y2),
2665							$style->getGradientStartColor(),
2666							$style->getGradientDecay());
2667	}
2668
2669	public function drawBackgroundGradient(Color $color, $decay) {
2670		$this->drawGradient(new Point(0, 0),
2671							new Point($this->XSize, $this->YSize),
2672							$color,
2673							$decay);
2674	}
2675
2676	/**
2677	 * This function will draw an ellipse
2678	 */
2679	function drawEllipse($Xc, $Yc, $Height, $Width, Color $color) {
2680		$this->canvas->drawCircle(new Point($Xc, $Yc), $Height, $color, $this->shadowProperties, $Width );
2681	}
2682
2683	/**
2684	 * This function will draw a filled ellipse
2685	 */
2686	function drawFilledEllipse($Xc, $Yc, $Height, $Width, Color $color) {
2687		$this->canvas->drawFilledCircle(new Point($Xc, $Yc), $Height, $color, $this->shadowProperties, $Width );
2688	}
2689
2690	/**
2691	 * Load a PNG file and draw it over the chart
2692	 */
2693	function drawFromPNG($FileName, $X, $Y, $Alpha = 100) {
2694		$this->drawFromPicture ( 1, $FileName, $X, $Y, $Alpha );
2695	}
2696
2697	/**
2698	 * Load a GIF file and draw it over the chart
2699	 */
2700	function drawFromGIF($FileName, $X, $Y, $Alpha = 100) {
2701		$this->drawFromPicture ( 2, $FileName, $X, $Y, $Alpha );
2702	}
2703
2704	/**
2705	 * Load a JPEG file and draw it over the chart
2706	 */
2707	function drawFromJPG($FileName, $X, $Y, $Alpha = 100) {
2708		$this->drawFromPicture ( 3, $FileName, $X, $Y, $Alpha );
2709	}
2710
2711	/**
2712	 * Generic loader function for external pictures
2713	 */
2714	function drawFromPicture($PicType, $FileName, $X, $Y, $Alpha = 100) {
2715		if (file_exists ( $FileName )) {
2716			$Infos = getimagesize ( $FileName );
2717			$Width = $Infos [0];
2718			$Height = $Infos [1];
2719			if ($PicType == 1) {
2720				$Raster = imagecreatefrompng ( $FileName );
2721			}
2722			if ($PicType == 2) {
2723				$Raster = imagecreatefromgif ( $FileName );
2724			}
2725			if ($PicType == 3) {
2726				$Raster = imagecreatefromjpeg ( $FileName );
2727			}
2728
2729			imagecopymerge ( $this->canvas->getPicture(), $Raster, $X, $Y, 0, 0, $Width, $Height, $Alpha );
2730			imagedestroy ( $Raster );
2731		}
2732	}
2733
2734	/**
2735	 * Add a border to the picture
2736	 *
2737	 * @todo This hasn't been updated to the new API yet
2738	 */
2739	function addBorder($Size = 3, $R = 0, $G = 0, $B = 0) {
2740		$Width = $this->XSize + 2 * $Size;
2741		$Height = $this->YSize + 2 * $Size;
2742
2743		$Resampled = imagecreatetruecolor ( $Width, $Height );
2744		$C_Background = imagecolorallocate($Resampled, $R, $G, $B);
2745		imagefilledrectangle ( $Resampled, 0, 0, $Width, $Height, $C_Background );
2746
2747		imagecopy ( $Resampled, $this->canvas->getPicture(), $Size, $Size, 0, 0, $this->XSize, $this->YSize );
2748		imagedestroy ( $this->canvas->getPicture() );
2749
2750		$this->XSize = $Width;
2751		$this->YSize = $Height;
2752
2753		$this->canvas->setPicture(imagecreatetruecolor ( $this->XSize, $this->YSize ));
2754		$C_White = $this->canvas->allocate(new Color(255, 255, 255));
2755		imagefilledrectangle ( $this->canvas->getPicture(), 0, 0, $this->XSize, $this->YSize, $C_White );
2756		imagecolortransparent ( $this->canvas->getPicture(), $C_White );
2757		imagecopy ( $this->canvas->getPicture(), $Resampled, 0, 0, 0, 0, $this->XSize, $this->YSize );
2758	}
2759
2760	/**
2761	 * Render the current picture to a file
2762	 */
2763	function Render($FileName) {
2764		if ($this->ErrorReporting)
2765			$this->printErrors ( $this->ErrorInterface );
2766
2767		/* Save image map if requested */
2768		if ($this->BuildMap)
2769			$this->SaveImageMap ();
2770
2771		imagepng ( $this->canvas->getPicture(), $FileName );
2772	}
2773
2774	/**
2775	 * Render the current picture to STDOUT
2776	 */
2777	function Stroke() {
2778		if ($this->ErrorReporting)
2779			$this->printErrors ( "GD" );
2780
2781		/* Save image map if requested */
2782		if ($this->BuildMap)
2783			$this->SaveImageMap ();
2784
2785		header ( 'Content-type: image/png' );
2786		imagepng ( $this->canvas->getPicture() );
2787	}
2788
2789	/**
2790	 * Validate data contained in the description array
2791	 *
2792	 * @todo Should this be a method on DataDescription?
2793	 */
2794	protected function validateDataDescription($FunctionName, DataDescription &$DataDescription, $DescriptionRequired = TRUE) {
2795		if ($DataDescription->getPosition() == '') {
2796			$this->Errors [] = "[Warning] " . $FunctionName . " - Y Labels are not set.";
2797			$DataDescription->setPosition("Name");
2798		}
2799
2800		if ($DescriptionRequired) {
2801			if (! isset ( $DataDescription->description)) {
2802				$this->Errors [] = "[Warning] " . $FunctionName . " - Series descriptions are not set.";
2803				foreach ( $DataDescription->values as $key => $Value ) {
2804					$DataDescription->description[$Value] = $Value;
2805				}
2806			}
2807
2808			if (count ( $DataDescription->description) < count ( $DataDescription->values )) {
2809				$this->Errors [] = "[Warning] " . $FunctionName . " - Some series descriptions are not set.";
2810				foreach ( $DataDescription->values as $key => $Value ) {
2811					if (! isset ( $DataDescription->description[$Value] ))
2812						$DataDescription->description[$Value] = $Value;
2813				}
2814			}
2815		}
2816	}
2817
2818	/**
2819	 * Validate data contained in the data array
2820	 */
2821	protected function validateData($FunctionName, &$Data) {
2822		$DataSummary = array ();
2823
2824		foreach ( $Data as $key => $Values ) {
2825			foreach ( $Values as $key2 => $Value ) {
2826				if (! isset ( $DataSummary [$key2] ))
2827					$DataSummary [$key2] = 1;
2828				else
2829					$DataSummary [$key2] ++;
2830			}
2831		}
2832
2833		if (empty($DataSummary))
2834			$this->Errors [] = "[Warning] " . $FunctionName . " - No data set.";
2835
2836		foreach ( $DataSummary as $key => $Value ) {
2837			if ($Value < max ( $DataSummary )) {
2838				$this->Errors [] = "[Warning] " . $FunctionName . " - Missing data in serie " . $key . ".";
2839			}
2840		}
2841	}
2842
2843	/**
2844	 * Print all error messages on the CLI or graphically
2845	 */
2846	function printErrors($Mode = "CLI") {
2847		if (count ( $this->Errors ) == 0)
2848			return (0);
2849
2850		if ($Mode == "CLI") {
2851			foreach ( $this->Errors as $key => $Value )
2852				echo $Value . "\r\n";
2853		} elseif ($Mode == "GD") {
2854			$MaxWidth = 0;
2855			foreach ( $this->Errors as $key => $Value ) {
2856				$Position = imageftbbox ( $this->ErrorFontSize, 0, $this->ErrorFontName, $Value );
2857				$TextWidth = $Position [2] - $Position [0];
2858				if ($TextWidth > $MaxWidth) {
2859					$MaxWidth = $TextWidth;
2860				}
2861			}
2862			$this->canvas->drawFilledRoundedRectangle(new Point($this->XSize - ($MaxWidth + 20),
2863																$this->YSize - (20 + (($this->ErrorFontSize + 4) * count ( $this->Errors )))),
2864													  new Point($this->XSize - 10,
2865																$this->YSize - 10),
2866													  6,
2867													  new Color(233, 185, 185),
2868													  $this->lineWidth,
2869													  $this->lineDotSize,
2870													  $this->shadowProperties);
2871
2872			$this->canvas->drawRoundedRectangle(new Point($this->XSize - ($MaxWidth + 20),
2873														  $this->YSize - (20 + (($this->ErrorFontSize + 4) * count ( $this->Errors )))),
2874												new Point($this->XSize - 10,
2875														  $this->YSize - 10),
2876												6,
2877												new Color(193, 145, 145),
2878												$this->LineWidth,
2879												$this->LineDotSize,
2880												$this->shadowProperties);
2881
2882			$YPos = $this->YSize - (18 + (count ( $this->Errors ) - 1) * ($this->ErrorFontSize + 4));
2883			foreach ( $this->Errors as $key => $Value ) {
2884				$this->canvas->drawText($this->ErrorFontSize,
2885										0,
2886										new Point($this->XSize - ($MaxWidth + 15),
2887												  $YPos),
2888										new Color(133, 85, 85),
2889										$this->ErrorFontName,
2890										$Value,
2891										ShadowProperties::NoShadow());
2892				$YPos = $YPos + ($this->ErrorFontSize + 4);
2893			}
2894		}
2895	}
2896
2897	/**
2898	 * Activate the image map creation process
2899	 */
2900	function setImageMap($Mode = TRUE, $GraphID = "MyGraph") {
2901		$this->BuildMap = $Mode;
2902		$this->MapID = $GraphID;
2903	}
2904
2905	/**
2906	 * Add a box into the image map
2907	 */
2908	function addToImageMap($X1, $Y1, $X2, $Y2, $SerieName, $Value, $CallerFunction) {
2909		if ($this->MapFunction == NULL || $this->MapFunction == $CallerFunction) {
2910			$this->ImageMap [] = round ( $X1 ) . "," . round ( $Y1 ) . "," . round ( $X2 ) . "," . round ( $Y2 ) . "," . $SerieName . "," . $Value;
2911			$this->MapFunction = $CallerFunction;
2912		}
2913	}
2914
2915	/**
2916	 * Load and cleanup the image map from disk
2917	 */
2918	function getImageMap($MapName, $Flush = TRUE) {
2919		/* Strip HTML query strings */
2920		$Values = $this->tmpFolder . $MapName;
2921		$Value = explode ( "\?", $Values );
2922		$FileName = $Value [0];
2923
2924		if (file_exists ( $FileName )) {
2925			$Handle = fopen ( $FileName, "r" );
2926			$MapContent = fread ( $Handle, filesize ( $FileName ) );
2927			fclose ( $Handle );
2928			echo $MapContent;
2929
2930			if ($Flush)
2931				unlink ( $FileName );
2932
2933			exit ();
2934		} else {
2935			header ( "HTTP/1.0 404 Not Found" );
2936			exit ();
2937		}
2938	}
2939
2940	/**
2941	 * Save the image map to the disk
2942	 */
2943	function SaveImageMap() {
2944		if (! $this->BuildMap) {
2945			return (- 1);
2946		}
2947
2948		if ($this->ImageMap == NULL) {
2949			$this->Errors [] = "[Warning] SaveImageMap - Image map is empty.";
2950			return (- 1);
2951		}
2952
2953		$Handle = fopen ( $this->tmpFolder . $this->MapID, 'w' );
2954		if (! $Handle) {
2955			$this->Errors [] = "[Warning] SaveImageMap - Cannot save the image map.";
2956			return (- 1);
2957		} else {
2958			foreach ( $this->ImageMap as $Value )
2959				fwrite ( $Handle, htmlentities ( $Value ) . "\r" );
2960		}
2961		fclose ( $Handle );
2962	}
2963
2964	/**
2965	 * Set date format for axis labels
2966	 */
2967	function setDateFormat($Format) {
2968		$this->DateFormat = $Format;
2969	}
2970
2971	/**
2972	 * Convert TS to a date format string
2973	 */
2974	function ToDate($Value) {
2975		return (date ( $this->DateFormat, $Value ));
2976	}
2977
2978	/**
2979	 * Check if a number is a full integer (for scaling)
2980	 */
2981	static private function isRealInt($Value) {
2982		if ($Value == floor ( $Value ))
2983			return (TRUE);
2984		return (FALSE);
2985	}
2986
2987	/**
2988	 * @todo I don't know what this does yet, I'm refactoring...
2989	 */
2990	public function calculateScales(& $Scale, & $Divisions) {
2991		/* Compute automatic scaling */
2992		$ScaleOk = FALSE;
2993		$Factor = 1;
2994		$MinDivHeight = 25;
2995		$MaxDivs = ($this->GArea_Y2 - $this->GArea_Y1) / $MinDivHeight;
2996
2997		if ($this->VMax <= $this->VMin) {
2998			throw new Exception("Impossible to calculate scales when VMax <= VMin");
2999		}
3000
3001		if ($this->VMin == 0 && $this->VMax == 0) {
3002			$this->VMin = 0;
3003			$this->VMax = 2;
3004			$Scale = 1;
3005			$Divisions = 2;
3006		} elseif ($MaxDivs > 1) {
3007			while ( ! $ScaleOk ) {
3008				$Scale1 = ($this->VMax - $this->VMin) / $Factor;
3009				$Scale2 = ($this->VMax - $this->VMin) / $Factor / 2;
3010
3011				if ($Scale1 > 1 && $Scale1 <= $MaxDivs && ! $ScaleOk) {
3012					$ScaleOk = TRUE;
3013					$Divisions = floor ( $Scale1 );
3014					$Scale = 1;
3015				}
3016				if ($Scale2 > 1 && $Scale2 <= $MaxDivs && ! $ScaleOk) {
3017					$ScaleOk = TRUE;
3018					$Divisions = floor ( $Scale2 );
3019					$Scale = 2;
3020				}
3021				if (! $ScaleOk) {
3022					if ($Scale2 > 1) {
3023						$Factor = $Factor * 10;
3024					}
3025					if ($Scale2 < 1) {
3026						$Factor = $Factor / 10;
3027					}
3028				}
3029			}
3030
3031			if (floor ( $this->VMax / $Scale / $Factor ) != $this->VMax / $Scale / $Factor) {
3032				$GridID = floor ( $this->VMax / $Scale / $Factor ) + 1;
3033				$this->VMax = $GridID * $Scale * $Factor;
3034				$Divisions ++;
3035			}
3036
3037			if (floor ( $this->VMin / $Scale / $Factor ) != $this->VMin / $Scale / $Factor) {
3038				$GridID = floor ( $this->VMin / $Scale / $Factor );
3039				$this->VMin = $GridID * $Scale * $Factor;
3040				$Divisions ++;
3041			}
3042		} else /* Can occur for small graphs */
3043			  $Scale = 1;
3044	}
3045
3046        /*
3047         * Returns the resource
3048         */
3049        public function getPicture() {
3050            return $this->canvas->getPicture();
3051        }
3052
3053	static private function computeAutomaticScaling($minCoord, $maxCoord, &$minVal, &$maxVal, &$Divisions) {
3054		$ScaleOk = FALSE;
3055		$Factor = 1.0;
3056		$MinDivHeight = 25;
3057		$MaxDivs = ($maxCoord - $minCoord) / $MinDivHeight;
3058
3059		if ($minVal == 0 && $maxVal == 0){
3060			$minVal = 0;
3061			$maxVal = 2;
3062			$Scale = 1;
3063			$Divisions = 2;
3064		}elseif ($minVal == $maxVal){
3065			$minVal = 0;
3066			$maxVal = $maxVal*2;
3067			$Scale = 1;
3068			$Divisions = 2;
3069		} elseif ($MaxDivs > 1) {
3070			while ( ! $ScaleOk ) {
3071                if($Factor == 0) { die(); }
3072				$Scale1 = ($maxVal - $minVal) / $Factor;
3073				$Scale2 = ($maxVal - $minVal) / $Factor / 2;
3074
3075				if ($Scale1 > 1 && $Scale1 <= $MaxDivs && ! $ScaleOk) {
3076					$ScaleOk = TRUE;
3077					$Divisions = floor ( $Scale1 );
3078					$Scale = 1;
3079				}
3080				if ($Scale2 > 1 && $Scale2 <= $MaxDivs && ! $ScaleOk) {
3081					$ScaleOk = TRUE;
3082					$Divisions = floor ( $Scale2 );
3083					$Scale = 2;
3084				}
3085				if (! $ScaleOk) {
3086					if ($Scale2 > 1) {
3087						$Factor = $Factor * 10;
3088					}
3089					if ($Scale2 < 1) {
3090						$Factor = $Factor / 10;
3091					}
3092				}
3093			}
3094
3095			if (floor ( $maxVal / $Scale / $Factor ) != $maxVal / $Scale / $Factor) {
3096				$GridID = floor ( $maxVal / $Scale / $Factor ) + 1;
3097				$maxVal = $GridID * $Scale * $Factor;
3098				$Divisions ++;
3099			}
3100
3101			if (floor ( $minVal / $Scale / $Factor ) != $minVal / $Scale / $Factor) {
3102				$GridID = floor ( $minVal / $Scale / $Factor );
3103				$minVal = $GridID * $Scale * $Factor;
3104				$Divisions ++;
3105			}
3106		} else /* Can occurs for small graphs */
3107			  $Scale = 1;
3108
3109		if (! isset ( $Divisions ))
3110			$Divisions = 2;
3111
3112		if (self::isRealInt ( ($maxVal - $minVal) / ($Divisions - 1) ))
3113			$Divisions --;
3114		elseif (self::isRealInt ( ($maxVal - $minVal) / ($Divisions + 1) ))
3115			$Divisions ++;
3116	}
3117
3118	static private function convertValueForDisplay($value, $format, $unit) {
3119		if ($format == "number")
3120			return $value . $unit;
3121		if ($format == "time")
3122			return ConversionHelpers::ToTime ( $value );
3123		if ($format == "date")
3124			return $this->ToDate ( $value );
3125		if ($format == "metric")
3126			return ConversionHelpers::ToMetric ( $value );
3127		if ($format == "currency")
3128			return ConversionHelpers::ToCurrency ( $value );
3129	}
3130}
3131
3132/**
3133 *
3134 * @param $Message
3135 */
3136function RaiseFatal($Message) {
3137	echo "[FATAL] " . $Message . "\r\n";
3138	exit ();
3139}
3140?>
3141