1<?php
2/*=======================================================================
3// File:	JPGRAPH_PIE3D.PHP
4// Description: 3D Pie plot extension for JpGraph
5// Created: 	2001-03-24
6// Author:	Johan Persson (johanp@aditus.nu)
7// Ver:		$Id: jpgraph_pie3d.php,v 1.1 2006/04/27 11:33:03 k-fish Exp $
8//
9// Copyright (c) Aditus Consulting. All rights reserved.
10//========================================================================
11*/
12
13//===================================================
14// CLASS PiePlot3D
15// Description: Plots a 3D pie with a specified projection
16// angle between 20 and 70 degrees.
17//===================================================
18class PiePlot3D extends PiePlot {
19    var $labelhintcolor="red",$showlabelhint=true;
20    var $angle=50;
21    var $edgecolor="", $edgeweight=1;
22    var $iThickness=false;
23
24//---------------
25// CONSTRUCTOR
26    function PiePlot3d(&$data) {
27	$this->radius = 0.5;
28	$this->data = $data;
29	$this->title = new Text("");
30	$this->title->SetFont(FF_FONT1,FS_BOLD);
31	$this->value = new DisplayValue();
32	$this->value->Show();
33	$this->value->SetFormat('%.0f%%');
34    }
35
36//---------------
37// PUBLIC METHODS
38
39    // Set label arrays
40    function SetLegends($aLegend) {
41	$this->legends = array_reverse(array_slice($aLegend,0,count($this->data)));
42    }
43
44    function SetSliceColors($aColors) {
45	$this->setslicecolors = $aColors;
46    }
47
48    function Legend(&$aGraph) {
49	parent::Legend($aGraph);
50	$aGraph->legend->txtcol = array_reverse($aGraph->legend->txtcol);
51    }
52
53    function SetCSIMTargets($targets,$alts=null) {
54	$this->csimtargets = $targets;
55	$this->csimalts = $alts;
56    }
57
58    // Should the slices be separated by a line? If color is specified as "" no line
59    // will be used to separate pie slices.
60    function SetEdge($aColor='black',$aWeight=1) {
61	$this->edgecolor = $aColor;
62	$this->edgeweight = $aWeight;
63    }
64
65    // Dummy function to make Pie3D behave in a similair way to 2D
66    function ShowBorder($exterior=true,$interior=true) {
67	JpGraphError::RaiseL(14001);
68//('Pie3D::ShowBorder() . Deprecated function. Use Pie3D::SetEdge() to control the edges around slices.');
69    }
70
71    // Specify projection angle for 3D in degrees
72    // Must be between 20 and 70 degrees
73    function SetAngle($a) {
74	if( $a<5 || $a>90 )
75	    JpGraphError::RaiseL(14002);
76//("PiePlot3D::SetAngle() 3D Pie projection angle must be between 5 and 85 degrees.");
77	else
78	    $this->angle = $a;
79    }
80
81    function AddSliceToCSIM($i,$xc,$yc,$height,$width,$thick,$sa,$ea) {  //Slice number, ellipse centre (x,y), height, width, start angle, end angle
82
83	$sa *= M_PI/180;
84	$ea *= M_PI/180;
85
86	//add coordinates of the centre to the map
87	$coords = "$xc, $yc";
88
89	//add coordinates of the first point on the arc to the map
90	$xp = floor($width*cos($sa)/2+$xc);
91	$yp = floor($yc-$height*sin($sa)/2);
92	$coords.= ", $xp, $yp";
93
94	//If on the front half, add the thickness offset
95	if ($sa >= M_PI && $sa <= 2*M_PI*1.01) {
96	    $yp = floor($yp+$thick);
97	    $coords.= ", $xp, $yp";
98	}
99
100	//add coordinates every 0.2 radians
101	$a=$sa+0.2;
102	while ($a<$ea) {
103	    $xp = floor($width*cos($a)/2+$xc);
104	    if ($a >= M_PI && $a <= 2*M_PI*1.01) {
105		$yp = floor($yc-($height*sin($a)/2)+$thick);
106	    } else {
107		$yp = floor($yc-$height*sin($a)/2);
108	    }
109	    $coords.= ", $xp, $yp";
110	    $a += 0.2;
111	}
112
113	//Add the last point on the arc
114	$xp = floor($width*cos($ea)/2+$xc);
115	$yp = floor($yc-$height*sin($ea)/2);
116
117
118	if ($ea >= M_PI && $ea <= 2*M_PI*1.01) {
119	    $coords.= ", $xp, ".floor($yp+$thick);
120	}
121	$coords.= ", $xp, $yp";
122	$alt='';
123	if( !empty($this->csimalts[$i]) ) {
124	    $tmp=sprintf($this->csimalts[$i],$this->data[$i]);
125	    $alt="alt=\"$tmp\" title=\"$tmp\"";
126	}
127	if( !empty($this->csimtargets[$i]) )
128	    $this->csimareas .= "<area shape=\"poly\" coords=\"$coords\" href=\"".$this->csimtargets[$i]."\" $alt />\n";
129    }
130
131    function SetLabels($aLabels,$aLblPosAdj="auto") {
132	$this->labels = $aLabels;
133	$this->ilabelposadj=$aLblPosAdj;
134    }
135
136
137    // Distance from the pie to the labels
138    function SetLabelMargin($m) {
139	$this->value->SetMargin($m);
140    }
141
142    // Show a thin line from the pie to the label for a specific slice
143    function ShowLabelHint($f=true) {
144	$this->showlabelhint=$f;
145    }
146
147    // Set color of hint line to label for each slice
148    function SetLabelHintColor($c) {
149	$this->labelhintcolor=$c;
150    }
151
152    function SetHeight($aHeight) {
153      $this->iThickness = $aHeight;
154    }
155
156
157// Normalize Angle between 0-360
158    function NormAngle($a) {
159	// Normalize anle to 0 to 2M_PI
160	//
161	if( $a > 0 ) {
162	    while($a > 360) $a -= 360;
163	}
164	else {
165	    while($a < 0) $a += 360;
166	}
167	if( $a < 0 )
168	    $a = 360 + $a;
169
170	if( $a == 360 ) $a=0;
171	return $a;
172    }
173
174
175
176// Draw one 3D pie slice at position ($xc,$yc) with height $z
177    function Pie3DSlice($img,$xc,$yc,$w,$h,$sa,$ea,$z,$fillcolor,$shadow=0.65) {
178
179	// Due to the way the 3D Pie algorithm works we are
180	// guaranteed that any slice we get into this method
181	// belongs to either the left or right side of the
182	// pie ellipse. Hence, no slice will cross 90 or 270
183	// point.
184	if( ($sa < 90 && $ea > 90) || ( ($sa > 90 && $sa < 270) && $ea > 270) ) {
185	    JpGraphError::RaiseL(14003);//('Internal assertion failed. Pie3D::Pie3DSlice');
186	    exit(1);
187	}
188
189	$p[] = array();
190
191	// Setup pre-calculated values
192	$rsa = $sa/180*M_PI;	// to Rad
193	$rea = $ea/180*M_PI;	// to Rad
194	$sinsa = sin($rsa);
195	$cossa = cos($rsa);
196	$sinea = sin($rea);
197	$cosea = cos($rea);
198
199	// p[] is the points for the overall slice and
200	// pt[] is the points for the top pie
201
202	// Angular step when approximating the arc with a polygon train.
203	$step = 0.05;
204
205	if( $sa >= 270 ) {
206	    if( $ea > 360 || ($ea > 0 && $ea <= 90) ) {
207		if( $ea > 0 && $ea <= 90 ) {
208		    // Adjust angle to simplify conditions in loops
209		    $rea += 2*M_PI;
210		}
211
212		$p = array($xc,$yc,$xc,$yc+$z,
213			   $xc+$w*$cossa,$z+$yc-$h*$sinsa);
214		$pt = array($xc,$yc,$xc+$w*$cossa,$yc-$h*$sinsa);
215
216		for( $a=$rsa; $a < 2*M_PI; $a += $step ) {
217		    $tca = cos($a);
218		    $tsa = sin($a);
219		    $p[] = $xc+$w*$tca;
220		    $p[] = $z+$yc-$h*$tsa;
221		    $pt[] = $xc+$w*$tca;
222		    $pt[] = $yc-$h*$tsa;
223		}
224
225		$pt[] = $xc+$w;
226		$pt[] = $yc;
227
228		$p[] = $xc+$w;
229		$p[] = $z+$yc;
230		$p[] = $xc+$w;
231		$p[] = $yc;
232		$p[] = $xc;
233		$p[] = $yc;
234
235		for( $a=2*M_PI+$step; $a < $rea; $a += $step ) {
236		    $pt[] = $xc + $w*cos($a);
237		    $pt[] = $yc - $h*sin($a);
238		}
239
240		$pt[] = $xc+$w*$cosea;
241		$pt[] = $yc-$h*$sinea;
242		$pt[] = $xc;
243		$pt[] = $yc;
244
245	    }
246	    else {
247		$p = array($xc,$yc,$xc,$yc+$z,
248			   $xc+$w*$cossa,$z+$yc-$h*$sinsa);
249		$pt = array($xc,$yc,$xc+$w*$cossa,$yc-$h*$sinsa);
250
251		$rea = $rea == 0.0 ? 2*M_PI : $rea;
252		for( $a=$rsa; $a < $rea; $a += $step ) {
253		    $tca = cos($a);
254		    $tsa = sin($a);
255		    $p[] = $xc+$w*$tca;
256		    $p[] = $z+$yc-$h*$tsa;
257		    $pt[] = $xc+$w*$tca;
258		    $pt[] = $yc-$h*$tsa;
259		}
260
261		$pt[] = $xc+$w*$cosea;
262		$pt[] = $yc-$h*$sinea;
263		$pt[] = $xc;
264		$pt[] = $yc;
265
266		$p[] = $xc+$w*$cosea;
267		$p[] = $z+$yc-$h*$sinea;
268		$p[] = $xc+$w*$cosea;
269		$p[] = $yc-$h*$sinea;
270		$p[] = $xc;
271		$p[] = $yc;
272	    }
273	}
274	elseif( $sa >= 180 ) {
275	    $p = array($xc,$yc,$xc,$yc+$z,$xc+$w*$cosea,$z+$yc-$h*$sinea);
276	    $pt = array($xc,$yc,$xc+$w*$cosea,$yc-$h*$sinea);
277
278	    for( $a=$rea; $a>$rsa; $a -= $step ) {
279		$tca = cos($a);
280		$tsa = sin($a);
281		$p[] = $xc+$w*$tca;
282		$p[] = $z+$yc-$h*$tsa;
283		$pt[] = $xc+$w*$tca;
284		$pt[] = $yc-$h*$tsa;
285	    }
286
287	    $pt[] = $xc+$w*$cossa;
288	    $pt[] = $yc-$h*$sinsa;
289	    $pt[] = $xc;
290	    $pt[] = $yc;
291
292	    $p[] = $xc+$w*$cossa;
293	    $p[] = $z+$yc-$h*$sinsa;
294	    $p[] = $xc+$w*$cossa;
295	    $p[] = $yc-$h*$sinsa;
296	    $p[] = $xc;
297	    $p[] = $yc;
298
299	}
300	elseif( $sa >= 90 ) {
301	    if( $ea > 180 ) {
302		$p = array($xc,$yc,$xc,$yc+$z,$xc+$w*$cosea,$z+$yc-$h*$sinea);
303		$pt = array($xc,$yc,$xc+$w*$cosea,$yc-$h*$sinea);
304
305		for( $a=$rea; $a > M_PI; $a -= $step ) {
306		    $tca = cos($a);
307		    $tsa = sin($a);
308		    $p[] = $xc+$w*$tca;
309		    $p[] = $z + $yc - $h*$tsa;
310		    $pt[] = $xc+$w*$tca;
311		    $pt[] = $yc-$h*$tsa;
312		}
313
314		$p[] = $xc-$w;
315		$p[] = $z+$yc;
316		$p[] = $xc-$w;
317		$p[] = $yc;
318		$p[] = $xc;
319		$p[] = $yc;
320
321		$pt[] = $xc-$w;
322		$pt[] = $z+$yc;
323		$pt[] = $xc-$w;
324		$pt[] = $yc;
325
326		for( $a=M_PI-$step; $a > $rsa; $a -= $step ) {
327		    $pt[] = $xc + $w*cos($a);
328		    $pt[] = $yc - $h*sin($a);
329		}
330
331		$pt[] = $xc+$w*$cossa;
332		$pt[] = $yc-$h*$sinsa;
333		$pt[] = $xc;
334		$pt[] = $yc;
335
336	    }
337	    else { // $sa >= 90 && $ea <= 180
338		$p = array($xc,$yc,$xc,$yc+$z,
339			   $xc+$w*$cosea,$z+$yc-$h*$sinea,
340			   $xc+$w*$cosea,$yc-$h*$sinea,
341			   $xc,$yc);
342
343		$pt = array($xc,$yc,$xc+$w*$cosea,$yc-$h*$sinea);
344
345		for( $a=$rea; $a>$rsa; $a -= $step ) {
346		    $pt[] = $xc + $w*cos($a);
347		    $pt[] = $yc - $h*sin($a);
348		}
349
350		$pt[] = $xc+$w*$cossa;
351		$pt[] = $yc-$h*$sinsa;
352		$pt[] = $xc;
353		$pt[] = $yc;
354
355	    }
356	}
357	else { // sa > 0 && ea < 90
358
359	    $p = array($xc,$yc,$xc,$yc+$z,
360		       $xc+$w*$cossa,$z+$yc-$h*$sinsa,
361		       $xc+$w*$cossa,$yc-$h*$sinsa,
362		       $xc,$yc);
363
364	    $pt = array($xc,$yc,$xc+$w*$cossa,$yc-$h*$sinsa);
365
366	    for( $a=$rsa; $a < $rea; $a += $step ) {
367		$pt[] = $xc + $w*cos($a);
368		$pt[] = $yc - $h*sin($a);
369	    }
370
371	    $pt[] = $xc+$w*$cosea;
372	    $pt[] = $yc-$h*$sinea;
373	    $pt[] = $xc;
374	    $pt[] = $yc;
375	}
376
377	$img->PushColor($fillcolor.":".$shadow);
378	$img->FilledPolygon($p);
379	$img->PopColor();
380
381	$img->PushColor($fillcolor);
382	$img->FilledPolygon($pt);
383	$img->PopColor();
384    }
385
386    function SetStartAngle($aStart) {
387	if( $aStart < 0 || $aStart > 360 ) {
388	    JpGraphError::RaiseL(14004);//('Slice start angle must be between 0 and 360 degrees.');
389	}
390	$this->startangle = $aStart;
391    }
392
393// Draw a 3D Pie
394    function Pie3D($aaoption,$img,$data,$colors,$xc,$yc,$d,$angle,$z,
395		   $shadow=0.65,$startangle=0,$edgecolor="",$edgeweight=1) {
396
397	//---------------------------------------------------------------------------
398	// As usual the algorithm get more complicated than I originally
399	// envisioned. I believe that this is as simple as it is possible
400	// to do it with the features I want. It's a good exercise to start
401	// thinking on how to do this to convince your self that all this
402	// is really needed for the general case.
403	//
404	// The algorithm two draw 3D pies without "real 3D" is done in
405	// two steps.
406	// First imagine the pie cut in half through a thought line between
407	// 12'a clock and 6'a clock. It now easy to imagine that we can plot
408	// the individual slices for each half by starting with the topmost
409	// pie slice and continue down to 6'a clock.
410	//
411	// In the algortithm this is done in three principal steps
412	// Step 1. Do the knife cut to ensure by splitting slices that extends
413	// over the cut line. This is done by splitting the original slices into
414	// upto 3 subslices.
415	// Step 2. Find the top slice for each half
416	// Step 3. Draw the slices from top to bottom
417	//
418	// The thing that slightly complicates this scheme with all the
419	// angle comparisons below is that we can have an arbitrary start
420	// angle so we must take into account the different equivalence classes.
421	// For the same reason we must walk through the angle array in a
422	// modulo fashion.
423	//
424	// Limitations of algorithm:
425	// * A small exploded slice which crosses the 270 degree point
426	//   will get slightly nagged close to the center due to the fact that
427	//   we print the slices in Z-order and that the slice left part
428	//   get printed first and might get slightly nagged by a larger
429	//   slice on the right side just before the right part of the small
430	//   slice. Not a major problem though.
431	//---------------------------------------------------------------------------
432
433
434	// Determine the height of the ellippse which gives an
435	// indication of the inclination angle
436	$h = ($angle/90.0)*$d;
437	$sum = 0;
438	for($i=0; $i<count($data); ++$i ) {
439	    $sum += $data[$i];
440	}
441
442	// Special optimization
443	if( $sum==0 ) return;
444
445	if( $this->labeltype == 2 ) {
446	    $this->adjusted_data = $this->AdjPercentage($data);
447	}
448
449	// Setup the start
450	$accsum = 0;
451	$a = $startangle;
452	$a = $this->NormAngle($a);
453
454	//
455	// Step 1 . Split all slices that crosses 90 or 270
456	//
457	$idx=0;
458	$adjexplode=array();
459	$numcolors = count($colors);
460	for($i=0; $i<count($data); ++$i, ++$idx ) {
461	    $da = $data[$i]/$sum * 360;
462
463	    if( empty($this->explode_radius[$i]) )
464		$this->explode_radius[$i]=0;
465
466	    $expscale=1;
467	    if( $aaoption == 1 )
468		$expscale=2;
469
470	    $la = $a + $da/2;
471	    $explode = array( $xc + $this->explode_radius[$i]*cos($la*M_PI/180)*$expscale,
472		              $yc - $this->explode_radius[$i]*sin($la*M_PI/180) * ($h/$d) *$expscale );
473	    $adjexplode[$idx] = $explode;
474	    $labeldata[$i] = array($la,$explode[0],$explode[1]);
475	    $originalangles[$i] = array($a,$a+$da);
476
477	    $ne = $this->NormAngle($a+$da);
478	    if( $da <= 180 ) {
479		// If the slice size is <= 90 it can at maximum cut across
480		// one boundary (either 90 or 270) where it needs to be split
481		$split=-1; // no split
482		if( ($da<=90 && ($a <= 90 && $ne > 90)) ||
483		    (($da <= 180 && $da >90)  && (($a < 90 || $a >= 270) && $ne > 90)) ) {
484		    $split = 90;
485		}
486		elseif( ($da<=90 && ($a <= 270 && $ne > 270)) ||
487		        (($da<=180 && $da>90) && ($a >= 90 && $a < 270 && ($a+$da) > 270 )) ) {
488		    $split = 270;
489		}
490		if( $split > 0 ) { // split in two
491		    $angles[$idx] = array($a,$split);
492		    $adjcolors[$idx] = $colors[$i % $numcolors];
493		    $adjexplode[$idx] = $explode;
494		    $angles[++$idx] = array($split,$ne);
495		    $adjcolors[$idx] = $colors[$i % $numcolors];
496		    $adjexplode[$idx] = $explode;
497		}
498		else { // no split
499		    $angles[$idx] = array($a,$ne);
500		    $adjcolors[$idx] = $colors[$i  % $numcolors];
501		    $adjexplode[$idx] = $explode;
502		}
503	    }
504	    else {
505		// da>180
506		// Slice may, depending on position, cross one or two
507		// bonudaries
508
509		if( $a < 90 )
510		    $split = 90;
511		elseif( $a <= 270 )
512		    $split = 270;
513		else
514		    $split = 90;
515
516		$angles[$idx] = array($a,$split);
517		$adjcolors[$idx] = $colors[$i % $numcolors];
518		$adjexplode[$idx] = $explode;
519		//if( $a+$da > 360-$split ) {
520		// For slices larger than 270 degrees we might cross
521		// another boundary as well. This means that we must
522		// split the slice further. The comparison gets a little
523		// bit complicated since we must take into accound that
524		// a pie might have a startangle >0 and hence a slice might
525		// wrap around the 0 angle.
526		// Three cases:
527		//  a) Slice starts before 90 and hence gets a split=90, but
528		//     we must also check if we need to split at 270
529		//  b) Slice starts after 90 but before 270 and slices
530		//     crosses 90 (after a wrap around of 0)
531		//  c) If start is > 270 (hence the firstr split is at 90)
532		//     and the slice is so large that it goes all the way
533		//     around 270.
534		if( ($a < 90 && ($a+$da > 270)) ||
535		    ($a > 90 && $a<=270 && ($a+$da>360+90) ) ||
536		    ($a > 270 && $this->NormAngle($a+$da)>270) ) {
537		    $angles[++$idx] = array($split,360-$split);
538		    $adjcolors[$idx] = $colors[$i % $numcolors];
539		    $adjexplode[$idx] = $explode;
540		    $angles[++$idx] = array(360-$split,$ne);
541		    $adjcolors[$idx] = $colors[$i % $numcolors];
542		    $adjexplode[$idx] = $explode;
543		}
544		else {
545		    // Just a simple split to the previous decided
546		    // angle.
547		    $angles[++$idx] = array($split,$ne);
548		    $adjcolors[$idx] = $colors[$i % $numcolors];
549		    $adjexplode[$idx] = $explode;
550		}
551	    }
552	    $a += $da;
553	    $a = $this->NormAngle($a);
554	}
555
556	// Total number of slices
557	$n = count($angles);
558
559	for($i=0; $i<$n; ++$i) {
560	    list($dbgs,$dbge) = $angles[$i];
561	}
562
563	//
564	// Step 2. Find start index (first pie that starts in upper left quadrant)
565	//
566	$minval = $angles[0][0];
567	$min = 0;
568	for( $i=0; $i<$n; ++$i ) {
569	    if( $angles[$i][0] < $minval ) {
570		$minval = $angles[$i][0];
571		$min = $i;
572	    }
573	}
574	$j = $min;
575	$cnt = 0;
576	while( $angles[$j][1] <= 90 ) {
577	    $j++;
578	    if( $j>=$n) {
579		$j=0;
580	    }
581	    if( $cnt > $n ) {
582		JpGraphError::RaiseL(14005);
583//("Pie3D Internal error (#1). Trying to wrap twice when looking for start index");
584	    }
585	    ++$cnt;
586	}
587	$start = $j;
588
589	//
590	// Step 3. Print slices in z-order
591	//
592	$cnt = 0;
593
594	// First stroke all the slices between 90 and 270 (left half circle)
595	// counterclockwise
596
597	while( $angles[$j][0] < 270  && $aaoption !== 2 ) {
598
599	    list($x,$y) = $adjexplode[$j];
600
601	    $this->Pie3DSlice($img,$x,$y,$d,$h,$angles[$j][0],$angles[$j][1],
602			      $z,$adjcolors[$j],$shadow);
603
604	    $last = array($x,$y,$j);
605
606	    $j++;
607	    if( $j >= $n ) $j=0;
608	    if( $cnt > $n ) {
609		JpGraphError::RaiseL(14006);
610//("Pie3D Internal Error: Z-Sorting algorithm for 3D Pies is not working properly (2). Trying to wrap twice while stroking.");
611	    }
612	    ++$cnt;
613	}
614
615	$slice_left = $n-$cnt;
616	$j=$start-1;
617	if($j<0) $j=$n-1;
618	$cnt = 0;
619
620	// The stroke all slices from 90 to -90 (right half circle)
621	// clockwise
622	while( $cnt < $slice_left  && $aaoption !== 2 ) {
623
624	    list($x,$y) = $adjexplode[$j];
625
626	    $this->Pie3DSlice($img,$x,$y,$d,$h,$angles[$j][0],$angles[$j][1],
627			      $z,$adjcolors[$j],$shadow);
628	    $j--;
629	    if( $cnt > $n ) {
630		JpGraphError::RaiseL(14006);
631//("Pie3D Internal Error: Z-Sorting algorithm for 3D Pies is not working properly (2). Trying to wrap twice while stroking.");
632	    }
633	    if($j<0) $j=$n-1;
634	    $cnt++;
635	}
636
637	// Now do a special thing. Stroke the last slice on the left
638	// halfcircle one more time.  This is needed in the case where
639	// the slice close to 270 have been exploded. In that case the
640	// part of the slice close to the center of the pie might be
641	// slightly nagged.
642	if( $aaoption !== 2 )
643	    $this->Pie3DSlice($img,$last[0],$last[1],$d,$h,$angles[$last[2]][0],
644			      $angles[$last[2]][1],$z,$adjcolors[$last[2]],$shadow);
645
646
647	if( $aaoption !== 1 ) {
648	    // Now print possible labels and add csim
649	    $img->SetFont($this->value->ff,$this->value->fs);
650	    $margin = $img->GetFontHeight()/2 + $this->value->margin ;
651	    for($i=0; $i < count($data); ++$i ) {
652		$la = $labeldata[$i][0];
653		$x = $labeldata[$i][1] + cos($la*M_PI/180)*($d+$margin);
654		$y = $labeldata[$i][2] - sin($la*M_PI/180)*($h+$margin);
655		if( $la > 180 && $la < 360 ) $y += $z;
656		if( $this->labeltype == 0 ) {
657		    if( $sum > 0 )
658			$l = 100*$data[$i]/$sum;
659		    else
660			$l = 0;
661		}
662		elseif( $this->labeltype == 1 ) {
663		    $l = $data[$i];
664		}
665		else {
666		    $l = $this->adjusted_data[$i];
667		}
668		if( isset($this->labels[$i]) && is_string($this->labels[$i]) )
669		    $l=sprintf($this->labels[$i],$l);
670
671		$this->StrokeLabels($l,$img,$labeldata[$i][0]*M_PI/180,$x,$y,$z);
672
673		$this->AddSliceToCSIM($i,$labeldata[$i][1],$labeldata[$i][2],$h*2,$d*2,$z,
674				      $originalangles[$i][0],$originalangles[$i][1]);
675	    }
676	}
677
678	//
679	// Finally add potential lines in pie
680	//
681
682	if( $edgecolor=="" || $aaoption !== 0 ) return;
683
684	$accsum = 0;
685	$a = $startangle;
686	$a = $this->NormAngle($a);
687
688	$a *= M_PI/180.0;
689
690	$idx=0;
691	$img->PushColor($edgecolor);
692	$img->SetLineWeight($edgeweight);
693
694	$fulledge = true;
695	for($i=0; $i < count($data) && $fulledge; ++$i ) {
696	    if( empty($this->explode_radius[$i]) )
697		$this->explode_radius[$i]=0;
698	    if( $this->explode_radius[$i] > 0 ) {
699		$fulledge = false;
700	    }
701	}
702
703
704	for($i=0; $i < count($data); ++$i, ++$idx ) {
705
706	    $da = $data[$i]/$sum * 2*M_PI;
707	    $this->StrokeFullSliceFrame($img,$xc,$yc,$a,$a+$da,$d,$h,$z,$edgecolor,
708					$this->explode_radius[$i],$fulledge);
709	    $a += $da;
710	}
711	$img->PopColor();
712    }
713
714    function StrokeFullSliceFrame($img,$xc,$yc,$sa,$ea,$w,$h,$z,$edgecolor,$exploderadius,$fulledge) {
715	$step = 0.02;
716
717	if( $exploderadius > 0 ) {
718	    $la = ($sa+$ea)/2;
719	    $xc += $exploderadius*cos($la);
720	    $yc -= $exploderadius*sin($la) * ($h/$w) ;
721
722	}
723
724	$p = array($xc,$yc,$xc+$w*cos($sa),$yc-$h*sin($sa));
725
726	for($a=$sa; $a < $ea; $a += $step ) {
727	    $p[] = $xc + $w*cos($a);
728	    $p[] = $yc - $h*sin($a);
729	}
730
731	$p[] = $xc+$w*cos($ea);
732	$p[] = $yc-$h*sin($ea);
733	$p[] = $xc;
734	$p[] = $yc;
735
736	$img->SetColor($edgecolor);
737	$img->Polygon($p);
738
739	// Unfortunately we can't really draw the full edge around the whole of
740	// of the slice if any of the slices are exploded. The reason is that
741	// this algorithm is to simply. There are cases where the edges will
742	// "overwrite" other slices when they have been exploded.
743	// Doing the full, proper 3D hidden lines stiff is actually quite
744	// tricky. So for exploded pies we only draw the top edge. Not perfect
745	// but the "real" solution is much more complicated.
746	if( $fulledge && !( $sa > 0 && $sa < M_PI && $ea < M_PI) ) {
747
748	    if($sa < M_PI && $ea > M_PI)
749		$sa = M_PI;
750
751	    if($sa < 2*M_PI && (($ea >= 2*M_PI) || ($ea > 0 && $ea < $sa ) ) )
752		$ea = 2*M_PI;
753
754	    if( $sa >= M_PI && $ea <= 2*M_PI ) {
755		$p = array($xc + $w*cos($sa),$yc - $h*sin($sa),
756			   $xc + $w*cos($sa),$z + $yc - $h*sin($sa));
757
758		for($a=$sa+$step; $a < $ea; $a += $step ) {
759		    $p[] = $xc + $w*cos($a);
760		    $p[] = $z + $yc - $h*sin($a);
761		}
762		$p[] = $xc + $w*cos($ea);
763		$p[] = $z + $yc - $h*sin($ea);
764		$p[] = $xc + $w*cos($ea);
765		$p[] = $yc - $h*sin($ea);
766		$img->SetColor($edgecolor);
767		$img->Polygon($p);
768	    }
769	}
770    }
771
772    function Stroke($img,$aaoption=0) {
773	$n = count($this->data);
774
775	// If user hasn't set the colors use the theme array
776   	if( $this->setslicecolors==null ) {
777	    $colors = array_keys($img->rgb->rgb_table);
778	    sort($colors);
779	    $idx_a=$this->themearr[$this->theme];
780	    $ca = array();
781	    $m = count($idx_a);
782	    for($i=0; $i < $m; ++$i)
783		$ca[$i] = $colors[$idx_a[$i]];
784	    $ca = array_reverse(array_slice($ca,0,$n));
785	}
786   	else {
787	    $ca = $this->setslicecolors;
788	}
789
790
791	if( $this->posx <= 1 && $this->posx > 0 )
792	    $xc = round($this->posx*$img->width);
793	else
794	    $xc = $this->posx ;
795
796	if( $this->posy <= 1 && $this->posy > 0 )
797	    $yc = round($this->posy*$img->height);
798	else
799	    $yc = $this->posy ;
800
801	if( $this->radius <= 1 ) {
802	    $width = floor($this->radius*min($img->width,$img->height));
803	    // Make sure that the pie doesn't overflow the image border
804	    // The 0.9 factor is simply an extra margin to leave some space
805	    // between the pie an the border of the image.
806	    $width = min($width,min($xc*0.9,($yc*90/$this->angle-$width/4)*0.9));
807	}
808	else {
809	    $width = $this->radius * ($aaoption === 1 ? 2 : 1 ) ;
810	}
811
812	// Add a sanity check for width
813	if( $width < 1 ) {
814	    JpGraphError::RaiseL(14007);//("Width for 3D Pie is 0. Specify a size > 0");
815	}
816
817	// Establish a thickness. By default the thickness is a fifth of the
818	// pie slice width (=pie radius) but since the perspective depends
819	// on the inclination angle we use some heuristics to make the edge
820	// slightly thicker the less the angle.
821
822	// Has user specified an absolute thickness? In that case use
823	// that instead
824
825	if( $this->iThickness ) {
826	  $thick = $this->iThickness;
827	  $thick *= ($aaoption === 1 ? 2 : 1 );
828	}
829	else
830	  $thick = $width/12;
831	$a = $this->angle;
832	if( $a <= 30 ) $thick *= 1.6;
833	elseif( $a <= 40 ) $thick *= 1.4;
834	elseif( $a <= 50 ) $thick *= 1.2;
835	elseif( $a <= 60 ) $thick *= 1.0;
836	elseif( $a <= 70 ) $thick *= 0.8;
837	elseif( $a <= 80 ) $thick *= 0.7;
838	else $thick *= 0.6;
839
840	$thick = floor($thick);
841
842	if( $this->explode_all )
843	    for($i=0; $i < $n; ++$i)
844		$this->explode_radius[$i]=$this->explode_r;
845
846	$this->Pie3D($aaoption,$img,$this->data, $ca, $xc, $yc, $width, $this->angle,
847	             $thick, 0.65, $this->startangle, $this->edgecolor, $this->edgeweight);
848
849	// Adjust title position
850	if( $aaoption != 1 ) {
851	    $this->title->Pos($xc,$yc-$this->title->GetFontHeight($img)-$width/2-$this->title->margin,			      "center","bottom");
852	    $this->title->Stroke($img);
853	}
854    }
855
856//---------------
857// PRIVATE METHODS
858
859    // Position the labels of each slice
860    function StrokeLabels($label,$img,$a,$xp,$yp,$z) {
861	$this->value->halign="left";
862	$this->value->valign="top";
863
864	// Position the axis title.
865	// dx, dy is the offset from the top left corner of the bounding box that sorrounds the text
866	// that intersects with the extension of the corresponding axis. The code looks a little
867	// bit messy but this is really the only way of having a reasonable position of the
868	// axis titles.
869	$img->SetFont($this->value->ff,$this->value->fs,$this->value->fsize);
870	$h=$img->GetTextHeight($label);
871	// For numeric values the format of the display value
872	// must be taken into account
873	if( is_numeric($label) ) {
874	    if( $label >= 0 )
875		$w=$img->GetTextWidth(sprintf($this->value->format,$label));
876	    else
877		$w=$img->GetTextWidth(sprintf($this->value->negformat,$label));
878	}
879	else
880	    $w=$img->GetTextWidth($label);
881	while( $a > 2*M_PI ) $a -= 2*M_PI;
882	if( $a>=7*M_PI/4 || $a <= M_PI/4 ) $dx=0;
883	if( $a>=M_PI/4 && $a <= 3*M_PI/4 ) $dx=($a-M_PI/4)*2/M_PI;
884	if( $a>=3*M_PI/4 && $a <= 5*M_PI/4 ) $dx=1;
885	if( $a>=5*M_PI/4 && $a <= 7*M_PI/4 ) $dx=(1-($a-M_PI*5/4)*2/M_PI);
886
887	if( $a>=7*M_PI/4 ) $dy=(($a-M_PI)-3*M_PI/4)*2/M_PI;
888	if( $a<=M_PI/4 ) $dy=(1-$a*2/M_PI);
889	if( $a>=M_PI/4 && $a <= 3*M_PI/4 ) $dy=1;
890	if( $a>=3*M_PI/4 && $a <= 5*M_PI/4 ) $dy=(1-($a-3*M_PI/4)*2/M_PI);
891	if( $a>=5*M_PI/4 && $a <= 7*M_PI/4 ) $dy=0;
892
893	$x = round($xp-$dx*$w);
894	$y = round($yp-$dy*$h);
895
896
897        // Mark anchor point for debugging
898	/*
899	$img->SetColor('red');
900	$img->Line($xp-10,$yp,$xp+10,$yp);
901	$img->Line($xp,$yp-10,$xp,$yp+10);
902	*/
903	$oldmargin = $this->value->margin;
904	$this->value->margin=0;
905	$this->value->Stroke($img,$label,$x,$y);
906	$this->value->margin=$oldmargin;
907
908    }
909} // Class
910
911/* EOF */
912?>
913