1<?php
2
3namespace CpChart;
4
5use Exception;
6
7/**
8 * This class exists only to try and reduce the number of methods and properties
9 * in the Draw class. Basically all methods not named 'drawX' were moved in here,
10 * as well as all the class fields.
11 */
12abstract class BaseDraw
13{
14    /**
15     * Width of the picture
16     * @var int
17     */
18    public $XSize;
19
20    /**
21     * Height of the picture
22     * @var int
23     */
24    public $YSize;
25
26    /**
27     * GD picture object
28     * @var resource
29     */
30    public $Picture;
31
32    /**
33     * Turn antialias on or off
34     * @var boolean
35     */
36    public $Antialias = true;
37
38    /**
39     * Quality of the antialiasing implementation (0-1)
40     * @var int
41     */
42    public $AntialiasQuality = 0;
43
44    /**
45     * Already drawn pixels mask (Filled circle implementation)
46     * @var array
47     */
48    public $Mask = [];
49
50    /**
51     * Just to know if we need to flush the alpha channels when rendering
52     * @var boolean
53     */
54    public $TransparentBackground = false;
55
56    /**
57     * Graph area X origin
58     * @var int
59     */
60    public $GraphAreaX1;
61
62    /**
63     * Graph area Y origin
64     * @var int
65     */
66    public $GraphAreaY1;
67
68    /**
69     * Graph area bottom right X position
70     * @var int
71     */
72    public $GraphAreaX2;
73
74    /**
75     * Graph area bottom right Y position
76     * @var int
77     */
78    public $GraphAreaY2;
79
80    /**
81     * Minimum height for scale divs
82     * @var int
83     */
84    public $ScaleMinDivHeight = 20;
85
86    /**
87     * @var string
88     */
89    public $FontName = "GeosansLight.ttf";
90
91    /**
92     * @var int
93     */
94    public $FontSize = 12;
95
96    /**
97     * Return the bounding box of the last written string
98     * @var array
99     */
100    public $FontBox;
101
102    /**
103     * @var int
104     */
105    public $FontColorR = 0;
106
107    /**
108     * @var int
109     */
110    public $FontColorG = 0;
111
112    /**
113     * @var int
114     */
115    public $FontColorB = 0;
116
117    /**
118     * @var int
119     */
120    public $FontColorA = 100;
121
122    /**
123     * Turn shadows on or off
124     * @var boolean
125     */
126    public $Shadow = false;
127
128    /**
129     * X Offset of the shadow
130     * @var int
131     */
132    public $ShadowX;
133
134    /**
135     * Y Offset of the shadow
136     * @var int
137     */
138    public $ShadowY;
139
140    /**
141     * R component of the shadow
142     * @var int
143     */
144    public $ShadowR;
145
146    /**
147     * G component of the shadow
148     * @var int
149     */
150    public $ShadowG;
151
152    /**
153     * B component of the shadow
154     * @var int
155     */
156    public $ShadowB;
157
158    /**
159     * Alpha level of the shadow
160     * @var int
161     */
162    public $Shadowa;
163
164    /**
165     * Array containing the image map
166     * @var array
167     */
168    public $ImageMap = [];
169
170    /**
171     * Name of the session array
172     * @var int
173     */
174    public $ImageMapIndex = "pChart";
175
176    /**
177     * Save the current imagemap storage mode
178     * @var int
179     */
180    public $ImageMapStorageMode;
181
182    /**
183     * Automatic deletion of the image map temp files
184     * @var boolean
185     */
186    public $ImageMapAutoDelete = true;
187
188    /**
189     * Attached dataset
190     * @var Data
191     */
192    public $DataSet;
193
194    /**
195     * Last generated chart info
196     * Last layout : regular or stacked
197     * @var int
198     */
199    public $LastChartLayout = CHART_LAST_LAYOUT_REGULAR;
200
201    /**
202     * @var string
203     */
204    private $resourcePath;
205
206    public function __construct()
207    {
208        $this->resourcePath = sprintf('%s/../resources', __DIR__);
209        $this->FontName = $this->loadFont($this->FontName, 'fonts');
210    }
211
212    /**
213     * Set the path to the folder containing library resources (fonts, data, palettes).
214     *
215     * @param string $path
216     * @throws Exception
217     */
218    public function setResourcePath($path)
219    {
220        $escapedPath = rtrim($path, '/');
221        if (!file_exists($escapedPath)) {
222            throw new Exception(sprintf(
223                "The path '%s' to resources' folder does not exist!",
224                $escapedPath
225            ));
226        }
227
228        $this->resourcePath = $escapedPath;
229    }
230
231    /**
232     * Check if requested resource exists and return the path to it if yes.
233     * @param string $name
234     * @param string $type
235     * @return string
236     * @throws Exception
237     */
238    protected function loadFont($name, $type)
239    {
240        if (file_exists($name)) {
241            return $name;
242        }
243
244        $path = sprintf('%s/%s/%s', $this->resourcePath, $type, $name);
245        if (file_exists($path)) {
246            return $path;
247        }
248
249        throw new Exception(
250            sprintf('The requested resource %s (%s) has not been found!', $name, $type)
251        );
252    }
253
254    /**
255     * Allocate a color with transparency
256     * @param resource $Picture
257     * @param int $R
258     * @param int $G
259     * @param int $B
260     * @param int $Alpha
261     * @return int
262     */
263    public function allocateColor($Picture, $R, $G, $B, $Alpha = 100)
264    {
265        if ($R < 0) {
266            $R = 0;
267        } if ($R > 255) {
268            $R = 255;
269        }
270        if ($G < 0) {
271            $G = 0;
272        } if ($G > 255) {
273            $G = 255;
274        }
275        if ($B < 0) {
276            $B = 0;
277        } if ($B > 255) {
278            $B = 255;
279        }
280        if ($Alpha < 0) {
281            $Alpha = 0;
282        }
283        if ($Alpha > 100) {
284            $Alpha = 100;
285        }
286
287        $Alpha = $this->convertAlpha($Alpha);
288        return imagecolorallocatealpha($Picture, $R, $G, $B, $Alpha);
289    }
290
291    /**
292     * Convert apha to base 10
293     * @param int|float $AlphaValue
294     * @return integer
295     */
296    public function convertAlpha($AlphaValue)
297    {
298        return (127 / 100) * (100 - $AlphaValue);
299    }
300
301    /**
302     * @param string $FileName
303     * @return array
304     */
305    public function getPicInfo($FileName)
306    {
307        $Infos = getimagesize($FileName);
308        $Width = $Infos[0];
309        $Height = $Infos[1];
310        $Type = $Infos["mime"];
311
312        if ($Type == "image/png") {
313            $Type = 1;
314        }
315        if ($Type == "image/gif") {
316            $Type = 2;
317        }
318        if ($Type == "image/jpeg ") {
319            $Type = 3;
320        }
321
322        return [$Width, $Height, $Type];
323    }
324
325    /**
326     * Compute the scale, check for the best visual factors
327     * @param int $XMin
328     * @param int $XMax
329     * @param int $MaxDivs
330     * @param array $Factors
331     * @param int $AxisID
332     * @return mixed
333     */
334    public function computeScale($XMin, $XMax, $MaxDivs, array $Factors, $AxisID = 0)
335    {
336        /* Compute each factors */
337        $Results = [];
338        foreach ($Factors as $Key => $Factor) {
339            $Results[$Factor] = $this->processScale($XMin, $XMax, $MaxDivs, [$Factor], $AxisID);
340        }
341        /* Remove scales that are creating to much decimals */
342        $GoodScaleFactors = [];
343        foreach ($Results as $Key => $Result) {
344            $Decimals = preg_split("/\./", $Result["RowHeight"]);
345            if ((!isset($Decimals[1])) || (strlen($Decimals[1]) < 6)) {
346                $GoodScaleFactors[] = $Key;
347            }
348        }
349
350        /* Found no correct scale, shame,... returns the 1st one as default */
351        if (!count($GoodScaleFactors)) {
352            return $Results[$Factors[0]];
353        }
354
355        /* Find the factor that cause the maximum number of Rows */
356        $MaxRows = 0;
357        $BestFactor = 0;
358        foreach ($GoodScaleFactors as $Key => $Factor) {
359            if ($Results[$Factor]["Rows"] > $MaxRows) {
360                $MaxRows = $Results[$Factor]["Rows"];
361                $BestFactor = $Factor;
362            }
363        }
364
365        /* Return the best visual scale */
366        return $Results[$BestFactor];
367    }
368
369    /**
370     * Compute the best matching scale based on size & factors
371     * @param int $XMin
372     * @param int $XMax
373     * @param int $MaxDivs
374     * @param array $Factors
375     * @param int $AxisID
376     * @return array
377     */
378    public function processScale($XMin, $XMax, $MaxDivs, array $Factors, $AxisID)
379    {
380        $ScaleHeight = abs(ceil($XMax) - floor($XMin));
381
382        $Format = null;
383        if (isset($this->DataSet->Data["Axis"][$AxisID]["Format"])) {
384            $Format = $this->DataSet->Data["Axis"][$AxisID]["Format"];
385        }
386
387        $Mode = AXIS_FORMAT_DEFAULT;
388        if (isset($this->DataSet->Data["Axis"][$AxisID]["Display"])) {
389            $Mode = $this->DataSet->Data["Axis"][$AxisID]["Display"];
390        }
391
392        $Scale = [];
393        if ($XMin != $XMax) {
394            $Found = false;
395            $Rescaled = false;
396            $Scaled10Factor = .0001;
397            $Result = 0;
398            while (!$Found) {
399                foreach ($Factors as $Key => $Factor) {
400                    if (!$Found) {
401                        $XMinRescaled = $XMin;
402                        if (!($this->modulo($XMin, $Factor * $Scaled10Factor) == 0)
403                            || ($XMin != floor($XMin))
404                        ) {
405                            $XMinRescaled = floor($XMin / ($Factor * $Scaled10Factor))
406                                * $Factor
407                                * $Scaled10Factor
408                            ;
409                        }
410
411                        $XMaxRescaled = $XMax;
412                        if (!($this->modulo($XMax, $Factor * $Scaled10Factor) == 0)
413                            || ($XMax != floor($XMax))
414                        ) {
415                            $XMaxRescaled = floor($XMax / ($Factor * $Scaled10Factor))
416                                * $Factor
417                                * $Scaled10Factor
418                                + ($Factor * $Scaled10Factor)
419                            ;
420                        }
421
422                        $ScaleHeightRescaled = abs($XMaxRescaled - $XMinRescaled);
423
424                        if (!$Found
425                            && floor($ScaleHeightRescaled / ($Factor * $Scaled10Factor)) <= $MaxDivs
426                        ) {
427                            $Found = true;
428                            $Rescaled = true;
429                            $Result = $Factor * $Scaled10Factor;
430                        }
431                    }
432                }
433                $Scaled10Factor = $Scaled10Factor * 10;
434            }
435
436            /* ReCall Min / Max / Height */
437            if ($Rescaled) {
438                $XMin = $XMinRescaled;
439                $XMax = $XMaxRescaled;
440                $ScaleHeight = $ScaleHeightRescaled;
441            }
442
443            /* Compute rows size */
444            $Rows = floor($ScaleHeight / $Result);
445            if ($Rows == 0) {
446                $Rows = 1;
447            }
448            $RowHeight = $ScaleHeight / $Rows;
449
450            /* Return the results */
451            $Scale["Rows"] = $Rows;
452            $Scale["RowHeight"] = $RowHeight;
453            $Scale["XMin"] = $XMin;
454            $Scale["XMax"] = $XMax;
455
456            /* Compute the needed decimals for the metric view to avoid repetition of the same X Axis labels */
457            if ($Mode == AXIS_FORMAT_METRIC && $Format == null) {
458                $Done = false;
459                $GoodDecimals = 0;
460                for ($Decimals = 0; $Decimals <= 10; $Decimals++) {
461                    if (!$Done) {
462                        $LastLabel = "zob";
463                        $ScaleOK = true;
464                        for ($i = 0; $i <= $Rows; $i++) {
465                            $Value = $XMin + $i * $RowHeight;
466                            $Label = $this->scaleFormat($Value, AXIS_FORMAT_METRIC, $Decimals);
467
468                            if ($LastLabel == $Label) {
469                                $ScaleOK = false;
470                            }
471                            $LastLabel = $Label;
472                        }
473                        if ($ScaleOK) {
474                            $Done = true;
475                            $GoodDecimals = $Decimals;
476                        }
477                    }
478                }
479                $Scale["Format"] = $GoodDecimals;
480            }
481        } else {
482            /* If all values are the same we keep a +1/-1 scale */
483            $Rows = 2;
484            $XMin = $XMax - 1;
485            $XMax = $XMax + 1;
486            $RowHeight = 1;
487
488            /* Return the results */
489            $Scale["Rows"] = $Rows;
490            $Scale["RowHeight"] = $RowHeight;
491            $Scale["XMin"] = $XMin;
492            $Scale["XMax"] = $XMax;
493        }
494
495        return $Scale;
496    }
497
498    /**
499     *
500     * @param int|float $Value1
501     * @param int|float $Value2
502     * @return double
503     */
504    public function modulo($Value1, $Value2)
505    {
506        if (floor($Value2) == 0) {
507            return 0;
508        }
509        if (floor($Value2) != 0) {
510            return $Value1 % $Value2;
511        }
512
513        $MinValue = min($Value1, $Value2);
514        $Factor = 10;
515        while (floor($MinValue * $Factor) == 0) {
516            $Factor = $Factor * 10;
517        }
518
519        return ($Value1 * $Factor) % ($Value2 * $Factor);
520    }
521
522    /**
523     * @param mixed $Value
524     * @param mixed $LastValue
525     * @param integer $LabelingMethod
526     * @param integer $ID
527     * @param boolean $LabelSkip
528     * @return boolean
529     */
530    public function isValidLabel($Value, $LastValue, $LabelingMethod, $ID, $LabelSkip)
531    {
532        if ($LabelingMethod == LABELING_DIFFERENT && $Value != $LastValue) {
533            return true;
534        }
535        if ($LabelingMethod == LABELING_DIFFERENT && $Value == $LastValue) {
536            return false;
537        }
538        if ($LabelingMethod == LABELING_ALL && $LabelSkip == 0) {
539            return true;
540        }
541        if ($LabelingMethod == LABELING_ALL && ($ID + $LabelSkip) % ($LabelSkip + 1) != 1) {
542            return false;
543        }
544
545        return true;
546    }
547
548    /**
549     * Returns the number of drawable series
550     * @return int
551     */
552    public function countDrawableSeries()
553    {
554        $count = 0;
555        $Data = $this->DataSet->getData();
556
557        foreach ($Data["Series"] as $SerieName => $Serie) {
558            if ($Serie["isDrawable"] == true && $SerieName != $Data["Abscissa"]) {
559                $count++;
560            }
561        }
562
563        return $count;
564    }
565
566    /**
567     * Fix box coordinates
568     * @param int $Xa
569     * @param int $Ya
570     * @param int $Xb
571     * @param int $Yb
572     * @return integer[]
573     */
574    public function fixBoxCoordinates($Xa, $Ya, $Xb, $Yb)
575    {
576        return [min($Xa, $Xb), min($Ya, $Yb), max($Xa, $Xb), max($Ya, $Yb)];
577    }
578
579    /**
580     * Apply AALias correction to the rounded box boundaries
581     * @param int|float $Value
582     * @param int $Mode
583     * @return int|float
584     */
585    public function offsetCorrection($Value, $Mode)
586    {
587        $Value = round($Value, 1);
588
589        if ($Value == 0 && $Mode != 1) {
590            return 0;
591        }
592
593        if ($Mode == 1) {
594            if ($Value == .5) {
595                return .5;
596            }
597            if ($Value == .8) {
598                return .6;
599            }
600            if (in_array($Value, [.4, .7])) {
601                return .7;
602            }
603            if (in_array($Value, [.2, .3, .6])) {
604                return .8;
605            }
606            if (in_array($Value, [0, 1, .1, .9])) {
607                return .9;
608            }
609        }
610
611        if ($Mode == 2) {
612            if ($Value == .1) {
613                return .1;
614            }
615            if ($Value == .2) {
616                return .2;
617            }
618            if ($Value == .3) {
619                return .3;
620            }
621            if ($Value == .4) {
622                return .4;
623            }
624            if ($Value == .5) {
625                return .5;
626            }
627            if ($Value == .7) {
628                return .7;
629            }
630            if (in_array($Value, [.6, .8])) {
631                return .8;
632            }
633            if (in_array($Value, [1, .9])) {
634                return .9;
635            }
636        }
637
638        if ($Mode == 3) {
639            if (in_array($Value, [1, .1])) {
640                return .1;
641            }
642            if ($Value == .2) {
643                return .2;
644            }
645            if ($Value == .3) {
646                return .3;
647            }
648            if (in_array($Value, [.4, .8])) {
649                return .4;
650            }
651            if ($Value == .5) {
652                return .9;
653            }
654            if ($Value == .6) {
655                return .6;
656            }
657            if ($Value == .7) {
658                return .7;
659            }
660            if ($Value == .9) {
661                return .5;
662            }
663        }
664
665        if ($Mode == 4) {
666            if ($Value == 1) {
667                return -1;
668            }
669            if (in_array($Value, [.1, .4, .7, .8, .9])) {
670                return .1;
671            }
672            if ($Value == .2) {
673                return .2;
674            }
675            if ($Value == .3) {
676                return .3;
677            }
678            if ($Value == .5) {
679                return -.1;
680            }
681            if ($Value == .6) {
682                return .8;
683            }
684        }
685    }
686
687    /**
688     * Get the legend box size
689     * @param array $Format
690     * @return array
691     */
692    public function getLegendSize(array $Format = [])
693    {
694        $FontName = isset($Format["FontName"]) ? $this->loadFont($Format["FontName"], 'fonts') : $this->FontName;
695        $FontSize = isset($Format["FontSize"]) ? $Format["FontSize"] : $this->FontSize;
696        $Margin = isset($Format["Margin"]) ? $Format["Margin"] : 5;
697        $Mode = isset($Format["Mode"]) ? $Format["Mode"] : LEGEND_VERTICAL;
698        $BoxWidth = isset($Format["BoxWidth"]) ? $Format["BoxWidth"] : 5;
699        $BoxHeight = isset($Format["BoxHeight"]) ? $Format["BoxHeight"] : 5;
700        $IconAreaWidth = isset($Format["IconAreaWidth"]) ? $Format["IconAreaWidth"] : $BoxWidth;
701        $IconAreaHeight = isset($Format["IconAreaHeight"]) ? $Format["IconAreaHeight"] : $BoxHeight;
702        $XSpacing = isset($Format["XSpacing"]) ? $Format["XSpacing"] : 5;
703
704        $Data = $this->DataSet->getData();
705
706        foreach ($Data["Series"] as $SerieName => $Serie) {
707            if ($Serie["isDrawable"] == true
708                && $SerieName != $Data["Abscissa"]
709                && isset($Serie["Picture"])
710            ) {
711                list($PicWidth, $PicHeight) = $this->getPicInfo($Serie["Picture"]);
712                if ($IconAreaWidth < $PicWidth) {
713                    $IconAreaWidth = $PicWidth;
714                }
715                if ($IconAreaHeight < $PicHeight) {
716                    $IconAreaHeight = $PicHeight;
717                }
718            }
719        }
720
721        $YStep = max($this->FontSize, $IconAreaHeight) + 5;
722        $XStep = $IconAreaWidth + 5;
723        $XStep = $XSpacing;
724
725        $X = 100;
726        $Y = 100;
727
728        $Boundaries = [];
729        $Boundaries["L"] = $X;
730        $Boundaries["T"] = $Y;
731        $Boundaries["R"] = 0;
732        $Boundaries["B"] = 0;
733        $vY = $Y;
734        $vX = $X;
735        foreach ($Data["Series"] as $SerieName => $Serie) {
736            if ($Serie["isDrawable"] == true && $SerieName != $Data["Abscissa"]) {
737                if ($Mode == LEGEND_VERTICAL) {
738                    $BoxArray = $this->getTextBox(
739                        $vX + $IconAreaWidth + 4,
740                        $vY + $IconAreaHeight / 2,
741                        $FontName,
742                        $FontSize,
743                        0,
744                        $Serie["Description"]
745                    );
746
747                    if ($Boundaries["T"] > $BoxArray[2]["Y"] + $IconAreaHeight / 2) {
748                        $Boundaries["T"] = $BoxArray[2]["Y"] + $IconAreaHeight / 2;
749                    }
750                    if ($Boundaries["R"] < $BoxArray[1]["X"] + 2) {
751                        $Boundaries["R"] = $BoxArray[1]["X"] + 2;
752                    }
753                    if ($Boundaries["B"] < $BoxArray[1]["Y"] + 2 + $IconAreaHeight / 2) {
754                        $Boundaries["B"] = $BoxArray[1]["Y"] + 2 + $IconAreaHeight / 2;
755                    }
756
757                    $Lines = preg_split("/\n/", $Serie["Description"]);
758                    $vY = $vY + max($this->FontSize * count($Lines), $IconAreaHeight) + 5;
759                } elseif ($Mode == LEGEND_HORIZONTAL) {
760                    $Lines = preg_split("/\n/", $Serie["Description"]);
761                    $Width = [];
762                    foreach ($Lines as $Key => $Value) {
763                        $BoxArray = $this->getTextBox(
764                            $vX + $IconAreaWidth + 6,
765                            $Y + $IconAreaHeight / 2 + (($this->FontSize + 3) * $Key),
766                            $FontName,
767                            $FontSize,
768                            0,
769                            $Value
770                        );
771
772                        if ($Boundaries["T"] > $BoxArray[2]["Y"] + $IconAreaHeight / 2) {
773                            $Boundaries["T"] = $BoxArray[2]["Y"] + $IconAreaHeight / 2;
774                        }
775                        if ($Boundaries["R"] < $BoxArray[1]["X"] + 2) {
776                            $Boundaries["R"] = $BoxArray[1]["X"] + 2;
777                        }
778                        if ($Boundaries["B"] < $BoxArray[1]["Y"] + 2 + $IconAreaHeight / 2) {
779                            $Boundaries["B"] = $BoxArray[1]["Y"] + 2 + $IconAreaHeight / 2;
780                        }
781
782                        $Width[] = $BoxArray[1]["X"];
783                    }
784
785                    $vX = max($Width) + $XStep;
786                }
787            }
788        }
789        $vY = $vY - $YStep;
790        $vX = $vX - $XStep;
791
792        $TopOffset = $Y - $Boundaries["T"];
793        if ($Boundaries["B"] - ($vY + $IconAreaHeight) < $TopOffset) {
794            $Boundaries["B"] = $vY + $IconAreaHeight + $TopOffset;
795        }
796
797        $Width = ($Boundaries["R"] + $Margin) - ($Boundaries["L"] - $Margin);
798        $Height = ($Boundaries["B"] + $Margin) - ($Boundaries["T"] - $Margin);
799
800        return ["Width" => $Width, "Height" => $Height];
801    }
802
803    /**
804     * Return the abscissa margin
805     * @param array $Data
806     * @return int
807     */
808    public function getAbscissaMargin(array $Data)
809    {
810        foreach ($Data["Axis"] as $Values) {
811            if ($Values["Identity"] == AXIS_X) {
812                return $Values["Margin"];
813            }
814        }
815        return 0;
816    }
817
818    /**
819     * Returns a random color
820     * @param int $Alpha
821     * @return array
822     */
823    public function getRandomColor($Alpha = 100)
824    {
825        return [
826            "R" => rand(0, 255),
827            "G" => rand(0, 255),
828            "B" => rand(0, 255),
829            "Alpha" => $Alpha
830        ];
831    }
832
833    /**
834     * Validate a palette
835     * @param mixed $Colors
836     * @param int|float $Surrounding
837     * @return array
838     */
839    public function validatePalette($Colors, $Surrounding = null)
840    {
841        $Result = [];
842
843        if (!is_array($Colors)) {
844            return $this->getRandomColor();
845        }
846
847        foreach ($Colors as $Key => $Values) {
848            if (isset($Values["R"])) {
849                $Result[$Key]["R"] = $Values["R"];
850            } else {
851                $Result[$Key]["R"] = rand(0, 255);
852            }
853
854            if (isset($Values["G"])) {
855                $Result[$Key]["G"] = $Values["G"];
856            } else {
857                $Result[$Key]["G"] = rand(0, 255);
858            }
859
860            if (isset($Values["B"])) {
861                $Result[$Key]["B"] = $Values["B"];
862            } else {
863                $Result[$Key]["B"] = rand(0, 255);
864            }
865            if (isset($Values["Alpha"])) {
866                $Result[$Key]["Alpha"] = $Values["Alpha"];
867            } else {
868                $Result[$Key]["Alpha"] = 100;
869            }
870
871            if (null !== $Surrounding) {
872                $Result[$Key]["BorderR"] = $Result[$Key]["R"] + $Surrounding;
873                $Result[$Key]["BorderG"] = $Result[$Key]["G"] + $Surrounding;
874                $Result[$Key]["BorderB"] = $Result[$Key]["B"] + $Surrounding;
875            } else {
876                if (isset($Values["BorderR"])) {
877                    $Result[$Key]["BorderR"] = $Values["BorderR"];
878                } else {
879                    $Result[$Key]["BorderR"] = $Result[$Key]["R"];
880                }
881                if (isset($Values["BorderG"])) {
882                    $Result[$Key]["BorderG"] = $Values["BorderG"];
883                } else {
884                    $Result[$Key]["BorderG"] = $Result[$Key]["G"];
885                }
886                if (isset($Values["BorderB"])) {
887                    $Result[$Key]["BorderB"] = $Values["BorderB"];
888                } else {
889                    $Result[$Key]["BorderB"] = $Result[$Key]["B"];
890                }
891                if (isset($Values["BorderAlpha"])) {
892                    $Result[$Key]["BorderAlpha"] = $Values["BorderAlpha"];
893                } else {
894                    $Result[$Key]["BorderAlpha"] = $Result[$Key]["Alpha"];
895                }
896            }
897        }
898
899        return $Result;
900    }
901
902    /**
903     * @param mixed $Values
904     * @param array $Option
905     * @param boolean $ReturnOnly0Height
906     * @return int|float|array
907     */
908    public function scaleComputeY($Values, array $Option = [], $ReturnOnly0Height = false)
909    {
910        $AxisID = isset($Option["AxisID"]) ? $Option["AxisID"] : 0;
911        $SerieName = isset($Option["SerieName"]) ? $Option["SerieName"] : null;
912
913        $Data = $this->DataSet->getData();
914        if (!isset($Data["Axis"][$AxisID])) {
915            return -1;
916        }
917
918        if ($SerieName != null) {
919            $AxisID = $Data["Series"][$SerieName]["Axis"];
920        }
921        if (!is_array($Values)) {
922            $tmp = $Values;
923            $Values = [];
924            $Values[0] = $tmp;
925        }
926
927        $Result = [];
928        if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
929            $Height = ($this->GraphAreaY2 - $this->GraphAreaY1) - $Data["Axis"][$AxisID]["Margin"] * 2;
930            $ScaleHeight = $Data["Axis"][$AxisID]["ScaleMax"] - $Data["Axis"][$AxisID]["ScaleMin"];
931            $Step = $Height / $ScaleHeight;
932
933            if ($ReturnOnly0Height) {
934                foreach ($Values as $Key => $Value) {
935                    if ($Value == VOID) {
936                        $Result[] = VOID;
937                    } else {
938                        $Result[] = $Step * $Value;
939                    }
940                }
941            } else {
942                foreach ($Values as $Key => $Value) {
943                    if ($Value == VOID) {
944                        $Result[] = VOID;
945                    } else {
946                        $Result[] = $this->GraphAreaY2
947                            - $Data["Axis"][$AxisID]["Margin"]
948                            - ($Step * ($Value - $Data["Axis"][$AxisID]["ScaleMin"]))
949                        ;
950                    }
951                }
952            }
953        } else {
954            $Width = ($this->GraphAreaX2 - $this->GraphAreaX1) - $Data["Axis"][$AxisID]["Margin"] * 2;
955            $ScaleWidth = $Data["Axis"][$AxisID]["ScaleMax"] - $Data["Axis"][$AxisID]["ScaleMin"];
956            $Step = $Width / $ScaleWidth;
957
958            if ($ReturnOnly0Height) {
959                foreach ($Values as $Key => $Value) {
960                    if ($Value == VOID) {
961                        $Result[] = VOID;
962                    } else {
963                        $Result[] = $Step * $Value;
964                    }
965                }
966            } else {
967                foreach ($Values as $Key => $Value) {
968                    if ($Value == VOID) {
969                        $Result[] = VOID;
970                    } else {
971                        $Result[] = $this->GraphAreaX1
972                            + $Data["Axis"][$AxisID]["Margin"]
973                            + ($Step * ($Value - $Data["Axis"][$AxisID]["ScaleMin"]))
974                        ;
975                    }
976                }
977            }
978        }
979        return count($Result) == 1 ? reset($Result) : $Result;
980    }
981
982    /**
983     * Format the axis values
984     * @param mixed $Value
985     * @param int $Mode
986     * @param array $Format
987     * @param string $Unit
988     * @return string
989     */
990    public function scaleFormat($Value, $Mode = null, $Format = null, $Unit = null)
991    {
992        if ($Value == VOID) {
993            return "";
994        }
995
996        if ($Mode == AXIS_FORMAT_TRAFFIC) {
997            if ($Value == 0) {
998                return "0B";
999            }
1000            $Units = ["B", "KB", "MB", "GB", "TB", "PB"];
1001            $Sign = "";
1002            if ($Value < 0) {
1003                $Value = abs($Value);
1004                $Sign = "-";
1005            }
1006
1007            $Value = number_format($Value / pow(1024, ($Scale = floor(log($Value, 1024)))), 2, ",", ".");
1008            return $Sign . $Value . " " . $Units[$Scale];
1009        }
1010
1011        if ($Mode == AXIS_FORMAT_CUSTOM) {
1012            if (is_callable($Format)) {
1013                return call_user_func($Format, $Value);
1014            }
1015        }
1016
1017        if ($Mode == AXIS_FORMAT_DATE) {
1018            $Pattern = "d/m/Y";
1019            if ($Format !== null) {
1020                $Pattern = $Format;
1021            }
1022
1023            return gmdate($Pattern, $Value);
1024        }
1025
1026        if ($Mode == AXIS_FORMAT_TIME) {
1027            $Pattern = "H:i:s";
1028            if ($Format !== null) {
1029                $Pattern = $Format;
1030            }
1031
1032            return gmdate($Pattern, $Value);
1033        }
1034
1035        if ($Mode == AXIS_FORMAT_CURRENCY) {
1036            return $Format . number_format($Value, 2);
1037        }
1038
1039        if ($Mode == AXIS_FORMAT_METRIC) {
1040            if (abs($Value) > 1000000000) {
1041                return round($Value / 1000000000, $Format) . "g" . $Unit;
1042            }
1043            if (abs($Value) > 1000000) {
1044                return round($Value / 1000000, $Format) . "m" . $Unit;
1045            } elseif (abs($Value) >= 1000) {
1046                return round($Value / 1000, $Format) . "k" . $Unit;
1047            }
1048        }
1049        return $Value . $Unit;
1050    }
1051
1052    /**
1053     * @return array|null
1054     */
1055    public function scaleGetXSettings()
1056    {
1057        $Data = $this->DataSet->getData();
1058        foreach ($Data["Axis"] as $Settings) {
1059            if ($Settings["Identity"] == AXIS_X) {
1060                return [$Settings["Margin"], $Settings["Rows"]];
1061            }
1062        }
1063    }
1064
1065    /**
1066     * Write Max value on a chart
1067     * @param int $Type
1068     * @param array $Format
1069     */
1070    public function writeBounds($Type = BOUND_BOTH, $Format = null)
1071    {
1072        $MaxLabelTxt = isset($Format["MaxLabelTxt"]) ? $Format["MaxLabelTxt"] : "max=";
1073        $MinLabelTxt = isset($Format["MinLabelTxt"]) ? $Format["MinLabelTxt"] : "min=";
1074        $Decimals = isset($Format["Decimals"]) ? $Format["Decimals"] : 1;
1075        $ExcludedSeries = isset($Format["ExcludedSeries"]) ? $Format["ExcludedSeries"] : "";
1076        $DisplayOffset = isset($Format["DisplayOffset"]) ? $Format["DisplayOffset"] : 4;
1077        $DisplayColor = isset($Format["DisplayColor"]) ? $Format["DisplayColor"] : DISPLAY_MANUAL;
1078        $MaxDisplayR = isset($Format["MaxDisplayR"]) ? $Format["MaxDisplayR"] : 0;
1079        $MaxDisplayG = isset($Format["MaxDisplayG"]) ? $Format["MaxDisplayG"] : 0;
1080        $MaxDisplayB = isset($Format["MaxDisplayB"]) ? $Format["MaxDisplayB"] : 0;
1081        $MinDisplayR = isset($Format["MinDisplayR"]) ? $Format["MinDisplayR"] : 255;
1082        $MinDisplayG = isset($Format["MinDisplayG"]) ? $Format["MinDisplayG"] : 255;
1083        $MinDisplayB = isset($Format["MinDisplayB"]) ? $Format["MinDisplayB"] : 255;
1084        $MinLabelPos = isset($Format["MinLabelPos"]) ? $Format["MinLabelPos"] : BOUND_LABEL_POS_AUTO;
1085        $MaxLabelPos = isset($Format["MaxLabelPos"]) ? $Format["MaxLabelPos"] : BOUND_LABEL_POS_AUTO;
1086        $DrawBox = isset($Format["DrawBox"]) ? $Format["DrawBox"] : true;
1087        $DrawBoxBorder = isset($Format["DrawBoxBorder"]) ? $Format["DrawBoxBorder"] : false;
1088        $BorderOffset = isset($Format["BorderOffset"]) ? $Format["BorderOffset"] : 5;
1089        $BoxRounded = isset($Format["BoxRounded"]) ? $Format["BoxRounded"] : true;
1090        $RoundedRadius = isset($Format["RoundedRadius"]) ? $Format["RoundedRadius"] : 3;
1091        $BoxR = isset($Format["BoxR"]) ? $Format["BoxR"] : 0;
1092        $BoxG = isset($Format["BoxG"]) ? $Format["BoxG"] : 0;
1093        $BoxB = isset($Format["BoxB"]) ? $Format["BoxB"] : 0;
1094        $BoxAlpha = isset($Format["BoxAlpha"]) ? $Format["BoxAlpha"] : 20;
1095        $BoxSurrounding = isset($Format["BoxSurrounding"]) ? $Format["BoxSurrounding"] : "";
1096        $BoxBorderR = isset($Format["BoxBorderR"]) ? $Format["BoxBorderR"] : 255;
1097        $BoxBorderG = isset($Format["BoxBorderG"]) ? $Format["BoxBorderG"] : 255;
1098        $BoxBorderB = isset($Format["BoxBorderB"]) ? $Format["BoxBorderB"] : 255;
1099        $BoxBorderAlpha = isset($Format["BoxBorderAlpha"]) ? $Format["BoxBorderAlpha"] : 100;
1100
1101        $CaptionSettings = [
1102            "DrawBox" => $DrawBox,
1103            "DrawBoxBorder" => $DrawBoxBorder,
1104            "BorderOffset" => $BorderOffset,
1105            "BoxRounded" => $BoxRounded,
1106            "RoundedRadius" => $RoundedRadius,
1107            "BoxR" => $BoxR,
1108            "BoxG" => $BoxG,
1109            "BoxB" => $BoxB,
1110            "BoxAlpha" => $BoxAlpha,
1111            "BoxSurrounding" => $BoxSurrounding,
1112            "BoxBorderR" => $BoxBorderR,
1113            "BoxBorderG" => $BoxBorderG,
1114            "BoxBorderB" => $BoxBorderB,
1115            "BoxBorderAlpha" => $BoxBorderAlpha
1116        ];
1117
1118        list($XMargin, $XDivs) = $this->scaleGetXSettings();
1119
1120        $Data = $this->DataSet->getData();
1121        foreach ($Data["Series"] as $SerieName => $Serie) {
1122            if ($Serie["isDrawable"] == true
1123                && $SerieName != $Data["Abscissa"]
1124                && !isset($ExcludedSeries[$SerieName])
1125            ) {
1126                $R = $Serie["Color"]["R"];
1127                $G = $Serie["Color"]["G"];
1128                $B = $Serie["Color"]["B"];
1129
1130                $MinValue = $this->DataSet->getMin($SerieName);
1131                $MaxValue = $this->DataSet->getMax($SerieName);
1132
1133                $MinPos = VOID;
1134                $MaxPos = VOID;
1135                foreach ($Serie["Data"] as $Key => $Value) {
1136                    if ($Value == $MinValue && $MinPos == VOID) {
1137                        $MinPos = $Key;
1138                    }
1139                    if ($Value == $MaxValue) {
1140                        $MaxPos = $Key;
1141                    }
1142                }
1143
1144                $AxisID = $Serie["Axis"];
1145                $Mode = $Data["Axis"][$AxisID]["Display"];
1146                $Format = $Data["Axis"][$AxisID]["Format"];
1147                $Unit = $Data["Axis"][$AxisID]["Unit"];
1148
1149                $PosArray = $this->scaleComputeY(
1150                    $Serie["Data"],
1151                    ["AxisID" => $Serie["Axis"]]
1152                );
1153
1154                if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
1155                    $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1 - $XMargin * 2) / $XDivs;
1156                    $X = $this->GraphAreaX1 + $XMargin;
1157                    $SerieOffset = isset($Serie["XOffset"]) ? $Serie["XOffset"] : 0;
1158
1159                    if ($Type == BOUND_MAX || $Type == BOUND_BOTH) {
1160                        if ($MaxLabelPos == BOUND_LABEL_POS_TOP
1161                            || ($MaxLabelPos == BOUND_LABEL_POS_AUTO && $MaxValue >= 0)
1162                        ) {
1163                            $YPos = $PosArray[$MaxPos] - $DisplayOffset + 2;
1164                            $Align = TEXT_ALIGN_BOTTOMMIDDLE;
1165                        }
1166                        if ($MaxLabelPos == BOUND_LABEL_POS_BOTTOM
1167                            || ($MaxLabelPos == BOUND_LABEL_POS_AUTO && $MaxValue < 0)
1168                        ) {
1169                            $YPos = $PosArray[$MaxPos] + $DisplayOffset + 2;
1170                            $Align = TEXT_ALIGN_TOPMIDDLE;
1171                        }
1172
1173                        $XPos = $X + $MaxPos * $XStep + $SerieOffset;
1174                        $Label = sprintf(
1175                            '%s%s',
1176                            $MaxLabelTxt,
1177                            $this->scaleFormat(round($MaxValue, $Decimals), $Mode, $Format, $Unit)
1178                        );
1179
1180                        $TxtPos = $this->getTextBox($XPos, $YPos, $this->FontName, $this->FontSize, 0, $Label);
1181                        $XOffset = 0;
1182                        $YOffset = 0;
1183                        if ($TxtPos[0]["X"] < $this->GraphAreaX1) {
1184                            $XOffset = (($this->GraphAreaX1 - $TxtPos[0]["X"]) / 2);
1185                        }
1186                        if ($TxtPos[1]["X"] > $this->GraphAreaX2) {
1187                            $XOffset = -(($TxtPos[1]["X"] - $this->GraphAreaX2) / 2);
1188                        }
1189                        if ($TxtPos[2]["Y"] < $this->GraphAreaY1) {
1190                            $YOffset = $this->GraphAreaY1 - $TxtPos[2]["Y"];
1191                        }
1192                        if ($TxtPos[0]["Y"] > $this->GraphAreaY2) {
1193                            $YOffset = -($TxtPos[0]["Y"] - $this->GraphAreaY2);
1194                        }
1195
1196                        $CaptionSettings["R"] = $MaxDisplayR;
1197                        $CaptionSettings["G"] = $MaxDisplayG;
1198                        $CaptionSettings["B"] = $MaxDisplayB;
1199                        $CaptionSettings["Align"] = $Align;
1200
1201                        $this->drawText($XPos + $XOffset, $YPos + $YOffset, $Label, $CaptionSettings);
1202                    }
1203
1204                    if ($Type == BOUND_MIN || $Type == BOUND_BOTH) {
1205                        if ($MinLabelPos == BOUND_LABEL_POS_TOP
1206                            || ($MinLabelPos == BOUND_LABEL_POS_AUTO && $MinValue >= 0)
1207                        ) {
1208                            $YPos = $PosArray[$MinPos] - $DisplayOffset + 2;
1209                            $Align = TEXT_ALIGN_BOTTOMMIDDLE;
1210                        }
1211                        if ($MinLabelPos == BOUND_LABEL_POS_BOTTOM
1212                            || ($MinLabelPos == BOUND_LABEL_POS_AUTO && $MinValue < 0)
1213                        ) {
1214                            $YPos = $PosArray[$MinPos] + $DisplayOffset + 2;
1215                            $Align = TEXT_ALIGN_TOPMIDDLE;
1216                        }
1217
1218                        $XPos = $X + $MinPos * $XStep + $SerieOffset;
1219                        $Label = sprintf(
1220                            '%s%s',
1221                            $MinLabelTxt,
1222                            $this->scaleFormat(round($MinValue, $Decimals), $Mode, $Format, $Unit)
1223                        );
1224
1225                        $TxtPos = $this->getTextBox($XPos, $YPos, $this->FontName, $this->FontSize, 0, $Label);
1226                        $XOffset = 0;
1227                        $YOffset = 0;
1228                        if ($TxtPos[0]["X"] < $this->GraphAreaX1) {
1229                            $XOffset = (($this->GraphAreaX1 - $TxtPos[0]["X"]) / 2);
1230                        }
1231                        if ($TxtPos[1]["X"] > $this->GraphAreaX2) {
1232                            $XOffset = -(($TxtPos[1]["X"] - $this->GraphAreaX2) / 2);
1233                        }
1234                        if ($TxtPos[2]["Y"] < $this->GraphAreaY1) {
1235                            $YOffset = $this->GraphAreaY1 - $TxtPos[2]["Y"];
1236                        }
1237                        if ($TxtPos[0]["Y"] > $this->GraphAreaY2) {
1238                            $YOffset = -($TxtPos[0]["Y"] - $this->GraphAreaY2);
1239                        }
1240
1241                        $CaptionSettings["R"] = $MinDisplayR;
1242                        $CaptionSettings["G"] = $MinDisplayG;
1243                        $CaptionSettings["B"] = $MinDisplayB;
1244                        $CaptionSettings["Align"] = $Align;
1245
1246                        $this->drawText(
1247                            $XPos + $XOffset,
1248                            $YPos - $DisplayOffset + $YOffset,
1249                            $Label,
1250                            $CaptionSettings
1251                        );
1252                    }
1253                } else {
1254                    $XStep = ($this->GraphAreaY2 - $this->GraphAreaY1 - $XMargin * 2) / $XDivs;
1255                    $X = $this->GraphAreaY1 + $XMargin;
1256                    $SerieOffset = isset($Serie["XOffset"]) ? $Serie["XOffset"] : 0;
1257
1258                    if ($Type == BOUND_MAX || $Type == BOUND_BOTH) {
1259                        if ($MaxLabelPos == BOUND_LABEL_POS_TOP
1260                            || ($MaxLabelPos == BOUND_LABEL_POS_AUTO && $MaxValue >= 0)
1261                        ) {
1262                            $YPos = $PosArray[$MaxPos] + $DisplayOffset + 2;
1263                            $Align = TEXT_ALIGN_MIDDLELEFT;
1264                        }
1265                        if ($MaxLabelPos == BOUND_LABEL_POS_BOTTOM
1266                            || ($MaxLabelPos == BOUND_LABEL_POS_AUTO && $MaxValue < 0)
1267                        ) {
1268                            $YPos = $PosArray[$MaxPos] - $DisplayOffset + 2;
1269                            $Align = TEXT_ALIGN_MIDDLERIGHT;
1270                        }
1271
1272                        $XPos = $X + $MaxPos * $XStep + $SerieOffset;
1273                        $Label = $MaxLabelTxt . $this->scaleFormat($MaxValue, $Mode, $Format, $Unit);
1274
1275                        $TxtPos = $this->getTextBox($YPos, $XPos, $this->FontName, $this->FontSize, 0, $Label);
1276                        $XOffset = 0;
1277                        $YOffset = 0;
1278                        if ($TxtPos[0]["X"] < $this->GraphAreaX1) {
1279                            $XOffset = $this->GraphAreaX1 - $TxtPos[0]["X"];
1280                        }
1281                        if ($TxtPos[1]["X"] > $this->GraphAreaX2) {
1282                            $XOffset = -($TxtPos[1]["X"] - $this->GraphAreaX2);
1283                        }
1284                        if ($TxtPos[2]["Y"] < $this->GraphAreaY1) {
1285                            $YOffset = ($this->GraphAreaY1 - $TxtPos[2]["Y"]) / 2;
1286                        }
1287                        if ($TxtPos[0]["Y"] > $this->GraphAreaY2) {
1288                            $YOffset = -(($TxtPos[0]["Y"] - $this->GraphAreaY2) / 2);
1289                        }
1290
1291                        $CaptionSettings["R"] = $MaxDisplayR;
1292                        $CaptionSettings["G"] = $MaxDisplayG;
1293                        $CaptionSettings["B"] = $MaxDisplayB;
1294                        $CaptionSettings["Align"] = $Align;
1295
1296                        $this->drawText($YPos + $XOffset, $XPos + $YOffset, $Label, $CaptionSettings);
1297                    }
1298
1299                    if ($Type == BOUND_MIN || $Type == BOUND_BOTH) {
1300                        if ($MinLabelPos == BOUND_LABEL_POS_TOP
1301                            || ($MinLabelPos == BOUND_LABEL_POS_AUTO && $MinValue >= 0)
1302                        ) {
1303                            $YPos = $PosArray[$MinPos] + $DisplayOffset + 2;
1304                            $Align = TEXT_ALIGN_MIDDLELEFT;
1305                        }
1306                        if ($MinLabelPos == BOUND_LABEL_POS_BOTTOM
1307                            || ($MinLabelPos == BOUND_LABEL_POS_AUTO && $MinValue < 0)
1308                        ) {
1309                            $YPos = $PosArray[$MinPos] - $DisplayOffset + 2;
1310                            $Align = TEXT_ALIGN_MIDDLERIGHT;
1311                        }
1312
1313                        $XPos = $X + $MinPos * $XStep + $SerieOffset;
1314                        $Label = $MinLabelTxt . $this->scaleFormat($MinValue, $Mode, $Format, $Unit);
1315
1316                        $TxtPos = $this->getTextBox($YPos, $XPos, $this->FontName, $this->FontSize, 0, $Label);
1317                        $XOffset = 0;
1318                        $YOffset = 0;
1319                        if ($TxtPos[0]["X"] < $this->GraphAreaX1) {
1320                            $XOffset = $this->GraphAreaX1 - $TxtPos[0]["X"];
1321                        }
1322                        if ($TxtPos[1]["X"] > $this->GraphAreaX2) {
1323                            $XOffset = -($TxtPos[1]["X"] - $this->GraphAreaX2);
1324                        }
1325                        if ($TxtPos[2]["Y"] < $this->GraphAreaY1) {
1326                            $YOffset = ($this->GraphAreaY1 - $TxtPos[2]["Y"]) / 2;
1327                        }
1328                        if ($TxtPos[0]["Y"] > $this->GraphAreaY2) {
1329                            $YOffset = -(($TxtPos[0]["Y"] - $this->GraphAreaY2) / 2);
1330                        }
1331
1332                        $CaptionSettings["R"] = $MinDisplayR;
1333                        $CaptionSettings["G"] = $MinDisplayG;
1334                        $CaptionSettings["B"] = $MinDisplayB;
1335                        $CaptionSettings["Align"] = $Align;
1336
1337                        $this->drawText($YPos + $XOffset, $XPos + $YOffset, $Label, $CaptionSettings);
1338                    }
1339                }
1340            }
1341        }
1342    }
1343
1344    /**
1345     * Write labels
1346     * @param string $SeriesName
1347     * @param array $Indexes
1348     * @param array $Format
1349     */
1350    public function writeLabel($SeriesName, $Indexes, array $Format = [])
1351    {
1352        $OverrideTitle = isset($Format["OverrideTitle"]) ? $Format["OverrideTitle"] : null;
1353        $ForceLabels = isset($Format["ForceLabels"]) ? $Format["ForceLabels"] : null;
1354        $DrawPoint = isset($Format["DrawPoint"]) ? $Format["DrawPoint"] : LABEL_POINT_BOX;
1355        $DrawVerticalLine = isset($Format["DrawVerticalLine"]) ? $Format["DrawVerticalLine"] : false;
1356        $VerticalLineR = isset($Format["VerticalLineR"]) ? $Format["VerticalLineR"] : 0;
1357        $VerticalLineG = isset($Format["VerticalLineG"]) ? $Format["VerticalLineG"] : 0;
1358        $VerticalLineB = isset($Format["VerticalLineB"]) ? $Format["VerticalLineB"] : 0;
1359        $VerticalLineAlpha = isset($Format["VerticalLineAlpha"]) ? $Format["VerticalLineAlpha"] : 40;
1360        $VerticalLineTicks = isset($Format["VerticalLineTicks"]) ? $Format["VerticalLineTicks"] : 2;
1361
1362        $Data = $this->DataSet->getData();
1363        list($XMargin, $XDivs) = $this->scaleGetXSettings();
1364
1365        if (!is_array($Indexes)) {
1366            $Index = $Indexes;
1367            $Indexes = [];
1368            $Indexes[] = $Index;
1369        }
1370        if (!is_array($SeriesName)) {
1371            $SerieName = $SeriesName;
1372            $SeriesName = [];
1373            $SeriesName[] = $SerieName;
1374        }
1375        if ($ForceLabels != null && !is_array($ForceLabels)) {
1376            $ForceLabel = $ForceLabels;
1377            $ForceLabels = [];
1378            $ForceLabels[] = $ForceLabel;
1379        }
1380
1381        foreach ($Indexes as $Key => $Index) {
1382            $Series = [];
1383
1384            if ($Data["Orientation"] == SCALE_POS_LEFTRIGHT) {
1385                if ($XDivs == 0) {
1386                    $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1) / 4;
1387                } else {
1388                    $XStep = ($this->GraphAreaX2 - $this->GraphAreaX1 - $XMargin * 2) / $XDivs;
1389                }
1390                $X = $this->GraphAreaX1 + $XMargin + $Index * $XStep;
1391
1392                if ($DrawVerticalLine) {
1393                    $this->drawLine(
1394                        $X,
1395                        $this->GraphAreaY1 + $Data["YMargin"],
1396                        $X,
1397                        $this->GraphAreaY2 - $Data["YMargin"],
1398                        [
1399                            "R" => $VerticalLineR,
1400                            "G" => $VerticalLineG,
1401                            "B" => $VerticalLineB,
1402                            "Alpha" => $VerticalLineAlpha,
1403                            "Ticks" => $VerticalLineTicks
1404                        ]
1405                    );
1406                }
1407
1408                $MinY = $this->GraphAreaY2;
1409                foreach ($SeriesName as $SerieName) {
1410                    if (isset($Data["Series"][$SerieName]["Data"][$Index])) {
1411                        $AxisID = $Data["Series"][$SerieName]["Axis"];
1412                        $XAxisMode = $Data["XAxisDisplay"];
1413                        $XAxisFormat = $Data["XAxisFormat"];
1414                        $XAxisUnit = $Data["XAxisUnit"];
1415                        $AxisMode = $Data["Axis"][$AxisID]["Display"];
1416                        $AxisFormat = $Data["Axis"][$AxisID]["Format"];
1417                        $AxisUnit = $Data["Axis"][$AxisID]["Unit"];
1418                        $XLabel = "";
1419
1420                        if (isset($Data["Abscissa"])
1421                            && isset($Data["Series"][$Data["Abscissa"]]["Data"][$Index])
1422                        ) {
1423                            $XLabel = $this->scaleFormat(
1424                                $Data["Series"][$Data["Abscissa"]]["Data"][$Index],
1425                                $XAxisMode,
1426                                $XAxisFormat,
1427                                $XAxisUnit
1428                            );
1429                        }
1430
1431                        if ($OverrideTitle != null) {
1432                            $Description = $OverrideTitle;
1433                        } elseif (count($SeriesName) == 1) {
1434                            $Description = $Data["Series"][$SerieName]["Description"] . " - " . $XLabel;
1435                        } elseif (isset($Data["Abscissa"])
1436                            && isset($Data["Series"][$Data["Abscissa"]]["Data"][$Index])
1437                        ) {
1438                            $Description = $XLabel;
1439                        }
1440
1441                        $Serie = [
1442                            "R" => $Data["Series"][$SerieName]["Color"]["R"],
1443                            "G" => $Data["Series"][$SerieName]["Color"]["G"],
1444                            "B" => $Data["Series"][$SerieName]["Color"]["B"],
1445                            "Alpha" => $Data["Series"][$SerieName]["Color"]["Alpha"]
1446                        ];
1447                        if (count($SeriesName) == 1
1448                            && isset($Data["Series"][$SerieName]["XOffset"])
1449                        ) {
1450                            $SerieOffset = $Data["Series"][$SerieName]["XOffset"];
1451                        } else {
1452                            $SerieOffset = 0;
1453                        }
1454                        $Value = $Data["Series"][$SerieName]["Data"][$Index];
1455                        if ($Value == VOID) {
1456                            $Value = "NaN";
1457                        }
1458
1459                        if ($ForceLabels != null) {
1460                            $Caption = isset($ForceLabels[$Key]) ? $ForceLabels[$Key] : "Not set";
1461                        } else {
1462                            $Caption = $this->scaleFormat($Value, $AxisMode, $AxisFormat, $AxisUnit);
1463                        }
1464
1465                        if ($this->LastChartLayout == CHART_LAST_LAYOUT_STACKED) {
1466                            if ($Value >= 0) {
1467                                $LookFor = "+";
1468                            } else {
1469                                $LookFor = "-";
1470                            }
1471
1472                            $Value = 0;
1473                            $Done = false;
1474                            foreach ($Data["Series"] as $Name => $SerieLookup) {
1475                                if ($SerieLookup["isDrawable"] == true
1476                                    && $Name != $Data["Abscissa"] && !$Done
1477                                ) {
1478                                    if (isset($Data["Series"][$Name]["Data"][$Index])
1479                                        && $Data["Series"][$Name]["Data"][$Index] != VOID
1480                                    ) {
1481                                        if ($Data["Series"][$Name]["Data"][$Index] >= 0 && $LookFor == "+") {
1482                                            $Value = $Value + $Data["Series"][$Name]["Data"][$Index];
1483                                        }
1484                                        if ($Data["Series"][$Name]["Data"][$Index] < 0 && $LookFor == "-") {
1485                                            $Value = $Value - $Data["Series"][$Name]["Data"][$Index];
1486                                        }
1487                                        if ($Name == $SerieName) {
1488                                            $Done = true;
1489                                        }
1490                                    }
1491                                }
1492                            }
1493                        }
1494
1495                        $X = floor($this->GraphAreaX1 + $XMargin + $Index * $XStep + $SerieOffset);
1496                        $Y = floor($this->scaleComputeY($Value, ["AxisID" => $AxisID]));
1497
1498                        if ($Y < $MinY) {
1499                            $MinY = $Y;
1500                        }
1501
1502                        if ($DrawPoint == LABEL_POINT_CIRCLE) {
1503                            $this->drawFilledCircle(
1504                                $X,
1505                                $Y,
1506                                3,
1507                                [
1508                                    "R" => 255,
1509                                    "G" => 255,
1510                                    "B" => 255,
1511                                    "BorderR" => 0,
1512                                    "BorderG" => 0,
1513                                    "BorderB" => 0
1514                                ]
1515                            );
1516                        } elseif ($DrawPoint == LABEL_POINT_BOX) {
1517                            $this->drawFilledRectangle(
1518                                $X - 2,
1519                                $Y - 2,
1520                                $X + 2,
1521                                $Y + 2,
1522                                [
1523                                    "R" => 255,
1524                                    "G" => 255,
1525                                    "B" => 255,
1526                                    "BorderR" => 0,
1527                                    "BorderG" => 0,
1528                                    "BorderB" => 0
1529                                ]
1530                            );
1531                        }
1532                        $Series[] = ["Format" => $Serie, "Caption" => $Caption];
1533                    }
1534                }
1535                $this->drawLabelBox($X, $MinY - 3, $Description, $Series, $Format);
1536            } else {
1537                if ($XDivs == 0) {
1538                    $XStep = ($this->GraphAreaY2 - $this->GraphAreaY1) / 4;
1539                } else {
1540                    $XStep = ($this->GraphAreaY2 - $this->GraphAreaY1 - $XMargin * 2) / $XDivs;
1541                }
1542                $Y = $this->GraphAreaY1 + $XMargin + $Index * $XStep;
1543
1544                if ($DrawVerticalLine) {
1545                    $this->drawLine(
1546                        $this->GraphAreaX1 + $Data["YMargin"],
1547                        $Y,
1548                        $this->GraphAreaX2 - $Data["YMargin"],
1549                        $Y,
1550                        [
1551                            "R" => $VerticalLineR,
1552                            "G" => $VerticalLineG,
1553                            "B" => $VerticalLineB,
1554                            "Alpha" => $VerticalLineAlpha,
1555                            "Ticks" => $VerticalLineTicks
1556                        ]
1557                    );
1558                }
1559
1560                $MinX = $this->GraphAreaX2;
1561                foreach ($SeriesName as $Key => $SerieName) {
1562                    if (isset($Data["Series"][$SerieName]["Data"][$Index])) {
1563                        $AxisID = $Data["Series"][$SerieName]["Axis"];
1564                        $XAxisMode = $Data["XAxisDisplay"];
1565                        $XAxisFormat = $Data["XAxisFormat"];
1566                        $XAxisUnit = $Data["XAxisUnit"];
1567                        $AxisMode = $Data["Axis"][$AxisID]["Display"];
1568                        $AxisFormat = $Data["Axis"][$AxisID]["Format"];
1569                        $AxisUnit = $Data["Axis"][$AxisID]["Unit"];
1570                        $XLabel = "";
1571
1572                        if (isset($Data["Abscissa"])
1573                            && isset($Data["Series"][$Data["Abscissa"]]["Data"][$Index])
1574                        ) {
1575                            $XLabel = $this->scaleFormat(
1576                                $Data["Series"][$Data["Abscissa"]]["Data"][$Index],
1577                                $XAxisMode,
1578                                $XAxisFormat,
1579                                $XAxisUnit
1580                            );
1581                        }
1582
1583                        if ($OverrideTitle != null) {
1584                            $Description = $OverrideTitle;
1585                        } elseif (count($SeriesName) == 1) {
1586                            if (isset($Data["Abscissa"])
1587                                && isset($Data["Series"][$Data["Abscissa"]]["Data"][$Index])
1588                            ) {
1589                                $Description = $Data["Series"][$SerieName]["Description"] . " - " . $XLabel;
1590                            }
1591                        } elseif (isset($Data["Abscissa"])
1592                            && isset($Data["Series"][$Data["Abscissa"]]["Data"][$Index])
1593                        ) {
1594                            $Description = $XLabel;
1595                        }
1596                        $Serie = [];
1597                        if (isset($Data["Extended"]["Palette"][$Index])) {
1598                            $Serie["R"] = $Data["Extended"]["Palette"][$Index]["R"];
1599                            $Serie["G"] = $Data["Extended"]["Palette"][$Index]["G"];
1600                            $Serie["B"] = $Data["Extended"]["Palette"][$Index]["B"];
1601                            $Serie["Alpha"] = $Data["Extended"]["Palette"][$Index]["Alpha"];
1602                        } else {
1603                            $Serie["R"] = $Data["Series"][$SerieName]["Color"]["R"];
1604                            $Serie["G"] = $Data["Series"][$SerieName]["Color"]["G"];
1605                            $Serie["B"] = $Data["Series"][$SerieName]["Color"]["B"];
1606                            $Serie["Alpha"] = $Data["Series"][$SerieName]["Color"]["Alpha"];
1607                        }
1608
1609                        if (count($SeriesName) == 1 && isset($Data["Series"][$SerieName]["XOffset"])) {
1610                            $SerieOffset = $Data["Series"][$SerieName]["XOffset"];
1611                        } else {
1612                            $SerieOffset = 0;
1613                        }
1614
1615                        $Value = $Data["Series"][$SerieName]["Data"][$Index];
1616                        if ($ForceLabels != null) {
1617                            $Caption = isset($ForceLabels[$Key]) ? $ForceLabels[$Key] : "Not set";
1618                        } else {
1619                            $Caption = $this->scaleFormat($Value, $AxisMode, $AxisFormat, $AxisUnit);
1620                        }
1621                        if ($Value == VOID) {
1622                            $Value = "NaN";
1623                        }
1624
1625                        if ($this->LastChartLayout == CHART_LAST_LAYOUT_STACKED) {
1626                            if ($Value >= 0) {
1627                                $LookFor = "+";
1628                            } else {
1629                                $LookFor = "-";
1630                            }
1631
1632                            $Value = 0;
1633                            $Done = false;
1634                            foreach ($Data["Series"] as $Name => $SerieLookup) {
1635                                if ($SerieLookup["isDrawable"] == true
1636                                    && $Name != $Data["Abscissa"]
1637                                    && !$Done
1638                                ) {
1639                                    if (isset($Data["Series"][$Name]["Data"][$Index])
1640                                        && $Data["Series"][$Name]["Data"][$Index] != VOID
1641                                    ) {
1642                                        if ($Data["Series"][$Name]["Data"][$Index] >= 0 && $LookFor == "+") {
1643                                            $Value = $Value + $Data["Series"][$Name]["Data"][$Index];
1644                                        }
1645                                        if ($Data["Series"][$Name]["Data"][$Index] < 0 && $LookFor == "-") {
1646                                            $Value = $Value - $Data["Series"][$Name]["Data"][$Index];
1647                                        }
1648                                        if ($Name == $SerieName) {
1649                                            $Done = true;
1650                                        }
1651                                    }
1652                                }
1653                            }
1654                        }
1655
1656                        $X = floor($this->scaleComputeY($Value, ["AxisID" => $AxisID]));
1657                        $Y = floor($this->GraphAreaY1 + $XMargin + $Index * $XStep + $SerieOffset);
1658
1659                        if ($X < $MinX) {
1660                            $MinX = $X;
1661                        }
1662
1663                        if ($DrawPoint == LABEL_POINT_CIRCLE) {
1664                            $this->drawFilledCircle(
1665                                $X,
1666                                $Y,
1667                                3,
1668                                [
1669                                    "R" => 255,
1670                                    "G" => 255,
1671                                    "B" => 255,
1672                                    "BorderR" => 0,
1673                                    "BorderG" => 0,
1674                                    "BorderB" => 0
1675                                ]
1676                            );
1677                        } elseif ($DrawPoint == LABEL_POINT_BOX) {
1678                            $this->drawFilledRectangle(
1679                                $X - 2,
1680                                $Y - 2,
1681                                $X + 2,
1682                                $Y + 2,
1683                                [
1684                                    "R" => 255,
1685                                    "G" => 255,
1686                                    "B" => 255,
1687                                    "BorderR" => 0,
1688                                    "BorderG" => 0,
1689                                    "BorderB" => 0
1690                                ]
1691                            );
1692                        }
1693                        $Series[] = ["Format" => $Serie, "Caption" => $Caption];
1694                    }
1695                }
1696                $this->drawLabelBox($MinX, $Y - 3, $Description, $Series, $Format);
1697            }
1698        }
1699    }
1700}
1701