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