source: trunk/xgraph/jpgraph/jpgraph_pie3d.php @ 42

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