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