1<?php
2
3namespace CpChart\Chart;
4
5use CpChart\Data;
6use CpChart\Image;
7
8/**
9 *  Pie - class to draw pie charts
10 *
11 *  Version     : 2.1.4
12 *  Made by     : Jean-Damien POGOLOTTI
13 *  Last Update : 19/01/2014
14 *
15 *  This file can be distributed under the license you can find at :
16 *
17 *  http://www.pchart.net/license
18 *
19 *  You can find the whole class documentation on the pChart web site.
20 */
21class Pie
22{
23    /**
24     * @var Image
25     */
26    public $pChartObject;
27
28    /**
29     * @var Data
30     */
31    public $pDataObject;
32
33    /**
34     * @var array
35     */
36    public $LabelPos = [];
37
38    /**
39     * @param Image $pChartObject
40     * @param Data $pDataObject
41     */
42    public function __construct(Image $pChartObject, Data $pDataObject)
43    {
44        $this->pChartObject = $pChartObject;
45        $this->pDataObject = $pDataObject;
46    }
47
48    /**
49     * Draw a pie chart
50     * @param int $X
51     * @param int $Y
52     * @param array $Format
53     * @return int
54     */
55    public function draw2DPie($X, $Y, array $Format = [])
56    {
57        $Radius = isset($Format["Radius"]) ? $Format["Radius"] : 60;
58        $Precision = isset($Format["Precision"]) ? $Format["Precision"] : 0;
59        $DataGapAngle = isset($Format["DataGapAngle"]) ? $Format["DataGapAngle"] : 0;
60        $DataGapRadius = isset($Format["DataGapRadius"]) ? $Format["DataGapRadius"] : 0;
61        $SecondPass = isset($Format["SecondPass"]) ? $Format["SecondPass"] : true;
62        $Border = isset($Format["Border"]) ? $Format["Border"] : false;
63        $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : 255;
64        $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : 255;
65        $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : 255;
66        $Shadow = isset($Format["Shadow"]) ? $Format["Shadow"] : false;
67        $DrawLabels = isset($Format["DrawLabels"]) ? $Format["DrawLabels"] : false;
68        $LabelStacked = isset($Format["LabelStacked"]) ? $Format["LabelStacked"] : false;
69        $LabelColor = isset($Format["LabelColor"]) ? $Format["LabelColor"] : PIE_LABEL_COLOR_MANUAL;
70        $LabelR = isset($Format["LabelR"]) ? $Format["LabelR"] : 0;
71        $LabelG = isset($Format["LabelG"]) ? $Format["LabelG"] : 0;
72        $LabelB = isset($Format["LabelB"]) ? $Format["LabelB"] : 0;
73        $LabelAlpha = isset($Format["LabelAlpha"]) ? $Format["LabelAlpha"] : 100;
74        $WriteValues = isset($Format["WriteValues"]) ? $Format["WriteValues"] : null;
75        $ValuePosition = isset($Format["ValuePosition"]) ? $Format["ValuePosition"] : PIE_VALUE_OUTSIDE;
76        $ValuePadding = isset($Format["ValuePadding"]) ? $Format["ValuePadding"] : 15;
77        $ValueSuffix = isset($Format["ValueSuffix"]) ? $Format["ValueSuffix"] : "";
78        $ValueR = isset($Format["ValueR"]) ? $Format["ValueR"] : 255;
79        $ValueG = isset($Format["ValueG"]) ? $Format["ValueG"] : 255;
80        $ValueB = isset($Format["ValueB"]) ? $Format["ValueB"] : 255;
81        $ValueAlpha = isset($Format["ValueAlpha"]) ? $Format["ValueAlpha"] : 100;
82        $RecordImageMap = isset($Format["RecordImageMap"]) ? $Format["RecordImageMap"] : false;
83
84        $Data = $this->pDataObject->getData();
85        $Palette = $this->pDataObject->getPalette();
86
87        /* Do we have an abscissa serie defined? */
88        if ($Data["Abscissa"] == "") {
89            return PIE_NO_ABSCISSA;
90        }
91
92        /* Try to find the data serie */
93        $DataSerie = null;
94        foreach (array_keys($Data["Series"]) as $SerieName) {
95            if ($SerieName != $Data["Abscissa"]) {
96                $DataSerie = $SerieName;
97            }
98        }
99
100        /* Do we have data to compute? */
101        if (!$DataSerie) {
102            return PIE_NO_DATASERIE;
103        }
104
105        /* Remove unused data */
106        list($Data, $Palette) = $this->clean0Values($Data, $Palette, $DataSerie, $Data["Abscissa"]);
107
108        /* Compute the pie sum */
109        $SerieSum = $this->pDataObject->getSum($DataSerie);
110
111        /* Do we have data to draw? */
112        if ($SerieSum == 0) {
113            return PIE_SUMISNULL;
114        }
115
116        /* Dump the real number of data to draw */
117        $Values = [];
118        foreach ($Data["Series"][$DataSerie]["Data"] as $Key => $Value) {
119            if ($Value != 0) {
120                $Values[] = $Value;
121            }
122        }
123
124        /* Compute the wasted angular space between series */
125        if (count($Values) == 1) {
126            $WastedAngular = 0;
127        } else {
128            $WastedAngular = count($Values) * $DataGapAngle;
129        }
130
131        /* Compute the scale */
132        $ScaleFactor = (360 - $WastedAngular) / $SerieSum;
133
134        $RestoreShadow = $this->pChartObject->Shadow;
135        if ($this->pChartObject->Shadow) {
136            $this->pChartObject->Shadow = false;
137
138            $ShadowFormat = $Format;
139            $ShadowFormat["Shadow"] = true;
140            $this->draw2DPie(
141                $X + $this->pChartObject->ShadowX,
142                $Y + $this->pChartObject->ShadowY,
143                $ShadowFormat
144            );
145        }
146
147        /* Draw the polygon pie elements */
148        $Step = 360 / (2 * PI * $Radius);
149        $Offset = 0;
150        $ID = 0;
151        foreach ($Values as $Key => $Value) {
152            if ($Shadow) {
153                $Settings = [
154                    "R" => $this->pChartObject->ShadowR,
155                    "G" => $this->pChartObject->ShadowG,
156                    "B" => $this->pChartObject->ShadowB,
157                    "Alpha" => $this->pChartObject->Shadowa
158                ];
159            } else {
160                if (!isset($Palette[$ID]["R"])) {
161                    $Color = $this->pChartObject->getRandomColor();
162                    $Palette[$ID] = $Color;
163                    $this->pDataObject->savePalette($ID, $Color);
164                }
165                $Settings = [
166                    "R" => $Palette[$ID]["R"],
167                    "G" => $Palette[$ID]["G"],
168                    "B" => $Palette[$ID]["B"],
169                    "Alpha" => $Palette[$ID]["Alpha"]
170                ];
171            }
172
173            if (!$SecondPass && !$Shadow) {
174                if (!$Border) {
175                    $Settings["Surrounding"] = 10;
176                } else {
177                    $Settings["BorderR"] = $BorderR;
178                    $Settings["BorderG"] = $BorderG;
179                    $Settings["BorderB"] = $BorderB;
180                }
181            }
182
183            $EndAngle = $Offset + ($Value * $ScaleFactor);
184            if ($EndAngle > 360) {
185                $EndAngle = 360;
186            }
187
188            $Angle = ($EndAngle - $Offset) / 2 + $Offset;
189            if ($DataGapAngle == 0) {
190                $X0 = $X;
191                $Y0 = $Y;
192            } else {
193                $X0 = cos(($Angle - 90) * PI / 180) * $DataGapRadius + $X;
194                $Y0 = sin(($Angle - 90) * PI / 180) * $DataGapRadius + $Y;
195            }
196
197            $Plots = [$X0, $Y0];
198
199            for ($i = $Offset; $i <= $EndAngle; $i = $i + $Step) {
200                $Xc = cos(($i - 90) * PI / 180) * $Radius + $X;
201                $Yc = sin(($i - 90) * PI / 180) * $Radius + $Y;
202
203                if ($SecondPass && ($i < 90)) {
204                    $Yc++;
205                }
206                if ($SecondPass && ($i > 180 && $i < 270)) {
207                    $Xc++;
208                }
209                if ($SecondPass && ($i >= 270)) {
210                    $Xc++;
211                    $Yc++;
212                }
213
214                $Plots[] = $Xc;
215                $Plots[] = $Yc;
216            }
217
218            $this->pChartObject->drawPolygon($Plots, $Settings);
219            if ($RecordImageMap && !$Shadow) {
220                $this->pChartObject->addToImageMap(
221                    "POLY",
222                    $this->arraySerialize($Plots),
223                    $this->pChartObject->toHTMLColor(
224                        $Palette[$ID]["R"],
225                        $Palette[$ID]["G"],
226                        $Palette[$ID]["B"]
227                    ),
228                    $Data["Series"][$Data["Abscissa"]]["Data"][$Key],
229                    $Value
230                );
231            }
232
233            if ($DrawLabels && !$Shadow && !$SecondPass) {
234                if ($LabelColor == PIE_LABEL_COLOR_AUTO) {
235                    $Settings = [
236                        "FillR" => $Palette[$ID]["R"],
237                        "FillG" => $Palette[$ID]["G"],
238                        "FillB" => $Palette[$ID]["B"],
239                        "Alpha" => $Palette[$ID]["Alpha"]
240                    ];
241                } else {
242                    $Settings = [
243                        "FillR" => $LabelR,
244                        "FillG" => $LabelG,
245                        "FillB" => $LabelB,
246                        "Alpha" => $LabelAlpha
247                    ];
248                }
249
250                $Angle = ($EndAngle - $Offset) / 2 + $Offset;
251                $Xc = cos(($Angle - 90) * PI / 180) * $Radius + $X;
252                $Yc = sin(($Angle - 90) * PI / 180) * $Radius + $Y;
253
254                $Label = $Data["Series"][$Data["Abscissa"]]["Data"][$Key];
255
256                if ($LabelStacked) {
257                    $this->writePieLabel($Xc, $Yc, $Label, $Angle, $Settings, true, $X, $Y, $Radius);
258                } else {
259                    $this->writePieLabel($Xc, $Yc, $Label, $Angle, $Settings, false);
260                }
261            }
262
263            $Offset = $i + $DataGapAngle;
264            $ID++;
265        }
266
267        /* Second pass to smooth the angles */
268        if ($SecondPass) {
269            $Step = 360 / (2 * PI * $Radius);
270            $Offset = 0;
271            $ID = 0;
272            foreach ($Values as $Key => $Value) {
273                $FirstPoint = true;
274                if ($Shadow) {
275                    $Settings = [
276                        "R" => $this->pChartObject->ShadowR,
277                        "G" => $this->pChartObject->ShadowG,
278                        "B" => $this->pChartObject->ShadowB,
279                        "Alpha" => $this->pChartObject->Shadowa
280                    ];
281                } else {
282                    if ($Border) {
283                        $Settings = ["R" => $BorderR, "G" => $BorderG, "B" => $BorderB];
284                    } else {
285                        $Settings = [
286                            "R" => $Palette[$ID]["R"],
287                            "G" => $Palette[$ID]["G"],
288                            "B" => $Palette[$ID]["B"],
289                            "Alpha" => $Palette[$ID]["Alpha"]
290                        ];
291                    }
292                }
293
294                $EndAngle = $Offset + ($Value * $ScaleFactor);
295                if ($EndAngle > 360) {
296                    $EndAngle = 360;
297                }
298
299                if ($DataGapAngle == 0) {
300                    $X0 = $X;
301                    $Y0 = $Y;
302                } else {
303                    $Angle = ($EndAngle - $Offset) / 2 + $Offset;
304                    $X0 = cos(($Angle - 90) * PI / 180) * $DataGapRadius + $X;
305                    $Y0 = sin(($Angle - 90) * PI / 180) * $DataGapRadius + $Y;
306                }
307                $Plots[] = $X0;
308                $Plots[] = $Y0;
309
310                for ($i = $Offset; $i <= $EndAngle; $i = $i + $Step) {
311                    $Xc = cos(($i - 90) * PI / 180) * $Radius + $X;
312                    $Yc = sin(($i - 90) * PI / 180) * $Radius + $Y;
313
314                    if ($FirstPoint) {
315                        $this->pChartObject->drawLine($Xc, $Yc, $X0, $Y0, $Settings);
316                        $FirstPoint = false;
317                    }
318
319                    $this->pChartObject->drawAntialiasPixel($Xc, $Yc, $Settings);
320                }
321                $this->pChartObject->drawLine($Xc, $Yc, $X0, $Y0, $Settings);
322
323                if ($DrawLabels && !$Shadow) {
324                    if ($LabelColor == PIE_LABEL_COLOR_AUTO) {
325                        $Settings = [
326                            "FillR" => $Palette[$ID]["R"],
327                            "FillG" => $Palette[$ID]["G"],
328                            "FillB" => $Palette[$ID]["B"],
329                            "Alpha" => $Palette[$ID]["Alpha"]
330                        ];
331                    } else {
332                        $Settings = [
333                            "FillR" => $LabelR,
334                            "FillG" => $LabelG,
335                            "FillB" => $LabelB,
336                            "Alpha" => $LabelAlpha
337                        ];
338                    }
339
340                    $Angle = ($EndAngle - $Offset) / 2 + $Offset;
341                    $Xc = cos(($Angle - 90) * PI / 180) * $Radius + $X;
342                    $Yc = sin(($Angle - 90) * PI / 180) * $Radius + $Y;
343
344                    $Label = $Data["Series"][$Data["Abscissa"]]["Data"][$Key];
345
346                    if ($LabelStacked) {
347                        $this->writePieLabel($Xc, $Yc, $Label, $Angle, $Settings, true, $X, $Y, $Radius);
348                    } else {
349                        $this->writePieLabel($Xc, $Yc, $Label, $Angle, $Settings, false);
350                    }
351                }
352
353                $Offset = $i + $DataGapAngle;
354                $ID++;
355            }
356        }
357
358        if ($WriteValues != null && !$Shadow) {
359            $Step = 360 / (2 * PI * $Radius);
360            $Offset = 0;
361            $ID = count($Values) - 1;
362            $Settings = [
363                "Align" => TEXT_ALIGN_MIDDLEMIDDLE,
364                "R" => $ValueR,
365                "G" => $ValueG,
366                "B" => $ValueB,
367                "Alpha" => $ValueAlpha
368            ];
369            foreach ($Values as $Key => $Value) {
370                $EndAngle = ($Value * $ScaleFactor) + $Offset;
371                if ((int) $EndAngle > 360) {
372                    $EndAngle = 0;
373                }
374                $Angle = ($EndAngle - $Offset) / 2 + $Offset;
375
376                if ($ValuePosition == PIE_VALUE_OUTSIDE) {
377                    $Xc = cos(($Angle - 90) * PI / 180) * ($Radius + $ValuePadding) + $X;
378                    $Yc = sin(($Angle - 90) * PI / 180) * ($Radius + $ValuePadding) + $Y;
379                } else {
380                    $Xc = cos(($Angle - 90) * PI / 180) * ($Radius) / 2 + $X;
381                    $Yc = sin(($Angle - 90) * PI / 180) * ($Radius) / 2 + $Y;
382                }
383
384                if ($WriteValues == PIE_VALUE_PERCENTAGE) {
385                    $Display = round((100 / $SerieSum) * $Value, $Precision) . "%";
386                } elseif ($WriteValues == PIE_VALUE_NATURAL) {
387                    $Display = $Value . $ValueSuffix;
388                }
389                $this->pChartObject->drawText($Xc, $Yc, $Display, $Settings);
390
391                $Offset = $EndAngle + $DataGapAngle;
392                $ID--;
393            }
394        }
395
396        if ($DrawLabels && $LabelStacked) {
397            $this->writeShiftedLabels();
398        }
399
400        $this->pChartObject->Shadow = $RestoreShadow;
401
402        return PIE_RENDERED;
403    }
404
405    /**
406     * Draw a 3D pie chart
407     * @param int $X
408     * @param int $Y
409     * @param array $Format
410     * @return int
411     */
412    public function draw3DPie($X, $Y, array $Format = [])
413    {
414        /* Rendering layout */
415        $Radius = isset($Format["Radius"]) ? $Format["Radius"] : 80;
416        $Precision = isset($Format["Precision"]) ? $Format["Precision"] : 0;
417        $SkewFactor = isset($Format["SkewFactor"]) ? $Format["SkewFactor"] : .5;
418        $SliceHeight = isset($Format["SliceHeight"]) ? $Format["SliceHeight"] : 20;
419        $DataGapAngle = isset($Format["DataGapAngle"]) ? $Format["DataGapAngle"] : 0;
420        $DataGapRadius = isset($Format["DataGapRadius"]) ? $Format["DataGapRadius"] : 0;
421        $SecondPass = isset($Format["SecondPass"]) ? $Format["SecondPass"] : true;
422        $Border = isset($Format["Border"]) ? $Format["Border"] : false;
423        $Shadow = isset($Format["Shadow"]) ? $Format["Shadow"] : false;
424        $DrawLabels = isset($Format["DrawLabels"]) ? $Format["DrawLabels"] : false;
425        $LabelStacked = isset($Format["LabelStacked"]) ? $Format["LabelStacked"] : false;
426        $LabelColor = isset($Format["LabelColor"]) ? $Format["LabelColor"] : PIE_LABEL_COLOR_MANUAL;
427        $LabelR = isset($Format["LabelR"]) ? $Format["LabelR"] : 0;
428        $LabelG = isset($Format["LabelG"]) ? $Format["LabelG"] : 0;
429        $LabelB = isset($Format["LabelB"]) ? $Format["LabelB"] : 0;
430        $LabelAlpha = isset($Format["LabelAlpha"]) ? $Format["LabelAlpha"] : 100;
431        $WriteValues = isset($Format["WriteValues"]) ? $Format["WriteValues"] : null; //PIE_VALUE_PERCENTAGE
432        $ValuePosition = isset($Format["ValuePosition"]) ? $Format["ValuePosition"] : PIE_VALUE_INSIDE;
433        $ValuePadding = isset($Format["ValuePadding"]) ? $Format["ValuePadding"] : 15;
434        $ValueSuffix = isset($Format["ValueSuffix"]) ? $Format["ValueSuffix"] : "";
435        $ValueR = isset($Format["ValueR"]) ? $Format["ValueR"] : 255;
436        $ValueG = isset($Format["ValueG"]) ? $Format["ValueG"] : 255;
437        $ValueB = isset($Format["ValueB"]) ? $Format["ValueB"] : 255;
438        $ValueAlpha = isset($Format["ValueAlpha"]) ? $Format["ValueAlpha"] : 100;
439        $RecordImageMap = isset($Format["RecordImageMap"]) ? $Format["RecordImageMap"] : false;
440
441        /* Error correction for overlaying rounded corners */
442        if ($SkewFactor < .5) {
443            $SkewFactor = .5;
444        }
445
446        /* Data Processing */
447        $Data = $this->pDataObject->getData();
448        $Palette = $this->pDataObject->getPalette();
449
450        /* Do we have an abscissa serie defined? */
451        if ($Data["Abscissa"] == "") {
452            return PIE_NO_ABSCISSA;
453        }
454
455        /* Try to find the data serie */
456        $DataSerie = null;
457        foreach ($Data["Series"] as $SerieName => $SerieData) {
458            if ($SerieName != $Data["Abscissa"]) {
459                $DataSerie = $SerieName;
460            }
461        }
462
463        /* Do we have data to compute? */
464        if (!$DataSerie) {
465            return PIE_NO_DATASERIE;
466        }
467
468        /* Remove unused data */
469        list($Data, $Palette) = $this->clean0Values($Data, $Palette, $DataSerie, $Data["Abscissa"]);
470
471        /* Compute the pie sum */
472        $SerieSum = $this->pDataObject->getSum($DataSerie);
473
474        /* Do we have data to draw? */
475        if ($SerieSum == 0) {
476            return PIE_SUMISNULL;
477        }
478
479        /* Dump the real number of data to draw */
480        $Values = [];
481        foreach ($Data["Series"][$DataSerie]["Data"] as $Key => $Value) {
482            if ($Value != 0) {
483                $Values[] = $Value;
484            }
485        }
486
487        /* Compute the wasted angular space between series */
488        if (count($Values) == 1) {
489            $WastedAngular = 0;
490        } else {
491            $WastedAngular = count($Values) * $DataGapAngle;
492        }
493
494        /* Compute the scale */
495        $ScaleFactor = (360 - $WastedAngular) / $SerieSum;
496
497        $RestoreShadow = $this->pChartObject->Shadow;
498        if ($this->pChartObject->Shadow) {
499            $this->pChartObject->Shadow = false;
500        }
501
502        /* Draw the polygon pie elements */
503        $Step = 360 / (2 * PI * $Radius);
504        $Offset = 360;
505        $ID = count($Values) - 1;
506        $Values = array_reverse($Values);
507        $Slice = 0;
508        $Slices = [];
509        $SliceColors = [];
510        $Visible = [];
511        $SliceAngle = [];
512        foreach ($Values as $Key => $Value) {
513            if (!isset($Palette[$ID]["R"])) {
514                $Color = $this->pChartObject->getRandomColor();
515                $Palette[$ID] = $Color;
516                $this->pDataObject->savePalette($ID, $Color);
517            }
518            $Settings = [
519                "R" => $Palette[$ID]["R"],
520                "G" => $Palette[$ID]["G"],
521                "B" => $Palette[$ID]["B"],
522                "Alpha" => $Palette[$ID]["Alpha"]
523            ];
524
525            $SliceColors[$Slice] = $Settings;
526
527            $StartAngle = $Offset;
528            $EndAngle = $Offset - ($Value * $ScaleFactor);
529            if ($EndAngle < 0) {
530                $EndAngle = 0;
531            }
532
533            if ($StartAngle > 180) {
534                $Visible[$Slice]["Start"] = true;
535            } else {
536                $Visible[$Slice]["Start"] = true;
537            }
538            if ($EndAngle < 180) {
539                $Visible[$Slice]["End"] = false;
540            } else {
541                $Visible[$Slice]["End"] = true;
542            }
543
544            if ($DataGapAngle == 0) {
545                $X0 = $X;
546                $Y0 = $Y;
547            } else {
548                $Angle = ($EndAngle - $Offset) / 2 + $Offset;
549                $X0 = cos(($Angle - 90) * PI / 180) * $DataGapRadius + $X;
550                $Y0 = sin(($Angle - 90) * PI / 180) * $DataGapRadius * $SkewFactor + $Y;
551            }
552            $Slices[$Slice][] = $X0;
553            $Slices[$Slice][] = $Y0;
554            $SliceAngle[$Slice][] = 0;
555
556            for ($i = $Offset; $i >= $EndAngle; $i = $i - $Step) {
557                $Xc = cos(($i - 90) * PI / 180) * $Radius + $X;
558                $Yc = sin(($i - 90) * PI / 180) * $Radius * $SkewFactor + $Y;
559
560                if (($SecondPass || $RestoreShadow) && ($i < 90)) {
561                    $Yc++;
562                }
563                if (($SecondPass || $RestoreShadow) && ($i > 90 && $i < 180)) {
564                    $Xc++;
565                }
566                if (($SecondPass || $RestoreShadow) && ($i > 180 && $i < 270)) {
567                    $Xc++;
568                }
569                if (($SecondPass || $RestoreShadow) && ($i >= 270)) {
570                    $Xc++;
571                    $Yc++;
572                }
573
574                $Slices[$Slice][] = $Xc;
575                $Slices[$Slice][] = $Yc;
576                $SliceAngle[$Slice][] = $i;
577            }
578
579            $Offset = $i - $DataGapAngle;
580            $ID--;
581            $Slice++;
582        }
583
584        /* Draw the bottom shadow if needed */
585        if ($RestoreShadow && ($this->pChartObject->ShadowX != 0 || $this->pChartObject->ShadowY != 0)) {
586            foreach ($Slices as $SliceID => $Plots) {
587                $ShadowPie = [];
588                for ($i = 0; $i < count($Plots); $i = $i + 2) {
589                    $ShadowPie[] = $Plots[$i] + $this->pChartObject->ShadowX;
590                    $ShadowPie[] = $Plots[$i + 1] + $this->pChartObject->ShadowY;
591                }
592
593                $Settings = [
594                    "R" => $this->pChartObject->ShadowR,
595                    "G" => $this->pChartObject->ShadowG,
596                    "B" => $this->pChartObject->ShadowB,
597                    "Alpha" => $this->pChartObject->Shadowa,
598                    "NoBorder" => true
599                ];
600                $this->pChartObject->drawPolygon($ShadowPie, $Settings);
601            }
602
603            $Step = 360 / (2 * PI * $Radius);
604            $Offset = 360;
605            foreach ($Values as $Key => $Value) {
606                $EndAngle = $Offset - ($Value * $ScaleFactor);
607                if ($EndAngle < 0) {
608                    $EndAngle = 0;
609                }
610
611                for ($i = $Offset; $i >= $EndAngle; $i = $i - $Step) {
612                    $Xc = cos(($i - 90) * PI / 180) * $Radius + $X + $this->pChartObject->ShadowX;
613                    $Yc = sin(($i - 90) * PI / 180) * $Radius * $SkewFactor + $Y + $this->pChartObject->ShadowY;
614
615                    $this->pChartObject->drawAntialiasPixel($Xc, $Yc, $Settings);
616                }
617
618                $Offset = $i - $DataGapAngle;
619                $ID--;
620            }
621        }
622
623        /* Draw the bottom pie splice */
624        foreach ($Slices as $SliceID => $Plots) {
625            $Settings = $SliceColors[$SliceID];
626            $Settings["NoBorder"] = true;
627            $this->pChartObject->drawPolygon($Plots, $Settings);
628
629            if ($SecondPass) {
630                $Settings = $SliceColors[$SliceID];
631                if ($Border) {
632                    $Settings["R"] += 30;
633                    $Settings["G"] += 30;
634                    $Settings["B"] += 30;
635                }
636                /* Empty error handling */
637                if (isset($SliceAngle[$SliceID][1])) {
638                    $Angle = $SliceAngle[$SliceID][1];
639                    $Xc = cos(($Angle - 90) * PI / 180) * $Radius + $X;
640                    $Yc = sin(($Angle - 90) * PI / 180) * $Radius * $SkewFactor + $Y;
641                    $this->pChartObject->drawLine($Plots[0], $Plots[1], $Xc, $Yc, $Settings);
642
643                    $Angle = $SliceAngle[$SliceID][count($SliceAngle[$SliceID]) - 1];
644                    $Xc = cos(($Angle - 90) * PI / 180) * $Radius + $X;
645                    $Yc = sin(($Angle - 90) * PI / 180) * $Radius * $SkewFactor + $Y;
646                    $this->pChartObject->drawLine($Plots[0], $Plots[1], $Xc, $Yc, $Settings);
647                }
648            }
649        }
650
651        /* Draw the two vertical edges */
652        $Slices = array_reverse($Slices);
653        $SliceColors = array_reverse($SliceColors);
654        foreach ($Slices as $SliceID => $Plots) {
655            $Settings = $SliceColors[$SliceID];
656            $Settings["R"] += 10;
657            $Settings["G"] += 10;
658            $Settings["B"] += 10;
659            $Settings["NoBorder"] = true;
660            /* Empty error handling */
661            if ($Visible[$SliceID]["Start"] && isset($Plots[2])) {
662                $this->pChartObject->drawLine(
663                    $Plots[2],
664                    $Plots[3],
665                    $Plots[2],
666                    $Plots[3] - $SliceHeight,
667                    ["R" => $Settings["R"], "G" => $Settings["G"], "B" => $Settings["B"]]
668                );
669                $Border = [];
670                $Border[] = $Plots[0];
671                $Border[] = $Plots[1];
672                $Border[] = $Plots[0];
673                $Border[] = $Plots[1] - $SliceHeight;
674                $Border[] = $Plots[2];
675                $Border[] = $Plots[3] - $SliceHeight;
676                $Border[] = $Plots[2];
677                $Border[] = $Plots[3];
678                $this->pChartObject->drawPolygon($Border, $Settings);
679            }
680        }
681
682        $Slices = array_reverse($Slices);
683        $SliceColors = array_reverse($SliceColors);
684        foreach ($Slices as $SliceID => $Plots) {
685            $Settings = $SliceColors[$SliceID];
686            $Settings["R"] += 10;
687            $Settings["G"] += 10;
688            $Settings["B"] += 10;
689            $Settings["NoBorder"] = true;
690            if ($Visible[$SliceID]["End"]) {
691                $this->pChartObject->drawLine(
692                    $Plots[count($Plots) - 2],
693                    $Plots[count($Plots) - 1],
694                    $Plots[count($Plots) - 2],
695                    $Plots[count($Plots) - 1] - $SliceHeight,
696                    ["R" => $Settings["R"], "G" => $Settings["G"], "B" => $Settings["B"]]
697                );
698
699                $Border = [];
700                $Border[] = $Plots[0];
701                $Border[] = $Plots[1];
702                $Border[] = $Plots[0];
703                $Border[] = $Plots[1] - $SliceHeight;
704                $Border[] = $Plots[count($Plots) - 2];
705                $Border[] = $Plots[count($Plots) - 1] - $SliceHeight;
706                $Border[] = $Plots[count($Plots) - 2];
707                $Border[] = $Plots[count($Plots) - 1];
708                $this->pChartObject->drawPolygon($Border, $Settings);
709            }
710        }
711
712        /* Draw the rounded edges */
713        foreach ($Slices as $SliceID => $Plots) {
714            $Settings = $SliceColors[$SliceID];
715            $Settings["R"] += 10;
716            $Settings["G"] += 10;
717            $Settings["B"] += 10;
718            $Settings["NoBorder"] = true;
719
720            for ($j = 2; $j < count($Plots) - 2; $j = $j + 2) {
721                $Angle = $SliceAngle[$SliceID][$j / 2];
722                if ($Angle < 270 && $Angle > 90) {
723                    $Border = [];
724                    $Border[] = $Plots[$j];
725                    $Border[] = $Plots[$j + 1];
726                    $Border[] = $Plots[$j + 2];
727                    $Border[] = $Plots[$j + 3];
728                    $Border[] = $Plots[$j + 2];
729                    $Border[] = $Plots[$j + 3] - $SliceHeight;
730                    $Border[] = $Plots[$j];
731                    $Border[] = $Plots[$j + 1] - $SliceHeight;
732                    $this->pChartObject->drawPolygon($Border, $Settings);
733                }
734            }
735
736            if ($SecondPass) {
737                $Settings = $SliceColors[$SliceID];
738                if (count($Border)) {
739                    $Settings["R"] += 30;
740                    $Settings["G"] += 30;
741                    $Settings["B"] += 30;
742                }
743
744                /* Empty error handling */
745                if (isset($SliceAngle[$SliceID][1])) {
746                    $Angle = $SliceAngle[$SliceID][1];
747                    if ($Angle < 270 && $Angle > 90) {
748                        $Xc = cos(($Angle - 90) * PI / 180) * $Radius + $X;
749                        $Yc = sin(($Angle - 90) * PI / 180) * $Radius * $SkewFactor + $Y;
750                        $this->pChartObject->drawLine($Xc, $Yc, $Xc, $Yc - $SliceHeight, $Settings);
751                    }
752                }
753
754                $Angle = $SliceAngle[$SliceID][count($SliceAngle[$SliceID]) - 1];
755                if ($Angle < 270 && $Angle > 90) {
756                    $Xc = cos(($Angle - 90) * PI / 180) * $Radius + $X;
757                    $Yc = sin(($Angle - 90) * PI / 180) * $Radius * $SkewFactor + $Y;
758                    $this->pChartObject->drawLine($Xc, $Yc, $Xc, $Yc - $SliceHeight, $Settings);
759                }
760
761                if (isset($SliceAngle[$SliceID][1])
762                    && $SliceAngle[$SliceID][1] > 270
763                    && $SliceAngle[$SliceID][count($SliceAngle[$SliceID]) - 1] < 270
764                ) {
765                    $Xc = cos((270 - 90) * PI / 180) * $Radius + $X;
766                    $Yc = sin((270 - 90) * PI / 180) * $Radius * $SkewFactor + $Y;
767                    $this->pChartObject->drawLine($Xc, $Yc, $Xc, $Yc - $SliceHeight, $Settings);
768                }
769
770                if (isset($SliceAngle[$SliceID][1])
771                    && $SliceAngle[$SliceID][1] > 90
772                    && $SliceAngle[$SliceID][count($SliceAngle[$SliceID]) - 1] < 90
773                ) {
774                    $Xc = cos((0) * PI / 180) * $Radius + $X;
775                    $Yc = sin((0) * PI / 180) * $Radius * $SkewFactor + $Y;
776                    $this->pChartObject->drawLine($Xc, $Yc, $Xc, $Yc - $SliceHeight, $Settings);
777                }
778            }
779        }
780
781        /* Draw the top splice */
782        foreach ($Slices as $SliceID => $Plots) {
783            $Settings = $SliceColors[$SliceID];
784            $Settings["R"] += 20;
785            $Settings["G"] += 20;
786            $Settings["B"] += 20;
787
788            $Top = [];
789            for ($j = 0; $j < count($Plots); $j = $j + 2) {
790                $Top[] = $Plots[$j];
791                $Top[] = $Plots[$j + 1] - $SliceHeight;
792            }
793            $this->pChartObject->drawPolygon($Top, $Settings);
794
795            if ($RecordImageMap && !$Shadow) {
796                $this->pChartObject->addToImageMap(
797                    "POLY",
798                    $this->arraySerialize($Top),
799                    $this->pChartObject->toHTMLColor(
800                        $Settings["R"],
801                        $Settings["G"],
802                        $Settings["B"]
803                    ),
804                    $Data["Series"][$Data["Abscissa"]]["Data"][count($Slices) - $SliceID - 1],
805                    $Values[$SliceID]
806                );
807            }
808        }
809
810
811        /* Second pass to smooth the angles */
812        if ($SecondPass) {
813            $Step = 360 / (2 * PI * $Radius);
814            $Offset = 360;
815            $ID = count($Values) - 1;
816            foreach ($Values as $Key => $Value) {
817                $FirstPoint = true;
818                if ($Shadow) {
819                    $Settings = [
820                        "R" => $this->pChartObject->ShadowR,
821                        "G" => $this->pChartObject->ShadowG,
822                        "B" => $this->pChartObject->ShadowB,
823                        "Alpha" => $this->pChartObject->Shadowa
824                    ];
825                } else {
826                    if ($Border) {
827                        $Settings = [
828                            "R" => $Palette[$ID]["R"] + 30,
829                            "G" => $Palette[$ID]["G"] + 30,
830                            "B" => $Palette[$ID]["B"] + 30,
831                            "Alpha" => $Palette[$ID]["Alpha"]
832                        ];
833                    } else {
834                        $Settings = [
835                            "R" => $Palette[$ID]["R"],
836                            "G" => $Palette[$ID]["G"],
837                            "B" => $Palette[$ID]["B"],
838                            "Alpha" => $Palette[$ID]["Alpha"]
839                        ];
840                    }
841                }
842
843                $EndAngle = $Offset - ($Value * $ScaleFactor);
844                if ($EndAngle < 0) {
845                    $EndAngle = 0;
846                }
847
848                if ($DataGapAngle == 0) {
849                    $X0 = $X;
850                    $Y0 = $Y - $SliceHeight;
851                } else {
852                    $Angle = ($EndAngle - $Offset) / 2 + $Offset;
853                    $X0 = cos(($Angle - 90) * PI / 180) * $DataGapRadius + $X;
854                    $Y0 = sin(($Angle - 90) * PI / 180) * $DataGapRadius * $SkewFactor + $Y - $SliceHeight;
855                }
856                $Plots[] = $X0;
857                $Plots[] = $Y0;
858
859                for ($i = $Offset; $i >= $EndAngle; $i = $i - $Step) {
860                    $Xc = cos(($i - 90) * PI / 180) * $Radius + $X;
861                    $Yc = sin(($i - 90) * PI / 180) * $Radius * $SkewFactor + $Y - $SliceHeight;
862
863                    if ($FirstPoint) {
864                        $this->pChartObject->drawLine($Xc, $Yc, $X0, $Y0, $Settings);
865                        $FirstPoint = false;
866                    }
867
868                    $this->pChartObject->drawAntialiasPixel($Xc, $Yc, $Settings);
869                    if ($i < 270 && $i > 90) {
870                        $this->pChartObject->drawAntialiasPixel($Xc, $Yc + $SliceHeight, $Settings);
871                    }
872                }
873                $this->pChartObject->drawLine($Xc, $Yc, $X0, $Y0, $Settings);
874
875                $Offset = $i - $DataGapAngle;
876                $ID--;
877            }
878        }
879
880        if ($WriteValues != null) {
881            $Step = 360 / (2 * PI * $Radius);
882            $Offset = 360;
883            $ID = count($Values) - 1;
884            $Settings = [
885                "Align" => TEXT_ALIGN_MIDDLEMIDDLE,
886                "R" => $ValueR,
887                "G" => $ValueG,
888                "B" => $ValueB,
889                "Alpha" => $ValueAlpha
890            ];
891            foreach ($Values as $Key => $Value) {
892                $EndAngle = $Offset - ($Value * $ScaleFactor);
893                if ($EndAngle < 0) {
894                    $EndAngle = 0;
895                }
896
897                $Angle = ($EndAngle - $Offset) / 2 + $Offset;
898
899                if ($ValuePosition == PIE_VALUE_OUTSIDE) {
900                    $Xc = cos(($Angle - 90) * PI / 180) * ($Radius + $ValuePadding) + $X;
901                    $Yc = sin(($Angle - 90) * PI / 180)
902                        * (($Radius * $SkewFactor) + $ValuePadding)
903                        + $Y - $SliceHeight
904                    ;
905                } else {
906                    $Xc = cos(($Angle - 90) * PI / 180) * ($Radius) / 2 + $X;
907                    $Yc = sin(($Angle - 90) * PI / 180) * ($Radius * $SkewFactor) / 2 + $Y - $SliceHeight;
908                }
909
910                if ($WriteValues == PIE_VALUE_PERCENTAGE) {
911                    $Display = round((100 / $SerieSum) * $Value, $Precision) . "%";
912                } elseif ($WriteValues == PIE_VALUE_NATURAL) {
913                    $Display = $Value . $ValueSuffix;
914                }
915
916                $this->pChartObject->drawText($Xc, $Yc, $Display, $Settings);
917
918                $Offset = $EndAngle - $DataGapAngle;
919                $ID--;
920            }
921        }
922
923        if ($DrawLabels) {
924            $Step = 360 / (2 * PI * $Radius);
925            $Offset = 360;
926            $ID = count($Values) - 1;
927            foreach ($Values as $Key => $Value) {
928                if ($LabelColor == PIE_LABEL_COLOR_AUTO) {
929                    $Settings = [
930                        "FillR" => $Palette[$ID]["R"],
931                        "FillG" => $Palette[$ID]["G"],
932                        "FillB" => $Palette[$ID]["B"],
933                        "Alpha" => $Palette[$ID]["Alpha"]
934                    ];
935                } else {
936                    $Settings = [
937                        "FillR" => $LabelR,
938                        "FillG" => $LabelG,
939                        "FillB" => $LabelB,
940                        "Alpha" => $LabelAlpha
941                    ];
942                }
943
944                $EndAngle = $Offset - ($Value * $ScaleFactor);
945                if ($EndAngle < 0) {
946                    $EndAngle = 0;
947                }
948
949                $Angle = ($EndAngle - $Offset) / 2 + $Offset;
950                $Xc = cos(($Angle - 90) * PI / 180) * $Radius + $X;
951                $Yc = sin(($Angle - 90) * PI / 180) * $Radius * $SkewFactor + $Y - $SliceHeight;
952
953                if (isset($Data["Series"][$Data["Abscissa"]]["Data"][$ID])) {
954                    $Label = $Data["Series"][$Data["Abscissa"]]["Data"][$ID];
955
956                    if ($LabelStacked) {
957                        $this->writePieLabel($Xc, $Yc, $Label, $Angle, $Settings, true, $X, $Y, $Radius, true);
958                    } else {
959                        $this->writePieLabel($Xc, $Yc, $Label, $Angle, $Settings, false);
960                    }
961                }
962
963                $Offset = $EndAngle - $DataGapAngle;
964                $ID--;
965            }
966        }
967
968        if ($DrawLabels && $LabelStacked) {
969            $this->writeShiftedLabels();
970        }
971
972        $this->pChartObject->Shadow = $RestoreShadow;
973
974        return PIE_RENDERED;
975    }
976
977    /**
978     * Draw the legend of pie chart
979     * @param int $X
980     * @param int $Y
981     * @param array $Format
982     * @return int
983     */
984    public function drawPieLegend($X, $Y, array $Format = [])
985    {
986        $FontName = isset($Format["FontName"]) ? $Format["FontName"] : $this->pChartObject->FontName;
987        $FontSize = isset($Format["FontSize"]) ? $Format["FontSize"] : $this->pChartObject->FontSize;
988        $FontR = isset($Format["FontR"]) ? $Format["FontR"] : $this->pChartObject->FontColorR;
989        $FontG = isset($Format["FontG"]) ? $Format["FontG"] : $this->pChartObject->FontColorG;
990        $FontB = isset($Format["FontB"]) ? $Format["FontB"] : $this->pChartObject->FontColorB;
991        $BoxSize = isset($Format["BoxSize"]) ? $Format["BoxSize"] : 5;
992        $Margin = isset($Format["Margin"]) ? $Format["Margin"] : 5;
993        $R = isset($Format["R"]) ? $Format["R"] : 200;
994        $G = isset($Format["G"]) ? $Format["G"] : 200;
995        $B = isset($Format["B"]) ? $Format["B"] : 200;
996        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
997        $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : 255;
998        $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : 255;
999        $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : 255;
1000        $Surrounding = isset($Format["Surrounding"]) ? $Format["Surrounding"] : null;
1001        $Style = isset($Format["Style"]) ? $Format["Style"] : LEGEND_ROUND;
1002        $Mode = isset($Format["Mode"]) ? $Format["Mode"] : LEGEND_VERTICAL;
1003
1004        if ($Surrounding != null) {
1005            $BorderR = $R + $Surrounding;
1006            $BorderG = $G + $Surrounding;
1007            $BorderB = $B + $Surrounding;
1008        }
1009
1010        $YStep = max($this->pChartObject->FontSize, $BoxSize) + 5;
1011        $XStep = $BoxSize + 5;
1012
1013        /* Data Processing */
1014        $Data = $this->pDataObject->getData();
1015        $Palette = $this->pDataObject->getPalette();
1016
1017        /* Do we have an abscissa serie defined? */
1018        if ($Data["Abscissa"] == "") {
1019            return PIE_NO_ABSCISSA;
1020        }
1021
1022        $Boundaries = [];
1023        $Boundaries["L"] = $X;
1024        $Boundaries["T"] = $Y;
1025        $Boundaries["R"] = 0;
1026        $Boundaries["B"] = 0;
1027        $vY = $Y;
1028        $vX = $X;
1029        foreach ($Data["Series"][$Data["Abscissa"]]["Data"] as $Key => $Value) {
1030            $BoxArray = $this->pChartObject->getTextBox(
1031                $vX + $BoxSize + 4,
1032                $vY + $BoxSize / 2,
1033                $FontName,
1034                $FontSize,
1035                0,
1036                $Value
1037            );
1038
1039            if ($Mode == LEGEND_VERTICAL) {
1040                if ($Boundaries["T"] > $BoxArray[2]["Y"] + $BoxSize / 2) {
1041                    $Boundaries["T"] = $BoxArray[2]["Y"] + $BoxSize / 2;
1042                }
1043                if ($Boundaries["R"] < $BoxArray[1]["X"] + 2) {
1044                    $Boundaries["R"] = $BoxArray[1]["X"] + 2;
1045                }
1046                if ($Boundaries["B"] < $BoxArray[1]["Y"] + 2 + $BoxSize / 2) {
1047                    $Boundaries["B"] = $BoxArray[1]["Y"] + 2 + $BoxSize / 2;
1048                }
1049                $vY = $vY + $YStep;
1050            } elseif ($Mode == LEGEND_HORIZONTAL) {
1051                if ($Boundaries["T"] > $BoxArray[2]["Y"] + $BoxSize / 2) {
1052                    $Boundaries["T"] = $BoxArray[2]["Y"] + $BoxSize / 2;
1053                }
1054                if ($Boundaries["R"] < $BoxArray[1]["X"] + 2) {
1055                    $Boundaries["R"] = $BoxArray[1]["X"] + 2;
1056                }
1057                if ($Boundaries["B"] < $BoxArray[1]["Y"] + 2 + $BoxSize / 2) {
1058                    $Boundaries["B"] = $BoxArray[1]["Y"] + 2 + $BoxSize / 2;
1059                }
1060                $vX = $Boundaries["R"] + $XStep;
1061            }
1062        }
1063        $vY = $vY - $YStep;
1064        $vX = $vX - $XStep;
1065
1066        $TopOffset = $Y - $Boundaries["T"];
1067        if ($Boundaries["B"] - ($vY + $BoxSize) < $TopOffset) {
1068            $Boundaries["B"] = $vY + $BoxSize + $TopOffset;
1069        }
1070
1071        if ($Style == LEGEND_ROUND) {
1072            $this->pChartObject->drawRoundedFilledRectangle(
1073                $Boundaries["L"] - $Margin,
1074                $Boundaries["T"] - $Margin,
1075                $Boundaries["R"] + $Margin,
1076                $Boundaries["B"] + $Margin,
1077                $Margin,
1078                [
1079                    "R" => $R,
1080                    "G" => $G,
1081                    "B" => $B,
1082                    "Alpha" => $Alpha,
1083                    "BorderR" => $BorderR,
1084                    "BorderG" => $BorderG,
1085                    "BorderB" => $BorderB
1086                ]
1087            );
1088        } elseif ($Style == LEGEND_BOX) {
1089            $this->pChartObject->drawFilledRectangle(
1090                $Boundaries["L"] - $Margin,
1091                $Boundaries["T"] - $Margin,
1092                $Boundaries["R"] + $Margin,
1093                $Boundaries["B"] + $Margin,
1094                [
1095                    "R" => $R,
1096                    "G" => $G,
1097                    "B" => $B,
1098                    "Alpha" => $Alpha,
1099                    "BorderR" => $BorderR,
1100                    "BorderG" => $BorderG,
1101                    "BorderB" => $BorderB
1102                ]
1103            );
1104        }
1105        $RestoreShadow = $this->pChartObject->Shadow;
1106        $this->pChartObject->Shadow = false;
1107        foreach ($Data["Series"][$Data["Abscissa"]]["Data"] as $Key => $Value) {
1108            $R = $Palette[$Key]["R"];
1109            $G = $Palette[$Key]["G"];
1110            $B = $Palette[$Key]["B"];
1111
1112            $this->pChartObject->drawFilledRectangle(
1113                $X + 1,
1114                $Y + 1,
1115                $X + $BoxSize + 1,
1116                $Y + $BoxSize + 1,
1117                ["R" => 0, "G" => 0, "B" => 0, "Alpha" => 20]
1118            );
1119            $this->pChartObject->drawFilledRectangle(
1120                $X,
1121                $Y,
1122                $X + $BoxSize,
1123                $Y + $BoxSize,
1124                ["R" => $R, "G" => $G, "B" => $B, "Surrounding" => 20]
1125            );
1126            if ($Mode == LEGEND_VERTICAL) {
1127                $this->pChartObject->drawText(
1128                    $X + $BoxSize + 4,
1129                    $Y + $BoxSize / 2,
1130                    $Value,
1131                    [
1132                        "R" => $FontR,
1133                        "G" => $FontG,
1134                        "B" => $FontB,
1135                        "Align" => TEXT_ALIGN_MIDDLELEFT,
1136                        "FontName" => $FontName,
1137                        "FontSize" => $FontSize
1138                    ]
1139                );
1140                $Y = $Y + $YStep;
1141            } elseif ($Mode == LEGEND_HORIZONTAL) {
1142                $BoxArray = $this->pChartObject->drawText(
1143                    $X + $BoxSize + 4,
1144                    $Y + $BoxSize / 2,
1145                    $Value,
1146                    [
1147                        "R" => $FontR,
1148                        "G" => $FontG,
1149                        "B" => $FontB,
1150                        "Align" => TEXT_ALIGN_MIDDLELEFT,
1151                        "FontName" => $FontName,
1152                        "FontSize" => $FontSize
1153                    ]
1154                );
1155                $X = $BoxArray[1]["X"] + 2 + $XStep;
1156            }
1157        }
1158
1159        $this->Shadow = $RestoreShadow;
1160    }
1161
1162    /**
1163     * Set the color of the specified slice
1164     * @param mixed $SliceID
1165     * @param array $Format
1166     */
1167    public function setSliceColor($SliceID, array $Format = [])
1168    {
1169        $R = isset($Format["R"]) ? $Format["R"] : 0;
1170        $G = isset($Format["G"]) ? $Format["G"] : 0;
1171        $B = isset($Format["B"]) ? $Format["B"] : 0;
1172        $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
1173
1174        $this->pDataObject->Palette[$SliceID]["R"] = $R;
1175        $this->pDataObject->Palette[$SliceID]["G"] = $G;
1176        $this->pDataObject->Palette[$SliceID]["B"] = $B;
1177        $this->pDataObject->Palette[$SliceID]["Alpha"] = $Alpha;
1178    }
1179
1180    /**
1181     * Internally used compute the label positions
1182     * @param int $X
1183     * @param int $Y
1184     * @param string $Label
1185     * @param int|float $Angle
1186     * @param array $Settings
1187     * @param boolean $Stacked
1188     * @param int $Xc
1189     * @param int $Yc
1190     * @param int $Radius
1191     * @param boolean $Reversed
1192     */
1193    public function writePieLabel(
1194        $X,
1195        $Y,
1196        $Label,
1197        $Angle,
1198        $Settings,
1199        $Stacked,
1200        $Xc = 0,
1201        $Yc = 0,
1202        $Radius = 0,
1203        $Reversed = false
1204    ) {
1205        $LabelOffset = 30;
1206        $FontName = $this->pChartObject->FontName;
1207        $FontSize = $this->pChartObject->FontSize;
1208
1209        if (!$Stacked) {
1210            $Settings["Angle"] = 360 - $Angle;
1211            $Settings["Length"] = 25;
1212            $Settings["Size"] = 8;
1213
1214            $this->pChartObject->drawArrowLabel($X, $Y, " " . $Label . " ", $Settings);
1215        } else {
1216            $X2 = cos(deg2rad($Angle - 90)) * 20 + $X;
1217            $Y2 = sin(deg2rad($Angle - 90)) * 20 + $Y;
1218
1219            $TxtPos = $this->pChartObject->getTextBox($X, $Y, $FontName, $FontSize, 0, $Label);
1220            $Height = $TxtPos[0]["Y"] - $TxtPos[2]["Y"];
1221            $YTop = $Y2 - $Height / 2 - 2;
1222            $YBottom = $Y2 + $Height / 2 + 2;
1223
1224            if (count($this->LabelPos)) {
1225                $Done = false;
1226                foreach ($this->LabelPos as $Key => $Settings) {
1227                    if (!$Done) {
1228                        $yTopAboveTopBelowBottom = ($YTop >= $Settings["YTop"] && $YTop <= $Settings["YBottom"]);
1229                        $yBottomAboveTopBelowBottom = ($YBottom >= $Settings["YTop"]
1230                            && $YBottom <= $Settings["YBottom"]
1231                        );
1232
1233                        if ($Angle <= 90
1234                            && ($yTopAboveTopBelowBottom || $yBottomAboveTopBelowBottom)
1235                        ) {
1236                            $this->shift(0, 180, -($Height + 2), $Reversed);
1237                            $Done = true;
1238                        }
1239                        if ($Angle > 90
1240                            && $Angle <= 180
1241                            && ($yTopAboveTopBelowBottom || $yBottomAboveTopBelowBottom)
1242                        ) {
1243                            $this->shift(0, 180, -($Height + 2), $Reversed);
1244                            $Done = true;
1245                        }
1246                        if ($Angle > 180
1247                            && $Angle <= 270
1248                            && ($yTopAboveTopBelowBottom || $yBottomAboveTopBelowBottom)
1249                        ) {
1250                            $this->shift(180, 360, ($Height + 2), $Reversed);
1251                            $Done = true;
1252                        }
1253                        if ($Angle > 270
1254                            && $Angle <= 360
1255                            && ($yTopAboveTopBelowBottom || $yBottomAboveTopBelowBottom)
1256                        ) {
1257                            $this->shift(180, 360, ($Height + 2), $Reversed);
1258                            $Done = true;
1259                        }
1260                    }
1261                }
1262            }
1263
1264            $LabelSettings = [
1265                "YTop" => $YTop,
1266                "YBottom" => $YBottom,
1267                "Label" => $Label,
1268                "Angle" => $Angle,
1269                "X1" => $X,
1270                "Y1" => $Y,
1271                "X2" => $X2,
1272                "Y2" => $Y2
1273            ];
1274            if ($Angle <= 180) {
1275                $LabelSettings["X3"] = $Xc + $Radius + $LabelOffset;
1276            }
1277            if ($Angle > 180) {
1278                $LabelSettings["X3"] = $Xc - $Radius - $LabelOffset;
1279            }
1280            $this->LabelPos[] = $LabelSettings;
1281        }
1282    }
1283
1284    /**
1285     * Internally used to shift label positions
1286     * @param int $StartAngle
1287     * @param int $EndAngle
1288     * @param int $Offset
1289     * @param boolean $Reversed
1290     */
1291    public function shift($StartAngle, $EndAngle, $Offset, $Reversed)
1292    {
1293        if ($Reversed) {
1294            $Offset = -$Offset;
1295        }
1296        foreach ($this->LabelPos as $Key => $Settings) {
1297            if ($Settings["Angle"] > $StartAngle && $Settings["Angle"] <= $EndAngle) {
1298                $this->LabelPos[$Key]["YTop"] = $Settings["YTop"] + $Offset;
1299                $this->LabelPos[$Key]["YBottom"] = $Settings["YBottom"] + $Offset;
1300                $this->LabelPos[$Key]["Y2"] = $Settings["Y2"] + $Offset;
1301            }
1302        }
1303    }
1304
1305    /**
1306     * Internally used to write the re-computed labels
1307     * @return null|int
1308     */
1309    public function writeShiftedLabels()
1310    {
1311        if (!count($this->LabelPos)) {
1312            return 0;
1313        }
1314        foreach ($this->LabelPos as $Settings) {
1315            $X1 = $Settings["X1"];
1316            $Y1 = $Settings["Y1"];
1317            $X2 = $Settings["X2"];
1318            $Y2 = $Settings["Y2"];
1319            $X3 = $Settings["X3"];
1320            $Angle = $Settings["Angle"];
1321            $Label = $Settings["Label"];
1322
1323            $this->pChartObject->drawArrow($X2, $Y2, $X1, $Y1, ["Size" => 8]);
1324            if ($Angle <= 180) {
1325                $this->pChartObject->drawLine($X2, $Y2, $X3, $Y2);
1326                $this->pChartObject->drawText($X3 + 2, $Y2, $Label, ["Align" => TEXT_ALIGN_MIDDLELEFT]);
1327            } else {
1328                $this->pChartObject->drawLine($X2, $Y2, $X3, $Y2);
1329                $this->pChartObject->drawText($X3 - 2, $Y2, $Label, ["Align" => TEXT_ALIGN_MIDDLERIGHT]);
1330            }
1331        }
1332    }
1333
1334    /**
1335     * Draw a ring chart
1336     * @param int $X
1337     * @param int $Y
1338     * @param array $Format
1339     * @return int
1340     */
1341    public function draw2DRing($X, $Y, array $Format = [])
1342    {
1343        $OuterRadius = isset($Format["Radius"]) ? $Format["Radius"] : 60; // For compatibility
1344        $OuterRadius = isset($Format["OuterRadius"]) ? $Format["OuterRadius"] : $OuterRadius;
1345        $Precision = isset($Format["Precision"]) ? $Format["Precision"] : 0;
1346        $InnerRadius = isset($Format["InnerRadius"]) ? $Format["InnerRadius"] : 30;
1347        $Border = isset($Format["Border"]) ? $Format["Border"] : false;
1348        $BorderR = isset($Format["BorderR"]) ? $Format["BorderR"] : 255;
1349        $BorderG = isset($Format["BorderG"]) ? $Format["BorderG"] : 255;
1350        $BorderB = isset($Format["BorderB"]) ? $Format["BorderB"] : 255;
1351        $BorderAlpha = isset($Format["BorderAlpha"]) ? $Format["BorderAlpha"] : 100;
1352        $Shadow = isset($Format["Shadow"]) ? $Format["Shadow"] : false;
1353        $DrawLabels = isset($Format["DrawLabels"]) ? $Format["DrawLabels"] : false;
1354        $LabelStacked = isset($Format["LabelStacked"]) ? $Format["LabelStacked"] : false;
1355        $LabelColor = isset($Format["LabelColor"]) ? $Format["LabelColor"] : PIE_LABEL_COLOR_MANUAL;
1356        $LabelR = isset($Format["LabelR"]) ? $Format["LabelR"] : 0;
1357        $LabelG = isset($Format["LabelG"]) ? $Format["LabelG"] : 0;
1358        $LabelB = isset($Format["LabelB"]) ? $Format["LabelB"] : 0;
1359        $LabelAlpha = isset($Format["LabelAlpha"]) ? $Format["LabelAlpha"] : 100;
1360        $WriteValues = isset($Format["WriteValues"]) ? $Format["WriteValues"] : null; //PIE_VALUE_PERCENTAGE
1361        $ValuePadding = isset($Format["ValuePadding"]) ? $Format["ValuePadding"] : 5;
1362        $ValuePosition = isset($Format["ValuePosition"]) ? $Format["ValuePosition"] : PIE_VALUE_OUTSIDE;
1363        $ValueSuffix = isset($Format["ValueSuffix"]) ? $Format["ValueSuffix"] : "";
1364        $ValueR = isset($Format["ValueR"]) ? $Format["ValueR"] : 255;
1365        $ValueG = isset($Format["ValueG"]) ? $Format["ValueG"] : 255;
1366        $ValueB = isset($Format["ValueB"]) ? $Format["ValueB"] : 255;
1367        $ValueAlpha = isset($Format["ValueAlpha"]) ? $Format["ValueAlpha"] : 100;
1368        $RecordImageMap = isset($Format["RecordImageMap"]) ? $Format["RecordImageMap"] : false;
1369
1370        /* Data Processing */
1371        $Data = $this->pDataObject->getData();
1372        $Palette = $this->pDataObject->getPalette();
1373
1374        /* Do we have an abscissa serie defined? */
1375        if ($Data["Abscissa"] == "") {
1376            return PIE_NO_ABSCISSA;
1377        }
1378
1379        /* Try to find the data serie */
1380        $DataSerie = null;
1381        foreach ($Data["Series"] as $SerieName => $SerieData) {
1382            if ($SerieName != $Data["Abscissa"]) {
1383                $DataSerie = $SerieName;
1384            }
1385        }
1386
1387        /* Do we have data to compute? */
1388        if (!$DataSerie) {
1389            return PIE_NO_DATASERIE;
1390        }
1391
1392        /* Remove unused data */
1393        list($Data, $Palette) = $this->clean0Values($Data, $Palette, $DataSerie, $Data["Abscissa"]);
1394
1395        /* Compute the pie sum */
1396        $SerieSum = $this->pDataObject->getSum($DataSerie);
1397
1398        /* Do we have data to draw? */
1399        if ($SerieSum == 0) {
1400            return PIE_SUMISNULL;
1401        }
1402
1403        /* Dump the real number of data to draw */
1404        $Values = [];
1405        foreach ($Data["Series"][$DataSerie]["Data"] as $Key => $Value) {
1406            if ($Value != 0) {
1407                $Values[] = $Value;
1408            }
1409        }
1410
1411        /* Compute the wasted angular space between series */
1412        if (count($Values) == 1) {
1413            $WastedAngular = 0;
1414        } else {
1415            $WastedAngular = 0;
1416        } // count($Values)
1417
1418        /* Compute the scale */
1419        $ScaleFactor = (360 - $WastedAngular) / $SerieSum;
1420
1421        $RestoreShadow = $this->pChartObject->Shadow;
1422        if ($this->pChartObject->Shadow) {
1423            $this->pChartObject->Shadow = false;
1424
1425            $ShadowFormat = $Format;
1426            $ShadowFormat["Shadow"] = true;
1427            $this->draw2DRing(
1428                $X + $this->pChartObject->ShadowX,
1429                $Y + $this->pChartObject->ShadowY,
1430                $ShadowFormat
1431            );
1432        }
1433
1434        /* Draw the polygon pie elements */
1435        $Step = 360 / (2 * PI * $OuterRadius);
1436        $Offset = 0;
1437        $ID = 0;
1438        foreach ($Values as $Key => $Value) {
1439            if ($Shadow) {
1440                $Settings = [
1441                    "R" => $this->pChartObject->ShadowR,
1442                    "G" => $this->pChartObject->ShadowG,
1443                    "B" => $this->pChartObject->ShadowB,
1444                    "Alpha" => $this->pChartObject->Shadowa
1445                ];
1446                $BorderColor = $Settings;
1447            } else {
1448                if (!isset($Palette[$ID]["R"])) {
1449                    $Color = $this->pChartObject->getRandomColor();
1450                    $Palette[$ID] = $Color;
1451                    $this->pDataObject->savePalette($ID, $Color);
1452                }
1453                $Settings = [
1454                    "R" => $Palette[$ID]["R"],
1455                    "G" => $Palette[$ID]["G"],
1456                    "B" => $Palette[$ID]["B"],
1457                    "Alpha" => $Palette[$ID]["Alpha"]
1458                ];
1459
1460                if ($Border) {
1461                    $BorderColor = [
1462                        "R" => $BorderR,
1463                        "G" => $BorderG,
1464                        "B" => $BorderB,
1465                        "Alpha" => $BorderAlpha
1466                    ];
1467                } else {
1468                    $BorderColor = $Settings;
1469                }
1470            }
1471
1472            $Plots = [];
1473            $Boundaries = [];
1474            $AAPixels = [];
1475            $EndAngle = $Offset + ($Value * $ScaleFactor);
1476            if ($EndAngle > 360) {
1477                $EndAngle = 360;
1478            }
1479            for ($i = $Offset; $i <= $EndAngle; $i = $i + $Step) {
1480                $Xc = cos(($i - 90) * PI / 180) * $OuterRadius + $X;
1481                $Yc = sin(($i - 90) * PI / 180) * $OuterRadius + $Y;
1482
1483                if (!isset($Boundaries[0]["X1"])) {
1484                    $Boundaries[0]["X1"] = $Xc;
1485                    $Boundaries[0]["Y1"] = $Yc;
1486                }
1487                $AAPixels[] = [$Xc, $Yc];
1488
1489                if ($i < 90) {
1490                    $Yc++;
1491                }
1492                if ($i > 180 && $i < 270) {
1493                    $Xc++;
1494                }
1495                if ($i >= 270) {
1496                    $Xc++;
1497                    $Yc++;
1498                }
1499
1500                $Plots[] = $Xc;
1501                $Plots[] = $Yc;
1502            }
1503            $Boundaries[1]["X1"] = $Xc;
1504            $Boundaries[1]["Y1"] = $Yc;
1505            $Lasti = $EndAngle;
1506
1507            for ($i = $EndAngle; $i >= $Offset; $i = $i - $Step) {
1508                $Xc = cos(($i - 90) * PI / 180) * ($InnerRadius - 1) + $X;
1509                $Yc = sin(($i - 90) * PI / 180) * ($InnerRadius - 1) + $Y;
1510
1511                if (!isset($Boundaries[1]["X2"])) {
1512                    $Boundaries[1]["X2"] = $Xc;
1513                    $Boundaries[1]["Y2"] = $Yc;
1514                }
1515                $AAPixels[] = [$Xc, $Yc];
1516
1517                $Xc = cos(($i - 90) * PI / 180) * $InnerRadius + $X;
1518                $Yc = sin(($i - 90) * PI / 180) * $InnerRadius + $Y;
1519
1520                if ($i < 90) {
1521                    $Yc++;
1522                }
1523                if ($i > 180 && $i < 270) {
1524                    $Xc++;
1525                }
1526                if ($i >= 270) {
1527                    $Xc++;
1528                    $Yc++;
1529                }
1530
1531                $Plots[] = $Xc;
1532                $Plots[] = $Yc;
1533            }
1534            $Boundaries[0]["X2"] = $Xc;
1535            $Boundaries[0]["Y2"] = $Yc;
1536
1537            /* Draw the polygon */
1538            $this->pChartObject->drawPolygon($Plots, $Settings);
1539            if ($RecordImageMap && !$Shadow) {
1540                $this->pChartObject->addToImageMap(
1541                    "POLY",
1542                    $this->arraySerialize($Plots),
1543                    $this->pChartObject->toHTMLColor(
1544                        $Palette[$ID]["R"],
1545                        $Palette[$ID]["G"],
1546                        $Palette[$ID]["B"]
1547                    ),
1548                    $Data["Series"][$Data["Abscissa"]]["Data"][$Key],
1549                    $Value
1550                );
1551            }
1552
1553            /* Smooth the edges using AA */
1554            foreach ($AAPixels as $Pos) {
1555                $this->pChartObject->drawAntialiasPixel($Pos[0], $Pos[1], $BorderColor);
1556            }
1557            $this->pChartObject->drawLine(
1558                $Boundaries[0]["X1"],
1559                $Boundaries[0]["Y1"],
1560                $Boundaries[0]["X2"],
1561                $Boundaries[0]["Y2"],
1562                $BorderColor
1563            );
1564            $this->pChartObject->drawLine(
1565                $Boundaries[1]["X1"],
1566                $Boundaries[1]["Y1"],
1567                $Boundaries[1]["X2"],
1568                $Boundaries[1]["Y2"],
1569                $BorderColor
1570            );
1571
1572            if ($DrawLabels && !$Shadow) {
1573                if ($LabelColor == PIE_LABEL_COLOR_AUTO) {
1574                    $Settings = [
1575                        "FillR" => $Palette[$ID]["R"],
1576                        "FillG" => $Palette[$ID]["G"],
1577                        "FillB" => $Palette[$ID]["B"],
1578                        "Alpha" => $Palette[$ID]["Alpha"]
1579                    ];
1580                } else {
1581                    $Settings = [
1582                        "FillR" => $LabelR,
1583                        "FillG" => $LabelG,
1584                        "FillB" => $LabelB,
1585                        "Alpha" => $LabelAlpha
1586                    ];
1587                }
1588
1589                $Angle = ($EndAngle - $Offset) / 2 + $Offset;
1590                $Xc = cos(($Angle - 90) * PI / 180) * $OuterRadius + $X;
1591                $Yc = sin(($Angle - 90) * PI / 180) * $OuterRadius + $Y;
1592
1593                $Label = $Data["Series"][$Data["Abscissa"]]["Data"][$Key];
1594
1595                if ($LabelStacked) {
1596                    $this->writePieLabel($Xc, $Yc, $Label, $Angle, $Settings, true, $X, $Y, $OuterRadius);
1597                } else {
1598                    $this->writePieLabel($Xc, $Yc, $Label, $Angle, $Settings, false);
1599                }
1600            }
1601
1602            $Offset = $Lasti;
1603            $ID++;
1604        }
1605
1606        if ($DrawLabels && $LabelStacked) {
1607            $this->writeShiftedLabels();
1608        }
1609
1610        if ($WriteValues && !$Shadow) {
1611            $Step = 360 / (2 * PI * $OuterRadius);
1612            $Offset = 0;
1613            foreach ($Values as $Key => $Value) {
1614                $EndAngle = $Offset + ($Value * $ScaleFactor);
1615                if ($EndAngle > 360) {
1616                    $EndAngle = 360;
1617                }
1618
1619                $Angle = $Offset + ($Value * $ScaleFactor) / 2;
1620                if ($ValuePosition == PIE_VALUE_OUTSIDE) {
1621                    $Xc = cos(($Angle - 90) * PI / 180) * ($OuterRadius + $ValuePadding) + $X;
1622                    $Yc = sin(($Angle - 90) * PI / 180) * ($OuterRadius + $ValuePadding) + $Y;
1623                    if ($Angle >= 0 && $Angle <= 90) {
1624                        $Align = TEXT_ALIGN_BOTTOMLEFT;
1625                    }
1626                    if ($Angle > 90 && $Angle <= 180) {
1627                        $Align = TEXT_ALIGN_TOPLEFT;
1628                    }
1629                    if ($Angle > 180 && $Angle <= 270) {
1630                        $Align = TEXT_ALIGN_TOPRIGHT;
1631                    }
1632                    if ($Angle > 270) {
1633                        $Align = TEXT_ALIGN_BOTTOMRIGHT;
1634                    }
1635                } else {
1636                    $Xc = cos(($Angle - 90) * PI / 180)
1637                        * (($OuterRadius - $InnerRadius) / 2 + $InnerRadius)
1638                        + $X
1639                    ;
1640                    $Yc = sin(($Angle - 90) * PI / 180)
1641                        * (($OuterRadius - $InnerRadius) / 2 + $InnerRadius)
1642                        + $Y
1643                    ;
1644                    $Align = TEXT_ALIGN_MIDDLEMIDDLE;
1645                }
1646
1647                if ($WriteValues == PIE_VALUE_PERCENTAGE) {
1648                    $Display = round((100 / $SerieSum) * $Value, $Precision) . "%";
1649                } elseif ($WriteValues == PIE_VALUE_NATURAL) {
1650                    $Display = $Value . $ValueSuffix;
1651                } else {
1652                    $Display = "";
1653                }
1654                $this->pChartObject->drawText(
1655                    $Xc,
1656                    $Yc,
1657                    $Display,
1658                    ["Align" => $Align, "R" => $ValueR, "G" => $ValueG, "B" => $ValueB]
1659                );
1660                $Offset = $EndAngle;
1661            }
1662        }
1663
1664        $this->pChartObject->Shadow = $RestoreShadow;
1665
1666        return PIE_RENDERED;
1667    }
1668
1669    /**
1670     * Draw a 3D ring chart
1671     * @param int $X
1672     * @param int $Y
1673     * @param array $Format
1674     * @return int
1675     */
1676    public function draw3DRing($X, $Y, array $Format = [])
1677    {
1678        $OuterRadius = isset($Format["OuterRadius"]) ? $Format["OuterRadius"] : 100;
1679        $Precision = isset($Format["Precision"]) ? $Format["Precision"] : 0;
1680        $InnerRadius = isset($Format["InnerRadius"]) ? $Format["InnerRadius"] : 30;
1681        $SkewFactor = isset($Format["SkewFactor"]) ? $Format["SkewFactor"] : .6;
1682        $SliceHeight = isset($Format["SliceHeight"]) ? $Format["SliceHeight"] : 10;
1683        $DataGapAngle = isset($Format["DataGapAngle"]) ? $Format["DataGapAngle"] : 10;
1684        $DataGapRadius = isset($Format["DataGapRadius"]) ? $Format["DataGapRadius"] : 10;
1685        $Border = isset($Format["Border"]) ? $Format["Border"] : false;
1686        $Shadow = isset($Format["Shadow"]) ? $Format["Shadow"] : false;
1687        $DrawLabels = isset($Format["DrawLabels"]) ? $Format["DrawLabels"] : false;
1688        $LabelStacked = isset($Format["LabelStacked"]) ? $Format["LabelStacked"] : false;
1689        $LabelColor = isset($Format["LabelColor"]) ? $Format["LabelColor"] : PIE_LABEL_COLOR_MANUAL;
1690        $LabelR = isset($Format["LabelR"]) ? $Format["LabelR"] : 0;
1691        $LabelG = isset($Format["LabelG"]) ? $Format["LabelG"] : 0;
1692        $LabelB = isset($Format["LabelB"]) ? $Format["LabelB"] : 0;
1693        $LabelAlpha = isset($Format["LabelAlpha"]) ? $Format["LabelAlpha"] : 100;
1694        $Cf = isset($Format["Cf"]) ? $Format["Cf"] : 20;
1695        $WriteValues = isset($Format["WriteValues"]) ? $Format["WriteValues"] : PIE_VALUE_NATURAL;
1696        $ValuePadding = isset($Format["ValuePadding"]) ? $Format["ValuePadding"] : $SliceHeight + 15;
1697        $ValuePosition = isset($Format["ValuePosition"]) ? $Format["ValuePosition"] : PIE_VALUE_OUTSIDE;
1698        $ValueSuffix = isset($Format["ValueSuffix"]) ? $Format["ValueSuffix"] : "";
1699        $ValueR = isset($Format["ValueR"]) ? $Format["ValueR"] : 255;
1700        $ValueG = isset($Format["ValueG"]) ? $Format["ValueG"] : 255;
1701        $ValueB = isset($Format["ValueB"]) ? $Format["ValueB"] : 255;
1702        $ValueAlpha = isset($Format["ValueAlpha"]) ? $Format["ValueAlpha"] : 100;
1703        $RecordImageMap = isset($Format["RecordImageMap"]) ? $Format["RecordImageMap"] : false;
1704
1705        /* Error correction for overlaying rounded corners */
1706        if ($SkewFactor < .5) {
1707            $SkewFactor = .5;
1708        }
1709
1710        /* Data Processing */
1711        $Data = $this->pDataObject->getData();
1712        $Palette = $this->pDataObject->getPalette();
1713
1714        /* Do we have an abscissa serie defined? */
1715        if ($Data["Abscissa"] == "") {
1716            return PIE_NO_ABSCISSA;
1717        }
1718
1719        /* Try to find the data serie */
1720        $DataSerie = null;
1721        foreach ($Data["Series"] as $SerieName => $SerieData) {
1722            if ($SerieName != $Data["Abscissa"]) {
1723                $DataSerie = $SerieName;
1724            }
1725        }
1726
1727        /* Do we have data to compute? */
1728        if (!$DataSerie) {
1729            return PIE_NO_DATASERIE;
1730        }
1731
1732        /* Remove unused data */
1733        list($Data, $Palette) = $this->clean0Values($Data, $Palette, $DataSerie, $Data["Abscissa"]);
1734
1735        /* Compute the pie sum */
1736        $SerieSum = $this->pDataObject->getSum($DataSerie);
1737
1738        /* Do we have data to draw? */
1739        if ($SerieSum == 0) {
1740            return PIE_SUMISNULL;
1741        }
1742
1743        /* Dump the real number of data to draw */
1744        $Values = [];
1745        foreach ($Data["Series"][$DataSerie]["Data"] as $Key => $Value) {
1746            if ($Value != 0) {
1747                $Values[] = $Value;
1748            }
1749        }
1750
1751        /* Compute the wasted angular space between series */
1752        if (count($Values) == 1) {
1753            $WastedAngular = 0;
1754        } else {
1755            $WastedAngular = count($Values) * $DataGapAngle;
1756        }
1757
1758        /* Compute the scale */
1759        $ScaleFactor = (360 - $WastedAngular) / $SerieSum;
1760
1761        $RestoreShadow = $this->pChartObject->Shadow;
1762        if ($this->pChartObject->Shadow) {
1763            $this->pChartObject->Shadow = false;
1764        }
1765
1766        /* Draw the polygon ring elements */
1767        $Offset = 360;
1768        $ID = count($Values) - 1;
1769        $Values = array_reverse($Values);
1770        $Slice = 0;
1771        $Slices = [];
1772        $SliceColors = [];
1773        $Visible = [];
1774        $SliceAngle = [];
1775        foreach ($Values as $Key => $Value) {
1776            if (!isset($Palette[$ID]["R"])) {
1777                $Color = $this->pChartObject->getRandomColor();
1778                $Palette[$ID] = $Color;
1779                $this->pDataObject->savePalette($ID, $Color);
1780            }
1781            $Settings = [
1782                "R" => $Palette[$ID]["R"],
1783                "G" => $Palette[$ID]["G"],
1784                "B" => $Palette[$ID]["B"],
1785                "Alpha" => $Palette[$ID]["Alpha"]
1786            ];
1787
1788            $SliceColors[$Slice] = $Settings;
1789
1790            $StartAngle = $Offset;
1791            $EndAngle = $Offset - ($Value * $ScaleFactor);
1792            if ($EndAngle < 0) {
1793                $EndAngle = 0;
1794            }
1795
1796            if ($StartAngle > 180) {
1797                $Visible[$Slice]["Start"] = true;
1798            } else {
1799                $Visible[$Slice]["Start"] = true;
1800            }
1801            if ($EndAngle < 180) {
1802                $Visible[$Slice]["End"] = false;
1803            } else {
1804                $Visible[$Slice]["End"] = true;
1805            }
1806
1807            $Step = (360 / (2 * PI * $OuterRadius)) / 2;
1808            $OutX1 = VOID;
1809            $OutY1 = VOID;
1810            for ($i = $Offset; $i >= $EndAngle; $i = $i - $Step) {
1811                $Xc = cos(($i - 90) * PI / 180) * ($OuterRadius + $DataGapRadius - 2) + $X;
1812                $Yc = sin(($i - 90) * PI / 180) * ($OuterRadius + $DataGapRadius - 2) * $SkewFactor + $Y;
1813                $Slices[$Slice]["AA"][] = [$Xc, $Yc];
1814
1815                $Xc = cos(($i - 90) * PI / 180) * ($OuterRadius + $DataGapRadius - 1) + $X;
1816                $Yc = sin(($i - 90) * PI / 180) * ($OuterRadius + $DataGapRadius - 1) * $SkewFactor + $Y;
1817                $Slices[$Slice]["AA"][] = [$Xc, $Yc];
1818
1819                $Xc = cos(($i - 90) * PI / 180) * ($OuterRadius + $DataGapRadius) + $X;
1820                $Yc = sin(($i - 90) * PI / 180) * ($OuterRadius + $DataGapRadius) * $SkewFactor + $Y;
1821                $this->pChartObject->drawAntialiasPixel($Xc, $Yc, $Settings);
1822
1823                if ($OutX1 == VOID) {
1824                    $OutX1 = $Xc;
1825                    $OutY1 = $Yc;
1826                }
1827
1828                if ($i < 90) {
1829                    $Yc++;
1830                }
1831                if ($i > 90 && $i < 180) {
1832                    $Xc++;
1833                }
1834                if ($i > 180 && $i < 270) {
1835                    $Xc++;
1836                }
1837                if ($i >= 270) {
1838                    $Xc++;
1839                    $Yc++;
1840                }
1841
1842                $Slices[$Slice]["BottomPoly"][] = floor($Xc);
1843                $Slices[$Slice]["BottomPoly"][] = floor($Yc);
1844                $Slices[$Slice]["TopPoly"][] = floor($Xc);
1845                $Slices[$Slice]["TopPoly"][] = floor($Yc) - $SliceHeight;
1846                $Slices[$Slice]["Angle"][] = $i;
1847            }
1848            $OutX2 = $Xc;
1849            $OutY2 = $Yc;
1850
1851            $Slices[$Slice]["Angle"][] = VOID;
1852            $Lasti = $i;
1853
1854            $Step = (360 / (2 * PI * $InnerRadius)) / 2;
1855            $InX1 = VOID;
1856            $InY1 = VOID;
1857            for ($i = $EndAngle; $i <= $Offset; $i = $i + $Step) {
1858                $Xc = cos(($i - 90) * PI / 180) * ($InnerRadius + $DataGapRadius - 1) + $X;
1859                $Yc = sin(($i - 90) * PI / 180) * ($InnerRadius + $DataGapRadius - 1) * $SkewFactor + $Y;
1860                $Slices[$Slice]["AA"][] = [$Xc, $Yc];
1861
1862                $Xc = cos(($i - 90) * PI / 180) * ($InnerRadius + $DataGapRadius) + $X;
1863                $Yc = sin(($i - 90) * PI / 180) * ($InnerRadius + $DataGapRadius) * $SkewFactor + $Y;
1864                $Slices[$Slice]["AA"][] = [$Xc, $Yc];
1865
1866                if ($InX1 == VOID) {
1867                    $InX1 = $Xc;
1868                    $InY1 = $Yc;
1869                }
1870
1871                if ($i < 90) {
1872                    $Yc++;
1873                }
1874                if ($i > 90 && $i < 180) {
1875                    $Xc++;
1876                }
1877                if ($i > 180 && $i < 270) {
1878                    $Xc++;
1879                }
1880                if ($i >= 270) {
1881                    $Xc++;
1882                    $Yc++;
1883                }
1884
1885                $Slices[$Slice]["BottomPoly"][] = floor($Xc);
1886                $Slices[$Slice]["BottomPoly"][] = floor($Yc);
1887                $Slices[$Slice]["TopPoly"][] = floor($Xc);
1888                $Slices[$Slice]["TopPoly"][] = floor($Yc) - $SliceHeight;
1889                $Slices[$Slice]["Angle"][] = $i;
1890            }
1891            $InX2 = $Xc;
1892            $InY2 = $Yc;
1893
1894            $Slices[$Slice]["InX1"] = $InX1;
1895            $Slices[$Slice]["InY1"] = $InY1;
1896            $Slices[$Slice]["InX2"] = $InX2;
1897            $Slices[$Slice]["InY2"] = $InY2;
1898            $Slices[$Slice]["OutX1"] = $OutX1;
1899            $Slices[$Slice]["OutY1"] = $OutY1;
1900            $Slices[$Slice]["OutX2"] = $OutX2;
1901            $Slices[$Slice]["OutY2"] = $OutY2;
1902
1903            $Offset = $Lasti - $DataGapAngle;
1904            $ID--;
1905            $Slice++;
1906        }
1907
1908        /* Draw the bottom pie splice */
1909        foreach ($Slices as $SliceID => $Plots) {
1910            $Settings = $SliceColors[$SliceID];
1911            $Settings["NoBorder"] = true;
1912            $this->pChartObject->drawPolygon($Plots["BottomPoly"], $Settings);
1913
1914            foreach ($Plots["AA"] as $Key => $Pos) {
1915                $this->pChartObject->drawAntialiasPixel($Pos[0], $Pos[1], $Settings);
1916            }
1917            $this->pChartObject->drawLine(
1918                $Plots["InX1"],
1919                $Plots["InY1"],
1920                $Plots["OutX2"],
1921                $Plots["OutY2"],
1922                $Settings
1923            );
1924            $this->pChartObject->drawLine(
1925                $Plots["InX2"],
1926                $Plots["InY2"],
1927                $Plots["OutX1"],
1928                $Plots["OutY1"],
1929                $Settings
1930            );
1931        }
1932
1933        $Slices = array_reverse($Slices);
1934        $SliceColors = array_reverse($SliceColors);
1935
1936        /* Draw the vertical edges (semi-visible) */
1937        foreach ($Slices as $SliceID => $Plots) {
1938            $Settings = $SliceColors[$SliceID];
1939            $Settings["NoBorder"] = true;
1940            $Settings["R"] = $Settings["R"] + $Cf;
1941            $Settings["G"] = $Settings["G"] + $Cf;
1942            $Settings["B"] = $Settings["B"] + $Cf;
1943
1944            $StartAngle = $Plots["Angle"][0];
1945            foreach ($Plots["Angle"] as $Key => $Angle) {
1946                if ($Angle == VOID) {
1947                    $EndAngle = $Plots["Angle"][$Key - 1];
1948                }
1949            }
1950
1951            if ($StartAngle >= 270 || $StartAngle <= 90) {
1952                $this->pChartObject->drawLine(
1953                    $Plots["OutX1"],
1954                    $Plots["OutY1"],
1955                    $Plots["OutX1"],
1956                    $Plots["OutY1"] - $SliceHeight,
1957                    $Settings
1958                );
1959            }
1960            if ($StartAngle >= 270 || $StartAngle <= 90) {
1961                $this->pChartObject->drawLine(
1962                    $Plots["OutX2"],
1963                    $Plots["OutY2"],
1964                    $Plots["OutX2"],
1965                    $Plots["OutY2"] - $SliceHeight,
1966                    $Settings
1967                );
1968            }
1969            $this->pChartObject->drawLine(
1970                $Plots["InX1"],
1971                $Plots["InY1"],
1972                $Plots["InX1"],
1973                $Plots["InY1"] - $SliceHeight,
1974                $Settings
1975            );
1976            $this->pChartObject->drawLine(
1977                $Plots["InX2"],
1978                $Plots["InY2"],
1979                $Plots["InX2"],
1980                $Plots["InY2"] - $SliceHeight,
1981                $Settings
1982            );
1983        }
1984
1985        /* Draw the inner vertical slices */
1986        foreach ($Slices as $SliceID => $Plots) {
1987            $Settings = $SliceColors[$SliceID];
1988            $Settings["NoBorder"] = true;
1989            $Settings["R"] = $Settings["R"] + $Cf;
1990            $Settings["G"] = $Settings["G"] + $Cf;
1991            $Settings["B"] = $Settings["B"] + $Cf;
1992
1993            $Outer = true;
1994            $Inner = false;
1995            $InnerPlotsA = [];
1996            $InnerPlotsB = [];
1997            foreach ($Plots["Angle"] as $ID => $Angle) {
1998                if ($Angle == VOID) {
1999                    $Outer = false;
2000                    $Inner = true;
2001                } elseif ($Inner && ($Angle < 90 || $Angle > 270) && isset($Plots["BottomPoly"][$ID * 2])) {
2002                    $Xo = $Plots["BottomPoly"][$ID * 2];
2003                    $Yo = $Plots["BottomPoly"][$ID * 2 + 1];
2004
2005                    $InnerPlotsA[] = $Xo;
2006                    $InnerPlotsA[] = $Yo;
2007                    $InnerPlotsB[] = $Xo;
2008                    $InnerPlotsB[] = $Yo - $SliceHeight;
2009                }
2010            }
2011
2012            if (count($InnerPlotsA)) {
2013                $InnerPlots = array_merge($InnerPlotsA, $this->arrayReverse($InnerPlotsB));
2014                $this->pChartObject->drawPolygon($InnerPlots, $Settings);
2015            }
2016        }
2017
2018        /* Draw the splice top and left poly */
2019        foreach ($Slices as $SliceID => $Plots) {
2020            $Settings = $SliceColors[$SliceID];
2021            $Settings["NoBorder"] = true;
2022            $Settings["R"] = $Settings["R"] + $Cf * 1.5;
2023            $Settings["G"] = $Settings["G"] + $Cf * 1.5;
2024            $Settings["B"] = $Settings["B"] + $Cf * 1.5;
2025
2026            $StartAngle = $Plots["Angle"][0];
2027            foreach ($Plots["Angle"] as $Key => $Angle) {
2028                if ($Angle == VOID) {
2029                    $EndAngle = $Plots["Angle"][$Key - 1];
2030                }
2031            }
2032
2033            if ($StartAngle < 180) {
2034                $Points = [];
2035                $Points[] = $Plots["InX2"];
2036                $Points[] = $Plots["InY2"];
2037                $Points[] = $Plots["InX2"];
2038                $Points[] = $Plots["InY2"] - $SliceHeight;
2039                $Points[] = $Plots["OutX1"];
2040                $Points[] = $Plots["OutY1"] - $SliceHeight;
2041                $Points[] = $Plots["OutX1"];
2042                $Points[] = $Plots["OutY1"];
2043
2044                $this->pChartObject->drawPolygon($Points, $Settings);
2045            }
2046
2047            if ($EndAngle > 180) {
2048                $Points = [];
2049                $Points[] = $Plots["InX1"];
2050                $Points[] = $Plots["InY1"];
2051                $Points[] = $Plots["InX1"];
2052                $Points[] = $Plots["InY1"] - $SliceHeight;
2053                $Points[] = $Plots["OutX2"];
2054                $Points[] = $Plots["OutY2"] - $SliceHeight;
2055                $Points[] = $Plots["OutX2"];
2056                $Points[] = $Plots["OutY2"];
2057
2058                $this->pChartObject->drawPolygon($Points, $Settings);
2059            }
2060        }
2061
2062
2063        /* Draw the vertical edges (visible) */
2064        foreach ($Slices as $SliceID => $Plots) {
2065            $Settings = $SliceColors[$SliceID];
2066            $Settings["NoBorder"] = true;
2067            $Settings["R"] = $Settings["R"] + $Cf;
2068            $Settings["G"] = $Settings["G"] + $Cf;
2069            $Settings["B"] = $Settings["B"] + $Cf;
2070
2071            $StartAngle = $Plots["Angle"][0];
2072            foreach ($Plots["Angle"] as $Key => $Angle) {
2073                if ($Angle == VOID) {
2074                    $EndAngle = $Plots["Angle"][$Key - 1];
2075                }
2076            }
2077
2078            if ($StartAngle <= 270 && $StartAngle >= 90) {
2079                $this->pChartObject->drawLine(
2080                    $Plots["OutX1"],
2081                    $Plots["OutY1"],
2082                    $Plots["OutX1"],
2083                    $Plots["OutY1"] - $SliceHeight,
2084                    $Settings
2085                );
2086            }
2087            if ($EndAngle <= 270 && $EndAngle >= 90) {
2088                $this->pChartObject->drawLine(
2089                    $Plots["OutX2"],
2090                    $Plots["OutY2"],
2091                    $Plots["OutX2"],
2092                    $Plots["OutY2"] - $SliceHeight,
2093                    $Settings
2094                );
2095            }
2096        }
2097
2098
2099        /* Draw the outer vertical slices */
2100        foreach ($Slices as $SliceID => $Plots) {
2101            $Settings = $SliceColors[$SliceID];
2102            $Settings["NoBorder"] = true;
2103            $Settings["R"] = $Settings["R"] + $Cf;
2104            $Settings["G"] = $Settings["G"] + $Cf;
2105            $Settings["B"] = $Settings["B"] + $Cf;
2106
2107            $Outer = true;
2108            $Inner = false;
2109            $OuterPlotsA = [];
2110            $OuterPlotsB = [];
2111            $InnerPlotsA = [];
2112            $InnerPlotsB = [];
2113            foreach ($Plots["Angle"] as $ID => $Angle) {
2114                if ($Angle == VOID) {
2115                    $Outer = false;
2116                    $Inner = true;
2117                } elseif ($Outer && ($Angle > 90 && $Angle < 270) && isset($Plots["BottomPoly"][$ID * 2])) {
2118                    $Xo = $Plots["BottomPoly"][$ID * 2];
2119                    $Yo = $Plots["BottomPoly"][$ID * 2 + 1];
2120
2121                    $OuterPlotsA[] = $Xo;
2122                    $OuterPlotsA[] = $Yo;
2123                    $OuterPlotsB[] = $Xo;
2124                    $OuterPlotsB[] = $Yo - $SliceHeight;
2125                }
2126            }
2127            if (count($OuterPlotsA)) {
2128                $OuterPlots = array_merge($OuterPlotsA, $this->arrayReverse($OuterPlotsB));
2129                $this->pChartObject->drawPolygon($OuterPlots, $Settings);
2130            }
2131        }
2132
2133        $Slices = array_reverse($Slices);
2134        $SliceColors = array_reverse($SliceColors);
2135
2136        /* Draw the top pie splice */
2137        foreach ($Slices as $SliceID => $Plots) {
2138            $Settings = $SliceColors[$SliceID];
2139            $Settings["NoBorder"] = true;
2140            $Settings["R"] = $Settings["R"] + $Cf * 2;
2141            $Settings["G"] = $Settings["G"] + $Cf * 2;
2142            $Settings["B"] = $Settings["B"] + $Cf * 2;
2143
2144            $this->pChartObject->drawPolygon($Plots["TopPoly"], $Settings);
2145
2146            if ($RecordImageMap) {
2147                $this->pChartObject->addToImageMap(
2148                    "POLY",
2149                    $this->arraySerialize($Plots["TopPoly"]),
2150                    $this->pChartObject->toHTMLColor(
2151                        $Settings["R"],
2152                        $Settings["G"],
2153                        $Settings["B"]
2154                    ),
2155                    $Data["Series"][$Data["Abscissa"]]["Data"][$SliceID],
2156                    $Data["Series"][$DataSerie]["Data"][count($Slices) - $SliceID - 1]
2157                );
2158            }
2159
2160            foreach ($Plots["AA"] as $Key => $Pos) {
2161                $this->pChartObject->drawAntialiasPixel(
2162                    $Pos[0],
2163                    $Pos[1] - $SliceHeight,
2164                    $Settings
2165                );
2166            }
2167            $this->pChartObject->drawLine(
2168                $Plots["InX1"],
2169                $Plots["InY1"] - $SliceHeight,
2170                $Plots["OutX2"],
2171                $Plots["OutY2"] - $SliceHeight,
2172                $Settings
2173            );
2174            $this->pChartObject->drawLine(
2175                $Plots["InX2"],
2176                $Plots["InY2"] - $SliceHeight,
2177                $Plots["OutX1"],
2178                $Plots["OutY1"] - $SliceHeight,
2179                $Settings
2180            );
2181        }
2182
2183        if ($DrawLabels) {
2184            $Offset = 360;
2185            foreach ($Values as $Key => $Value) {
2186                $StartAngle = $Offset;
2187                $EndAngle = $Offset - ($Value * $ScaleFactor);
2188                if ($EndAngle < 0) {
2189                    $EndAngle = 0;
2190                }
2191
2192                if ($LabelColor == PIE_LABEL_COLOR_AUTO) {
2193                    $Settings = [
2194                        "FillR" => $Palette[$ID]["R"],
2195                        "FillG" => $Palette[$ID]["G"],
2196                        "FillB" => $Palette[$ID]["B"],
2197                        "Alpha" => $Palette[$ID]["Alpha"]
2198                    ];
2199                } else {
2200                    $Settings = [
2201                        "FillR" => $LabelR,
2202                        "FillG" => $LabelG,
2203                        "FillB" => $LabelB,
2204                        "Alpha" => $LabelAlpha
2205                    ];
2206                }
2207
2208                $Angle = ($EndAngle - $Offset) / 2 + $Offset;
2209                $Xc = cos(($Angle - 90) * PI / 180) * ($OuterRadius + $DataGapRadius) + $X;
2210                $Yc = sin(($Angle - 90) * PI / 180) * ($OuterRadius + $DataGapRadius) * $SkewFactor + $Y;
2211
2212                $Label = "";
2213                if ($WriteValues == PIE_VALUE_PERCENTAGE) {
2214                    $Label = round((100 / $SerieSum) * $Value, $Precision) . "%";
2215                } elseif ($WriteValues == PIE_VALUE_NATURAL) {
2216                    $Label = $Data["Series"][$Data["Abscissa"]]["Data"][$Key];
2217                }
2218
2219                if ($LabelStacked) {
2220                    $this->writePieLabel(
2221                        $Xc,
2222                        $Yc - $SliceHeight,
2223                        $Label,
2224                        $Angle,
2225                        $Settings,
2226                        true,
2227                        $X,
2228                        $Y,
2229                        $OuterRadius
2230                    );
2231                } else {
2232                    $this->writePieLabel($Xc, $Yc - $SliceHeight, $Label, $Angle, $Settings, false);
2233                }
2234                $Offset = $EndAngle - $DataGapAngle;
2235                $ID--;
2236                $Slice++;
2237            }
2238        }
2239        if ($DrawLabels && $LabelStacked) {
2240            $this->writeShiftedLabels();
2241        }
2242
2243        $this->pChartObject->Shadow = $RestoreShadow;
2244
2245        return PIE_RENDERED;
2246    }
2247
2248    /**
2249     * Serialize an array
2250     * @param array $Data
2251     * @return string
2252     */
2253    public function arraySerialize(array $Data)
2254    {
2255        $Result = "";
2256        foreach ($Data as $Value) {
2257            if ($Result == "") {
2258                $Result = floor($Value);
2259            } else {
2260                $Result = $Result . "," . floor($Value);
2261            }
2262        }
2263
2264        return $Result;
2265    }
2266
2267    /**
2268     * Reverse an array
2269     * @param array $Plots
2270     * @return array
2271     */
2272    public function arrayReverse(array $Plots)
2273    {
2274        $Result = [];
2275
2276        for ($i = count($Plots) - 1; $i >= 0; $i = $i - 2) {
2277            $Result[] = $Plots[$i - 1];
2278            $Result[] = $Plots[$i];
2279        }
2280
2281        return $Result;
2282    }
2283
2284    /**
2285     * Remove unused series & values
2286     * @param array $Data
2287     * @param array $Palette
2288     * @param string $DataSerie
2289     * @param string $AbscissaSerie
2290     * @return array
2291     */
2292    public function clean0Values(array $Data, array $Palette, $DataSerie, $AbscissaSerie)
2293    {
2294        $NewPalette = [];
2295        $NewData = [];
2296        $NewAbscissa = [];
2297
2298        /* Remove unused series */
2299        foreach (array_keys($Data["Series"]) as $SerieName) {
2300            if ($SerieName != $DataSerie && $SerieName != $AbscissaSerie) {
2301                unset($Data["Series"][$SerieName]);
2302            }
2303        }
2304
2305        /* Remove null values */
2306        foreach ($Data["Series"][$DataSerie]["Data"] as $Key => $Value) {
2307            if ($Value != 0) {
2308                $NewData[] = $Value;
2309                $NewAbscissa[] = $Data["Series"][$AbscissaSerie]["Data"][$Key];
2310                if (isset($Palette[$Key])) {
2311                    $NewPalette[] = $Palette[$Key];
2312                }
2313            }
2314        }
2315        $Data["Series"][$DataSerie]["Data"] = $NewData;
2316        $Data["Series"][$AbscissaSerie]["Data"] = $NewAbscissa;
2317
2318        return [$Data, $NewPalette];
2319    }
2320}
2321