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