1<?php
2
3/**
4 *    pChart - a PHP class to build charts!
5 *    Copyright (C) 2008 Jean-Damien POGOLOTTI
6 *    Version 2.0
7 *    Copyright (C) 2010 Tim Martin
8 *
9 *    http://pchart.sourceforge.net
10 *
11 *    This program is free software: you can redistribute it and/or modify
12 *    it under the terms of the GNU General Public License as published by
13 *    the Free Software Foundation, either version 1,2,3 of the License, or
14 *    (at your option) any later version.
15 *
16 *    This program is distributed in the hope that it will be useful,
17 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
18 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 *    GNU General Public License for more details.
20 *
21 *    You should have received a copy of the GNU General Public License
22 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
23 */
24
25define ("PIE_PERCENTAGE", 1);
26define ("PIE_LABELS", 2);
27define ("PIE_NOLABEL", 3);
28define ("PIE_PERCENTAGE_LABEL", 4);
29
30/**
31 * This is an attempt to separate out the pie chart drawing code from
32 * the rest of the chart code, since pie charts are very different
33 * from charts that use 2D Cartesian coordinates.
34 *
35 * The inheritance hierarchy here probably isn't the finished article;
36 * separating out in this way is an intermediate form that I hope will
37 * shed light on the real dependency structure.
38 */
39class PieChart extends pChart {
40    /**
41     * Draw the data legends
42     *
43     * @param int X-Position
44     * @param int Y-Position
45     * @param array Data pData->getData
46     * @param array DataDescription pData->getDataDescription
47     * @param Color
48     * @param ShadowProperties
49     * @access public
50     */
51    public function drawPieLegend($XPos, $YPos, $Data, $DataDescription, Color $color, ShadowProperties $shadowProperties = null) {
52        if($shadowProperties == null) {
53            $shadowProperties = ShadowProperties::FromDefaults();
54        }
55
56        /* Validate the Data and DataDescription array */
57        $this->validateDataDescription("drawPieLegend", $DataDescription, FALSE);
58        $this->validateData("drawPieLegend", $Data);
59
60        if($DataDescription->getPosition() == '')
61            return (-1);
62
63        /* <-10->[8]<-4->Text<-10-> */
64        $MaxWidth  = 0;
65        $MaxHeight = 8;
66        foreach($Data as $Key => $Value) {
67            $Value2     = $Value [$DataDescription->getPosition()];
68            $Position   = imageftbbox($this->FontSize, 0, $this->FontName, $Value2);
69            $TextWidth  = $Position [2] - $Position [0];
70            $TextHeight = $Position [1] - $Position [7];
71            if($TextWidth > $MaxWidth) {
72                $MaxWidth = $TextWidth;
73            }
74
75            $MaxHeight = $MaxHeight + $TextHeight + 4;
76        }
77        $MaxHeight = $MaxHeight - 3;
78        $MaxWidth  = $MaxWidth + 32;
79
80        $this->canvas->drawFilledRoundedRectangle(
81            new Point($XPos + 1, $YPos + 1),
82            new Point($XPos + $MaxWidth + 1,
83                      $YPos + $MaxHeight + 1),
84            5,
85            $color->addRGBIncrement(-30),
86            $this->LineWidth,
87            $this->LineDotSize,
88            $shadowProperties
89        );
90
91        $this->canvas->drawFilledRoundedRectangle(
92            new Point($XPos, $YPos),
93            new Point($XPos + $MaxWidth,
94                      $YPos + $MaxHeight),
95            5, $color,
96            $this->LineWidth,
97            $this->LineDotSize,
98            $shadowProperties
99        );
100
101        $YOffset = 4 + $this->FontSize;
102        $ID      = 0;
103        foreach($Data as $Key => $Value) {
104            $Value2     = $Value [$DataDescription->getPosition()];
105            $Position   = imageftbbox($this->FontSize, 0, $this->FontName, $Value2);
106            $TextHeight = $Position [1] - $Position [7];
107            $this->canvas->drawFilledRectangle(
108                new Point($XPos + 10,
109                          $YPos + $YOffset - 6),
110                new Point($XPos + 14,
111                          $YPos + $YOffset - 2),
112                $this->palette->colors[$ID],
113                $shadowProperties
114            );
115
116            $this->canvas->drawText(
117                $this->FontSize,
118                0,
119                new Point($XPos + 22,
120                          $YPos + $YOffset),
121                new Color(0, 0, 0),
122                $this->FontName,
123                $Value2,
124                $shadowProperties
125            );
126            $YOffset = $YOffset + $TextHeight + 4;
127            $ID++;
128        }
129    }
130
131    /**
132     * This function draw a flat pie chart
133     *
134     * @param array Data (PieChart->getData())
135     * @param array Description (PieChart->getDataDescription())
136     * @param int X-Position of the Center
137     * @param int Y-Position of the Center
138     * @param int Radius of the cake
139     * @param const int Draw the Labels to the pies? PIE_LABELS, PIE_NOLABEL, PIE_PERCENTAGE, PIE_PERCENATGE_LABEL
140     * @param int Distance between the splices
141     * @param int number of decimals
142     * @param ShadowProperties
143     * @access public
144     */
145    public function drawBasicPieGraph($Data, $DataDescription, $XPos, $YPos, $Radius = 100, $DrawLabels = PIE_NOLABEL, Color $color = null, $Decimals = 0, ShadowProperties $shadowProperties = null) {
146        if($shadowProperties == null) {
147            $shadowProperties = ShadowProperties::NoShadow();
148        }
149
150        if(empty($DataDescription->values)) {
151            throw new Exception("No values available in data description in drawBasicPieGraph()");
152        }
153
154        if($color == null) {
155            $color = new Color(255, 255, 255);
156        }
157
158        /* Validate the Data and DataDescription array */
159        $this->validateDataDescription("drawBasicPieGraph", $DataDescription, FALSE);
160        $this->validateData("drawBasicPieGraph", $Data);
161
162        /* Determine pie sum */
163        $Series = 0;
164        $PieSum = 0;
165        foreach($DataDescription->values as $Key2 => $ColName) {
166            if($ColName != $DataDescription->getPosition()) {
167                $Series++;
168                foreach(array_keys($Data) as $Key) {
169                    if(isset ($Data [$Key] [$ColName]))
170                        $PieSum = $PieSum + $Data [$Key] [$ColName];
171                    $iValues [] = $Data [$Key] [$ColName];
172                }
173            }
174        }
175
176        /* Validate serie */
177        if($Series != 1)
178            throw new Exception("Pie chart can only accept one serie of data.");
179        /** @todo Proper exception type needed here */
180
181        $SpliceRatio   = 360 / $PieSum;
182        $SplicePercent = 100 / $PieSum;
183
184        /* Calculate all polygons */
185        $Angle    = 0;
186        $TopPlots = [];
187        foreach($iValues as $Key => $Value) {
188            $TopPlots [$Key] [] = $XPos;
189            $TopPlots [$Key] [] = $YPos;
190
191            /* Process labels position & size */
192            $this->processLabelsPositionAndSize($DrawLabels, $Angle, $Value, $SpliceRatio, $SplicePercent, 0, $Decimals, $Radius, $XPos, $YPos, $shadowProperties);
193
194            /* Process pie slices */
195            for($iAngle = $Angle; $iAngle <= $Angle + $Value * $SpliceRatio; $iAngle = $iAngle + .5) {
196                $TopX = cos($iAngle * M_PI / 180) * $Radius + $XPos;
197                $TopY = sin($iAngle * M_PI / 180) * $Radius + $YPos;
198
199                $TopPlots [$Key] [] = $TopX;
200                $TopPlots [$Key] [] = $TopY;
201            }
202
203            $TopPlots [$Key] [] = $XPos;
204            $TopPlots [$Key] [] = $YPos;
205
206            $Angle = $iAngle;
207        }
208        $PolyPlots = $TopPlots;
209
210        /* Set array values type to float --- PHP Bug with
211           * imagefilledpolygon casting to integer */
212        foreach($TopPlots as $Key => $Value) {
213            foreach(array_keys($TopPlots[$Key]) as $Key2) {
214                settype($TopPlots [$Key] [$Key2], "float");
215            }
216        }
217
218        /* Draw Top polygons */
219        foreach($PolyPlots as $Key => $Value) {
220            $this->canvas->drawFilledPolygon(
221                $PolyPlots [$Key],
222                (count($PolyPlots [$Key]) + 1) / 2,
223                $this->palette->colors[$Key]
224            );
225        }
226
227        $this->canvas->drawCircle(
228            new Point($XPos - .5, $YPos - .5),
229            $Radius,
230            $color,
231            $shadowProperties
232        );
233        $this->canvas->drawCircle(
234            new Point($XPos - .5, $YPos - .5),
235            $Radius + .5,
236            $color,
237            $shadowProperties
238        );
239
240        /* Draw Top polygons */
241        foreach($TopPlots as $Key => $Value) {
242            for($j = 0; $j <= count($TopPlots [$Key]) - 4; $j = $j + 2)
243                $this->canvas->drawLine(
244                    new Point($TopPlots [$Key] [$j],
245                              $TopPlots [$Key] [$j + 1]),
246                    new Point($TopPlots [$Key] [$j + 2],
247                              $TopPlots [$Key] [$j + 3]),
248                    $color,
249                    $this->LineWidth,
250                    $this->LineDotSize,
251                    $shadowProperties
252                );
253        }
254    }
255
256    /**
257     * @todo This method was generated by pulling out a bunch of
258     * copy&paste duplication. It needs further work to improve the
259     * interface.
260     */
261    private function processLabelsPositionAndSize($DrawLabels, $Angle, $Value, $SpliceRatio, $SplicePercent, $SpliceDistance, $Decimals, $Radius, $XPos, $YPos, ShadowProperties $shadowProperties) {
262        $Caption = "";
263        if(!($DrawLabels == PIE_NOLABEL)) {
264            $TAngle = $Angle + ($Value * $SpliceRatio / 2);
265            if($DrawLabels == PIE_PERCENTAGE)
266                $Caption = (round($Value * pow(10, $Decimals) * $SplicePercent) / pow(10, $Decimals))."%";
267            elseif($DrawLabels == PIE_LABELS)
268                $Caption = $iLabels [$Key];
269            elseif($DrawLabels == PIE_PERCENTAGE_LABEL)
270                $Caption = $iLabels [$Key]."\r\n".(round($Value * pow(10, $Decimals) * $SplicePercent) / pow(10, $Decimals))."%";
271            elseif($DrawLabels == PIE_PERCENTAGE_LABEL)
272                $Caption = $iLabels [$Key]."\r\n".(round($Value * pow(10, $Decimals) * $SplicePercent) / pow(10, $Decimals))."%";
273
274            $Position   = imageftbbox($this->FontSize, 0, $this->FontName, $Caption);
275            $TextWidth  = $Position [2] - $Position [0];
276            $TextHeight = abs($Position [1]) + abs($Position [3]);
277
278            $TX = cos(($TAngle) * M_PI / 180) * ($Radius + 10 + $SpliceDistance) + $XPos;
279
280            if($TAngle > 0 && $TAngle < 180)
281                $TY = sin(($TAngle) * M_PI / 180) * ($Radius + 10 + $SpliceDistance) + $YPos + 4;
282            else
283                $TY = sin(($TAngle) * M_PI / 180) * ($Radius + $SpliceDistance + 4) + $YPos - ($TextHeight / 2);
284
285            if($TAngle > 90 && $TAngle < 270)
286                $TX = $TX - $TextWidth;
287
288            $this->canvas->drawText(
289                $this->FontSize,
290                0,
291                new Point($TX, $TY),
292                new Color(70, 70, 70),
293                $this->FontName,
294                $Caption,
295                $shadowProperties
296            );
297        }
298    }
299
300    /**
301     * This function draw a simple flat pie graph with shadows
302     *
303     * @param array Data (PieChart->getData())
304     * @param array Description (PieChart->getDataDescription())
305     * @param int X-Position of the Center
306     * @param int Y-Position of the Center
307     * @param int Radius of the cake
308     * @param const int Draw the Labels to the pies? PIE_LABELS, PIE_NOLABEL, PIE_PERCENTAGE, PIE_PERCENATGE_LABEL
309     * @param int Distance between the splices
310     * @param int number of decimals
311     * @param ShadowProperties
312     * @access public
313     */
314    public function drawFlatPieGraphWithShadow($Data, $DataDescription, $XPos, $YPos, $Radius = 100, $DrawLabels = PIE_NOLABEL, $SpliceDistance = 0, $Decimals = 0, ShadowProperties $shadowProperties = NULL) {
315        /**
316         * @todo Slightly ugly code follows: We want to draw the graph
317         * with once to be the 'shadow', without itself having a
318         * shadow, and once again to be the actual graph. In fact, we
319         * can't pass ShadowProperties::NoShadow() into the first
320         * drawFlatPieGraph() call, since the method expects to use
321         * the color on the shadow properties, even though it is
322         * inactive. We do a clone to avoid mucking with the caller's
323         * copy of the shadow properties.
324         */
325        $inactiveShadowProperties         = ShadowProperties::Copy($shadowProperties);
326        $inactiveShadowProperties->active = false;
327
328        $this->drawFlatPieGraph(
329            $Data,
330            $DataDescription,
331            $XPos + $shadowProperties->xDistance,
332            $YPos + $shadowProperties->yDistance,
333            $Radius,
334            PIE_NOLABEL,
335            $SpliceDistance, $Decimals, TRUE,
336            $inactiveShadowProperties
337        );
338        $this->drawFlatPieGraph(
339            $Data, $DataDescription, $XPos, $YPos, $Radius, $DrawLabels, $SpliceDistance, $Decimals, FALSE,
340            $inactiveShadowProperties
341        );
342    }
343
344    /**
345     * This function draw a simple flat pie graph with shadows
346     *
347     * @param array Data (PieChart->getData())
348     * @param array Description (PieChart->getDataDescription())
349     * @param int X-Position of the Center
350     * @param int Y-Position of the Center
351     * @param int Radius of the cake
352     * @param const int Draw the Labels to the pies? PIE_LABELS, PIE_NOLABEL, PIE_PERCENTAGE, PIE_PERCENATGE_LABEL
353     * @param int Distance between the splices
354     * @param int number of decimals
355     * @param bool Should the Chart be gray?
356     * @param ShadowProperties
357     * @access public
358     */
359    public function drawFlatPieGraph($Data, $DataDescription, $XPos, $YPos, $Radius = 100, $DrawLabels = PIE_NOLABEL, $SpliceDistance = 0, $Decimals = 0, $AllBlack = FALSE, ShadowProperties $shadowProperties = null) {
360        if($shadowProperties == null) {
361            $shadowProperties = ShadowProperties::FromDefaults();
362        }
363
364        /* Validate the Data and DataDescription array */
365        $this->validateDataDescription("drawFlatPieGraph", $DataDescription, FALSE);
366        $this->validateData("drawFlatPieGraph", $Data);
367
368        /* Determine pie sum */
369        $Series = 0;
370        $PieSum = 0;
371        foreach($DataDescription->values as $ColName) {
372            if($ColName != $DataDescription->getPosition()) {
373                $Series++;
374                foreach(array_keys($Data) as $Key) {
375                    if(isset ($Data [$Key] [$ColName]))
376                        $PieSum = $PieSum + $Data [$Key] [$ColName];
377                    $iValues [] = $Data [$Key] [$ColName];
378                }
379            }
380        }
381
382        /* Validate serie */
383        if($Series != 1) {
384            /**
385             * @todo Proper exception type needed here
386             */
387            throw new Exception("Pie chart can only accept one serie of data.");
388        }
389
390        $SpliceRatio   = 360 / $PieSum;
391        $SplicePercent = 100 / $PieSum;
392
393        /* Calculate all polygons */
394        $Angle    = 0;
395        $TopPlots = "";
396        foreach($iValues as $Key => $Value) {
397            $XOffset = cos(($Angle + ($Value / 2 * $SpliceRatio)) * M_PI / 180) * $SpliceDistance;
398            $YOffset = sin(($Angle + ($Value / 2 * $SpliceRatio)) * M_PI / 180) * $SpliceDistance;
399
400            $TopPlots [$Key] [] = round($XPos + $XOffset);
401            $TopPlots [$Key] [] = round($YPos + $YOffset);
402
403            if($AllBlack) {
404                $color = $shadowProperties->color;
405            } else {
406                $color = $this->palette->colors[$Key];
407            }
408
409            /* Process labels position & size */
410            $this->processLabelsPositionAndSize($DrawLabels, $Angle, $Value, $SpliceRatio, $SplicePercent, $SpliceDistance, $Decimals, $Radius, $XPos, $YPos, $shadowProperties);
411
412            /* Process pie slices */
413            $this->processPieSlices($Angle, $SpliceRatio, $Value, $Radius, $XPos, $YPos, $XOffset, $YOffset, $color, $TopPlots[$Key], $shadowProperties);
414
415            $TopPlots [$Key] [] = round($XPos + $XOffset);
416            $TopPlots [$Key] [] = round($YPos + $YOffset);
417        }
418        $PolyPlots = $TopPlots;
419
420        /* Draw Top polygons */
421        foreach($PolyPlots as $Key => $Value) {
422            if(!$AllBlack)
423                $polygonColor = $this->palette->colors[$Key];
424            else
425                $polygonColor = $shadowProperties->color;
426
427            $this->canvas->drawFilledPolygon(
428                $PolyPlots [$Key],
429                (count($PolyPlots [$Key]) + 1) / 2,
430                $polygonColor
431            );
432        }
433    }
434
435    /**
436     * This function draw a pseudo-3D pie chart
437     *
438     * @param pData
439     * @param int X-Position of the Center
440     * @param int Y-Position of the Center
441     * @param int Radius of the cake
442     * @param const int Draw the Labels to the pies? PIE_LABELS, PIE_NOLABEL, PIE_PERCENTAGE, PIE_PERCENATGE_LABEL
443     * @param bool Enhance colors?
444     * @param int Skew
445     * @param int Height of the splices
446     * @param int Distance between the splices
447     * @param int number of decimals
448     * @param ShadowProperties
449     * @access public
450     */
451    public function drawPieGraph(pData $data, $XPos, $YPos,
452                                 $Radius = 100, $DrawLabels = PIE_NOLABEL,
453                                 $EnhanceColors = TRUE, $Skew = 60,
454                                 $SpliceHeight = 20, $SpliceDistance = 0,
455                                 $Decimals = 0,
456                                 ShadowProperties $shadowProperties = null) {
457        if($shadowProperties == null) {
458            $shadowProperties = ShadowProperties::FromDefaults();
459        }
460
461        /* Validate the Data and DataDescription array */
462        $this->validateDataDescription("drawPieGraph", $data->getDataDescription(), FALSE);
463        $this->validateData("drawPieGraph", $data->getData());
464
465        /* Determine pie sum */
466        $Series  = 0;
467        $PieSum  = 0;
468        $rPieSum = 0;
469        foreach($data->getDataDescription()->values as $ColName) {
470            if($ColName != $data->getDataDescription()->getPosition()) {
471                $Series++;
472                $dataArray = $data->getData();
473                foreach(array_keys($dataArray) as $Key) {
474                    if(isset ($dataArray[$Key] [$ColName])) {
475                        if($dataArray[$Key] [$ColName] == 0) {
476                            $iValues [] = 0;
477                            $rValues [] = 0;
478                            $iLabels [] = $dataArray[$Key] [$data->getDataDescription()->getPosition()];
479                        } // Removed : $PieSum++; $rValues[] = 1;
480                        else {
481                            $PieSum += $dataArray[$Key] [$ColName];
482                            $iValues [] = $dataArray[$Key] [$ColName];
483                            $iLabels [] = $dataArray[$Key] [$data->getDataDescription()->getPosition()];
484                            $rValues [] = $dataArray[$Key] [$ColName];
485                            $rPieSum += $dataArray[$Key] [$ColName];
486                        }
487                    }
488                }
489            }
490        }
491
492        /* Validate serie */
493        if($Series != 1)
494            throw new Exception("Pie chart can only accept one serie of data.");
495        /** @todo Proper exception type needed here */
496
497        $SpliceDistanceRatio = $SpliceDistance;
498        $SkewHeight          = ($Radius * $Skew) / 100;
499        $SpliceRatio         = (360 - $SpliceDistanceRatio * count($iValues)) / $PieSum;
500        $SplicePercent       = 100 / $PieSum;
501        $rSplicePercent      = 100 / $rPieSum;
502
503        /* Calculate all polygons */
504        $Angle     = 0;
505        $CDev      = 5;
506        $TopPlots  = "";
507        $BotPlots  = "";
508        $aTopPlots = "";
509        $aBotPlots = "";
510        foreach($iValues as $Key => $Value) {
511            $XCenterPos  = cos(($Angle - $CDev + ($Value * $SpliceRatio + $SpliceDistanceRatio) / 2) * M_PI / 180) * $SpliceDistance + $XPos;
512            $YCenterPos  = sin(($Angle - $CDev + ($Value * $SpliceRatio + $SpliceDistanceRatio) / 2) * M_PI / 180) * $SpliceDistance + $YPos;
513            $XCenterPos2 = cos(($Angle + $CDev + ($Value * $SpliceRatio + $SpliceDistanceRatio) / 2) * M_PI / 180) * $SpliceDistance + $XPos;
514            $YCenterPos2 = sin(($Angle + $CDev + ($Value * $SpliceRatio + $SpliceDistanceRatio) / 2) * M_PI / 180) * $SpliceDistance + $YPos;
515
516            $TopPlots [$Key] []  = round($XCenterPos);
517            $BotPlots [$Key] []  = round($XCenterPos);
518            $TopPlots [$Key] []  = round($YCenterPos);
519            $BotPlots [$Key] []  = round($YCenterPos + $SpliceHeight);
520            $aTopPlots [$Key] [] = $XCenterPos;
521            $aBotPlots [$Key] [] = $XCenterPos;
522            $aTopPlots [$Key] [] = $YCenterPos;
523            $aBotPlots [$Key] [] = $YCenterPos + $SpliceHeight;
524
525            /* Process labels position & size */
526            $Caption = "";
527            if(!($DrawLabels == PIE_NOLABEL)) {
528                $TAngle = $Angle + ($Value * $SpliceRatio / 2);
529                if($DrawLabels == PIE_PERCENTAGE)
530                    $Caption = (round($rValues [$Key] * pow(10, $Decimals) * $rSplicePercent) / pow(10, $Decimals))."%";
531                elseif($DrawLabels == PIE_LABELS)
532                    $Caption = $iLabels [$Key];
533                elseif($DrawLabels == PIE_PERCENTAGE_LABEL)
534                    $Caption = $iLabels [$Key]."\r\n".(round($Value * pow(10, $Decimals) * $SplicePercent) / pow(10, $Decimals))."%";
535
536                $Position   = imageftbbox($this->FontSize, 0, $this->FontName, $Caption);
537                $TextWidth  = $Position [2] - $Position [0];
538                $TextHeight = abs($Position [1]) + abs($Position [3]);
539
540                $TX = cos(($TAngle) * M_PI / 180) * ($Radius + 10) + $XPos;
541
542                if($TAngle > 0 && $TAngle < 180)
543                    $TY = sin(($TAngle) * M_PI / 180) * ($SkewHeight + 10) + $YPos + $SpliceHeight + 4;
544                else
545                    $TY = sin(($TAngle) * M_PI / 180) * ($SkewHeight + 4) + $YPos - ($TextHeight / 2);
546
547                if($TAngle > 90 && $TAngle < 270)
548                    $TX = $TX - $TextWidth;
549
550                $this->canvas->drawText(
551                    $this->FontSize,
552                    0,
553                    new Point($TX, $TY),
554                    new Color(70, 70, 70),
555                    $this->FontName,
556                    $Caption,
557                    $shadowProperties
558                );
559            }
560
561            /* Process pie slices */
562            for($iAngle = $Angle; $iAngle <= $Angle + $Value * $SpliceRatio; $iAngle = $iAngle + .5) {
563                $TopX = cos($iAngle * M_PI / 180) * $Radius + $XPos;
564                $TopY = sin($iAngle * M_PI / 180) * $SkewHeight + $YPos;
565
566                $TopPlots [$Key] []  = round($TopX);
567                $BotPlots [$Key] []  = round($TopX);
568                $TopPlots [$Key] []  = round($TopY);
569                $BotPlots [$Key] []  = round($TopY + $SpliceHeight);
570                $aTopPlots [$Key] [] = $TopX;
571                $aBotPlots [$Key] [] = $TopX;
572                $aTopPlots [$Key] [] = $TopY;
573                $aBotPlots [$Key] [] = $TopY + $SpliceHeight;
574            }
575
576            $TopPlots [$Key] []  = round($XCenterPos2);
577            $BotPlots [$Key] []  = round($XCenterPos2);
578            $TopPlots [$Key] []  = round($YCenterPos2);
579            $BotPlots [$Key] []  = round($YCenterPos2 + $SpliceHeight);
580            $aTopPlots [$Key] [] = $XCenterPos2;
581            $aBotPlots [$Key] [] = $XCenterPos2;
582            $aTopPlots [$Key] [] = $YCenterPos2;
583            $aBotPlots [$Key] [] = $YCenterPos2 + $SpliceHeight;
584
585            $Angle = $iAngle + $SpliceDistanceRatio;
586        }
587
588        $this->drawPieGraphBottomPolygons(
589            $iValues, $BotPlots,
590            $EnhanceColors, $aBotPlots,
591            $shadowProperties
592        );
593
594        $this->drawPieGraphLayers(
595            $iValues, $TopPlots, $EnhanceColors,
596            $SpliceHeight, $this->palette, $shadowProperties
597        );
598
599        $this->drawPieGraphTopPolygons(
600            $iValues, $TopPlots, $EnhanceColors,
601            $aTopPlots, $shadowProperties
602        );
603    }
604
605    /**
606     * @todo Not really sure what this does, it appears to do several
607     * things at once (it was generated by pulling code out of
608     * drawPieChart())
609     */
610    private function processPieSlices(& $Angle, $SpliceRatio, $Value, $Radius, $XPos, $YPos, $XOffset, $YOffset, Color $color, array & $plotArray, ShadowProperties $shadowProperties) {
611        $lastPos = null;
612
613        for($iAngle = $Angle; $iAngle <= $Angle + $Value * $SpliceRatio; $iAngle = $iAngle + .5) {
614            $PosX = cos($iAngle * M_PI / 180) * $Radius + $XPos + $XOffset;
615            $PosY = sin($iAngle * M_PI / 180) * $Radius + $YPos + $YOffset;
616
617            $plotArray[] = round($PosX);
618            $plotArray[] = round($PosY);
619
620            $currentPos = new Point($PosX, $PosY);
621
622            if($iAngle == $Angle || $iAngle == $Angle + $Value * $SpliceRatio || $iAngle + .5 > $Angle + $Value * $SpliceRatio)
623                $this->canvas->drawLine(
624                    new Point($XPos + $XOffset, $YPos + $YOffset),
625                    $currentPos,
626                    $color,
627                    $this->LineWidth,
628                    $this->LineDotSize,
629                    $shadowProperties
630                );
631
632            if($lastPos != null)
633                $this->canvas->drawLine(
634                    $lastPos,
635                    $currentPos,
636                    $color,
637                    $this->LineWidth,
638                    $this->LineDotSize,
639                    $shadowProperties
640                );
641
642            $lastPos = $currentPos;
643        }
644
645        /* Update the angle in the caller to the final angle we
646           * reached while processing */
647        $Angle = $iAngle;
648    }
649
650    private function drawPieGraphBottomPolygons(array $iValues, array $BotPlots, $EnhanceColors, array $aBotPlots, ShadowProperties $shadowProperties) {
651        foreach(array_keys($iValues) as $Key) {
652            $this->canvas->drawFilledPolygon(
653                $BotPlots [$Key],
654                (count($BotPlots [$Key]) + 1) / 2,
655                $this->palette->colors[$Key]->addRGBIncrement(-20)
656            );
657
658            if($EnhanceColors) {
659                $En = -10;
660            } else {
661                $En = 0;
662            }
663
664            for($j = 0; $j <= count($aBotPlots [$Key]) - 4; $j = $j + 2) {
665                $this->canvas->drawLine(
666                    new Point($aBotPlots [$Key] [$j],
667                              $aBotPlots [$Key] [$j + 1]),
668                    new Point($aBotPlots [$Key] [$j + 2],
669                              $aBotPlots [$Key] [$j + 3]),
670                    $this->palette->colors[$Key]->addRGBIncrement($En),
671                    $this->LineWidth,
672                    $this->LineDotSize,
673                    $shadowProperties
674                );
675            }
676        }
677    }
678
679    private function drawPieGraphLayers($iValues, $TopPlots, $EnhanceColors, $SpliceHeight, Palette $palette, ShadowProperties $shadowProperties) {
680        if($EnhanceColors) {
681            $ColorRatio = 30 / $SpliceHeight;
682        } else {
683            $ColorRatio = 25 / $SpliceHeight;
684        }
685        for($i = $SpliceHeight - 1; $i >= 1; $i--) {
686            foreach(array_keys($iValues) as $Key) {
687                $Plots = "";
688                $Plot  = 0;
689                foreach($TopPlots [$Key] as $Value2) {
690                    $Plot++;
691                    if($Plot % 2 == 1)
692                        $Plots [] = $Value2;
693                    else
694                        $Plots [] = $Value2 + $i;
695                }
696                $this->canvas->drawFilledPolygon(
697                    $Plots,
698                    (count($Plots) + 1) / 2,
699                    $palette->colors[$Key]->addRGBIncrement(-10)
700                );
701
702                $Index = count($Plots);
703                if($EnhanceColors) {
704                    $ColorFactor = -20 + ($SpliceHeight - $i) * $ColorRatio;
705                } else {
706                    $ColorFactor = 0;
707                }
708
709                $this->canvas->drawAntialiasPixel(
710                    new Point($Plots[0], $Plots[1]),
711                    $palette->colors[$Key]->addRGBIncrement($ColorFactor),
712                    $shadowProperties
713                );
714
715                $this->canvas->drawAntialiasPixel(
716                    new Point($Plots[2], $Plots[3]),
717                    $palette->colors[$Key]->addRGBIncrement($ColorFactor),
718                    $shadowProperties
719                );
720
721                $this->canvas->drawAntialiasPixel(
722                    new Point($Plots[$Index - 4], $Plots [$Index - 3]),
723                    $palette->colors[$Key]->addRGBIncrement($ColorFactor),
724                    $shadowProperties
725                );
726            }
727        }
728    }
729
730    /**
731     * @brief Draw the polygons that form the top of a 3D pie chart
732     */
733    private function drawPieGraphTopPolygons($iValues, $TopPlots, $EnhanceColors, $aTopPlots, ShadowProperties $shadowProperties) {
734        for($Key = count($iValues) - 1; $Key >= 0; $Key--) {
735            $this->canvas->drawFilledPolygon(
736                $TopPlots [$Key],
737                (count($TopPlots [$Key]) + 1) / 2,
738                $this->palette->colors[$Key]
739            );
740
741            if($EnhanceColors) {
742                $En = 10;
743            } else {
744                $En = 0;
745            }
746            for($j = 0; $j <= count($aTopPlots [$Key]) - 4; $j = $j + 2)
747                $this->canvas->drawLine(
748                    new Point($aTopPlots[$Key][$j],
749                              $aTopPlots[$Key][$j + 1]),
750                    new Point($aTopPlots [$Key] [$j + 2],
751                              $aTopPlots [$Key] [$j + 3]),
752                    $this->palette->colors[$Key]->addRGBIncrement($En),
753                    $this->LineWidth,
754                    $this->LineDotSize,
755                    $shadowProperties
756                );
757        }
758    }
759}
760