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 2003/05/26 20:49:48 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 | // Setup the start
|
---|
434 | $accsum = 0;
|
---|
435 | $a = $startangle;
|
---|
436 | $a = $this->NormAngle($a);
|
---|
437 |
|
---|
438 | //
|
---|
439 | // Step 1 . Split all slices that crosses 90 or 270
|
---|
440 | //
|
---|
441 | $idx=0;
|
---|
442 | $adjexplode=array();
|
---|
443 | $numcolors = count($colors);
|
---|
444 | for($i=0; $i<count($data); ++$i, ++$idx ) {
|
---|
445 | $da = $data[$i]/$sum * 360;
|
---|
446 |
|
---|
447 | if( empty($this->explode_radius[$i]) )
|
---|
448 | $this->explode_radius[$i]=0;
|
---|
449 |
|
---|
450 | $expscale=1;
|
---|
451 | if( $aaoption == 1 )
|
---|
452 | $expscale=2;
|
---|
453 |
|
---|
454 | $la = $a + $da/2;
|
---|
455 | $explode = array( $xc + $this->explode_radius[$i]*cos($la*M_PI/180)*$expscale,
|
---|
456 | $yc - $this->explode_radius[$i]*sin($la*M_PI/180) * ($h/$d) *$expscale );
|
---|
457 | $adjexplode[$idx] = $explode;
|
---|
458 | $labeldata[$i] = array($la,$explode[0],$explode[1]);
|
---|
459 | $originalangles[$i] = array($a,$a+$da);
|
---|
460 |
|
---|
461 | $ne = $this->NormAngle($a+$da);
|
---|
462 | if( $da <= 180 ) {
|
---|
463 | // If the slice size is <= 90 it can at maximum cut across
|
---|
464 | // one boundary (either 90 or 270) where it needs to be split
|
---|
465 | $split=-1; // no split
|
---|
466 | if( ($da<=90 && ($a <= 90 && $ne > 90)) ||
|
---|
467 | (($da <= 180 && $da >90) && (($a < 90 || $a >= 270) && $ne > 90)) ) {
|
---|
468 | $split = 90;
|
---|
469 | }
|
---|
470 | elseif( ($da<=90 && ($a <= 270 && $ne > 270)) ||
|
---|
471 | (($da<=180 && $da>90) && ($a >= 90 && $a < 270 && ($a+$da) > 270 )) ) {
|
---|
472 | $split = 270;
|
---|
473 | }
|
---|
474 | if( $split > 0 ) { // split in two
|
---|
475 | $angles[$idx] = array($a,$split);
|
---|
476 | $adjcolors[$idx] = $colors[$i % $numcolors];
|
---|
477 | $adjexplode[$idx] = $explode;
|
---|
478 | $angles[++$idx] = array($split,$ne);
|
---|
479 | $adjcolors[$idx] = $colors[$i % $numcolors];
|
---|
480 | $adjexplode[$idx] = $explode;
|
---|
481 | }
|
---|
482 | else { // no split
|
---|
483 | $angles[$idx] = array($a,$ne);
|
---|
484 | $adjcolors[$idx] = $colors[$i % $numcolors];
|
---|
485 | $adjexplode[$idx] = $explode;
|
---|
486 | }
|
---|
487 | }
|
---|
488 | else {
|
---|
489 | // da>180
|
---|
490 | // Slice may, depending on position, cross one or two
|
---|
491 | // bonudaries
|
---|
492 |
|
---|
493 | if( $a < 90 )
|
---|
494 | $split = 90;
|
---|
495 | elseif( $a <= 270 )
|
---|
496 | $split = 270;
|
---|
497 | else
|
---|
498 | $split = 90;
|
---|
499 |
|
---|
500 | $angles[$idx] = array($a,$split);
|
---|
501 | $adjcolors[$idx] = $colors[$i % $numcolors];
|
---|
502 | $adjexplode[$idx] = $explode;
|
---|
503 | //if( $a+$da > 360-$split ) {
|
---|
504 | // For slices larger than 270 degrees we might cross
|
---|
505 | // another boundary as well. This means that we must
|
---|
506 | // split the slice further. The comparison gets a little
|
---|
507 | // bit complicated since we must take into accound that
|
---|
508 | // a pie might have a startangle >0 and hence a slice might
|
---|
509 | // wrap around the 0 angle.
|
---|
510 | // Three cases:
|
---|
511 | // a) Slice starts before 90 and hence gets a split=90, but
|
---|
512 | // we must also check if we need to split at 270
|
---|
513 | // b) Slice starts after 90 but before 270 and slices
|
---|
514 | // crosses 90 (after a wrap around of 0)
|
---|
515 | // c) If start is > 270 (hence the firstr split is at 90)
|
---|
516 | // and the slice is so large that it goes all the way
|
---|
517 | // around 270.
|
---|
518 | if( ($a < 90 && ($a+$da > 270)) ||
|
---|
519 | ($a > 90 && $a<=270 && ($a+$da>360+90) ) ||
|
---|
520 | ($a > 270 && $this->NormAngle($a+$da)>270) ) {
|
---|
521 | $angles[++$idx] = array($split,360-$split);
|
---|
522 | $adjcolors[$idx] = $colors[$i % $numcolors];
|
---|
523 | $adjexplode[$idx] = $explode;
|
---|
524 | $angles[++$idx] = array(360-$split,$ne);
|
---|
525 | $adjcolors[$idx] = $colors[$i % $numcolors];
|
---|
526 | $adjexplode[$idx] = $explode;
|
---|
527 | }
|
---|
528 | else {
|
---|
529 | // Just a simple split to the previous decided
|
---|
530 | // angle.
|
---|
531 | $angles[++$idx] = array($split,$ne);
|
---|
532 | $adjcolors[$idx] = $colors[$i % $numcolors];
|
---|
533 | $adjexplode[$idx] = $explode;
|
---|
534 | }
|
---|
535 | }
|
---|
536 | $a += $da;
|
---|
537 | $a = $this->NormAngle($a);
|
---|
538 | }
|
---|
539 |
|
---|
540 | // Total number of slices
|
---|
541 | $n = count($angles);
|
---|
542 |
|
---|
543 | for($i=0; $i<$n; ++$i) {
|
---|
544 | list($dbgs,$dbge) = $angles[$i];
|
---|
545 | }
|
---|
546 |
|
---|
547 | //
|
---|
548 | // Step 2. Find start index (first pie that starts in upper left quadrant)
|
---|
549 | //
|
---|
550 | $minval = $angles[0][0];
|
---|
551 | $min = 0;
|
---|
552 | for( $i=0; $i<$n; ++$i ) {
|
---|
553 | if( $angles[$i][0] < $minval ) {
|
---|
554 | $minval = $angles[$i][0];
|
---|
555 | $min = $i;
|
---|
556 | }
|
---|
557 | }
|
---|
558 | $j = $min;
|
---|
559 | $cnt = 0;
|
---|
560 | while( $angles[$j][1] <= 90 ) {
|
---|
561 | $j++;
|
---|
562 | if( $j>=$n) {
|
---|
563 | $j=0;
|
---|
564 | }
|
---|
565 | if( $cnt > $n ) {
|
---|
566 | JpGraphError::Raise("Pie3D Internal error (#1). Trying to wrap twice when looking for start index");
|
---|
567 | }
|
---|
568 | ++$cnt;
|
---|
569 | }
|
---|
570 | $start = $j;
|
---|
571 |
|
---|
572 | //
|
---|
573 | // Step 3. Print slices in z-order
|
---|
574 | //
|
---|
575 | $cnt = 0;
|
---|
576 |
|
---|
577 | // First stroke all the slices between 90 and 270 (left half circle)
|
---|
578 | // counterclockwise
|
---|
579 |
|
---|
580 | while( $angles[$j][0] < 270 && $aaoption !== 2 ) {
|
---|
581 |
|
---|
582 | list($x,$y) = $adjexplode[$j];
|
---|
583 |
|
---|
584 | $this->Pie3DSlice($img,$x,$y,$d,$h,$angles[$j][0],$angles[$j][1],
|
---|
585 | $z,$adjcolors[$j],$shadow);
|
---|
586 |
|
---|
587 | $last = array($x,$y,$j);
|
---|
588 |
|
---|
589 | $j++;
|
---|
590 | if( $j >= $n ) $j=0;
|
---|
591 | if( $cnt > $n ) {
|
---|
592 | JpGraphError::Raise("Pie3D Internal Error: Z-Sorting algorithm for 3D Pies is not working properly (2). Trying to wrap twice while stroking.");
|
---|
593 | }
|
---|
594 | ++$cnt;
|
---|
595 | }
|
---|
596 |
|
---|
597 | $slice_left = $n-$cnt;
|
---|
598 | $j=$start-1;
|
---|
599 | if($j<0) $j=$n-1;
|
---|
600 | $cnt = 0;
|
---|
601 |
|
---|
602 | // The stroke all slices from 90 to -90 (right half circle)
|
---|
603 | // clockwise
|
---|
604 | while( $cnt < $slice_left && $aaoption !== 2 ) {
|
---|
605 |
|
---|
606 | list($x,$y) = $adjexplode[$j];
|
---|
607 |
|
---|
608 | $this->Pie3DSlice($img,$x,$y,$d,$h,$angles[$j][0],$angles[$j][1],
|
---|
609 | $z,$adjcolors[$j],$shadow);
|
---|
610 | $j--;
|
---|
611 | if( $cnt > $n ) {
|
---|
612 | JpGraphError::Raise("Pie3D Internal Error: Z-Sorting algorithm for 3D Pies is not working properly (2). Trying to wrap twice while stroking.");
|
---|
613 | }
|
---|
614 | if($j<0) $j=$n-1;
|
---|
615 | $cnt++;
|
---|
616 | }
|
---|
617 |
|
---|
618 | // Now do a special thing. Stroke the last slice on the left
|
---|
619 | // halfcircle one more time. This is needed in the case where
|
---|
620 | // the slice close to 270 have been exploded. In that case the
|
---|
621 | // part of the slice close to the center of the pie might be
|
---|
622 | // slightly nagged.
|
---|
623 | if( $aaoption !== 2 )
|
---|
624 | $this->Pie3DSlice($img,$last[0],$last[1],$d,$h,$angles[$last[2]][0],
|
---|
625 | $angles[$last[2]][1],$z,$adjcolors[$last[2]],$shadow);
|
---|
626 |
|
---|
627 |
|
---|
628 | if( $aaoption !== 1 ) {
|
---|
629 | // Now print possible labels and add csim
|
---|
630 | $img->SetFont($this->value->ff,$this->value->fs);
|
---|
631 | $margin = $img->GetFontHeight()/2;
|
---|
632 | for($i=0; $i < count($data); ++$i ) {
|
---|
633 | $la = $labeldata[$i][0];
|
---|
634 | $x = $labeldata[$i][1] + cos($la*M_PI/180)*($d+$margin);
|
---|
635 | $y = $labeldata[$i][2] - sin($la*M_PI/180)*($h+$margin);
|
---|
636 | if( $la > 180 && $la < 360 ) $y += $z;
|
---|
637 | if( $this->labeltype == 0 )
|
---|
638 | if( $sum > 0 )
|
---|
639 | $l = 100*$data[$i]/$sum;
|
---|
640 | else
|
---|
641 | $l = 0;
|
---|
642 | else
|
---|
643 | $l = $data[$i];
|
---|
644 | if( isset($this->labels[$i]) && is_string($this->labels[$i]) )
|
---|
645 | $l=sprintf($this->labels[$i],$l);
|
---|
646 |
|
---|
647 | $this->StrokeLabels($l,$img,$labeldata[$i][0]*M_PI/180,$x,$y,$z);
|
---|
648 |
|
---|
649 | $this->AddSliceToCSIM($i,$labeldata[$i][1],$labeldata[$i][2],$h*2,$d*2,$z,
|
---|
650 | $originalangles[$i][0],$originalangles[$i][1]);
|
---|
651 | }
|
---|
652 | }
|
---|
653 |
|
---|
654 | //
|
---|
655 | // Finally add potential lines in pie
|
---|
656 | //
|
---|
657 |
|
---|
658 | if( $edgecolor=="" || $aaoption !== 0 ) return;
|
---|
659 |
|
---|
660 | $accsum = 0;
|
---|
661 | $a = $startangle;
|
---|
662 | $a = $this->NormAngle($a);
|
---|
663 |
|
---|
664 | $a *= M_PI/180.0;
|
---|
665 |
|
---|
666 | $idx=0;
|
---|
667 | $img->PushColor($edgecolor);
|
---|
668 | $img->SetLineWeight($edgeweight);
|
---|
669 |
|
---|
670 | $fulledge = true;
|
---|
671 | for($i=0; $i < count($data) && $fulledge; ++$i ) {
|
---|
672 | if( empty($this->explode_radius[$i]) )
|
---|
673 | $this->explode_radius[$i]=0;
|
---|
674 | if( $this->explode_radius[$i] > 0 ) {
|
---|
675 | $fulledge = false;
|
---|
676 | }
|
---|
677 | }
|
---|
678 |
|
---|
679 |
|
---|
680 | for($i=0; $i < count($data); ++$i, ++$idx ) {
|
---|
681 |
|
---|
682 | $da = $data[$i]/$sum * 2*M_PI;
|
---|
683 | $this->StrokeFullSliceFrame($img,$xc,$yc,$a,$a+$da,$d,$h,$z,$edgecolor,
|
---|
684 | $this->explode_radius[$i],$fulledge);
|
---|
685 | $a += $da;
|
---|
686 | }
|
---|
687 | $img->PopColor();
|
---|
688 | }
|
---|
689 |
|
---|
690 | function StrokeFullSliceFrame($img,$xc,$yc,$sa,$ea,$w,$h,$z,$edgecolor,$exploderadius,$fulledge) {
|
---|
691 | $step = 0.02;
|
---|
692 |
|
---|
693 | if( $exploderadius > 0 ) {
|
---|
694 | $la = ($sa+$ea)/2;
|
---|
695 | $xc += $exploderadius*cos($la);
|
---|
696 | $yc -= $exploderadius*sin($la) * ($h/$w) ;
|
---|
697 |
|
---|
698 | }
|
---|
699 |
|
---|
700 | $p = array($xc,$yc,$xc+$w*cos($sa),$yc-$h*sin($sa));
|
---|
701 |
|
---|
702 | for($a=$sa; $a < $ea; $a += $step ) {
|
---|
703 | $p[] = $xc + $w*cos($a);
|
---|
704 | $p[] = $yc - $h*sin($a);
|
---|
705 | }
|
---|
706 |
|
---|
707 | $p[] = $xc+$w*cos($ea);
|
---|
708 | $p[] = $yc-$h*sin($ea);
|
---|
709 | $p[] = $xc;
|
---|
710 | $p[] = $yc;
|
---|
711 |
|
---|
712 | $img->SetColor($edgecolor);
|
---|
713 | $img->Polygon($p);
|
---|
714 |
|
---|
715 | // Unfortunately we can't really draw the full edge around the whole of
|
---|
716 | // of the slice if any of the slices are exploded. The reason is that
|
---|
717 | // this algorithm is to simply. There are cases where the edges will
|
---|
718 | // "overwrite" other slices when they have been exploded.
|
---|
719 | // Doing the full, proper 3D hidden lines stiff is actually quite
|
---|
720 | // tricky. So for exploded pies we only draw the top edge. Not perfect
|
---|
721 | // but the "real" solution is much more complicated.
|
---|
722 | if( $fulledge && !( $sa > 0 && $sa < M_PI && $ea < M_PI) ) {
|
---|
723 |
|
---|
724 | if($sa < M_PI && $ea > M_PI)
|
---|
725 | $sa = M_PI;
|
---|
726 |
|
---|
727 | if($sa < 2*M_PI && (($ea >= 2*M_PI) || ($ea > 0 && $ea < $sa ) ) )
|
---|
728 | $ea = 2*M_PI;
|
---|
729 |
|
---|
730 | if( $sa >= M_PI && $ea <= 2*M_PI ) {
|
---|
731 | $p = array($xc + $w*cos($sa),$yc - $h*sin($sa),
|
---|
732 | $xc + $w*cos($sa),$z + $yc - $h*sin($sa));
|
---|
733 |
|
---|
734 | for($a=$sa+$step; $a < $ea; $a += $step ) {
|
---|
735 | $p[] = $xc + $w*cos($a);
|
---|
736 | $p[] = $z + $yc - $h*sin($a);
|
---|
737 | }
|
---|
738 | $p[] = $xc + $w*cos($ea);
|
---|
739 | $p[] = $z + $yc - $h*sin($ea);
|
---|
740 | $p[] = $xc + $w*cos($ea);
|
---|
741 | $p[] = $yc - $h*sin($ea);
|
---|
742 | $img->SetColor($edgecolor);
|
---|
743 | $img->Polygon($p);
|
---|
744 | }
|
---|
745 | }
|
---|
746 | }
|
---|
747 |
|
---|
748 | function Stroke($img,$aaoption=0) {
|
---|
749 |
|
---|
750 | // If user hasn't set the colors use the theme array
|
---|
751 | if( $this->setslicecolors==null ) {
|
---|
752 | $colors = array_keys($img->rgb->rgb_table);
|
---|
753 | sort($colors);
|
---|
754 | $idx_a=$this->themearr[$this->theme];
|
---|
755 | $ca = array();
|
---|
756 | $n = count($idx_a);
|
---|
757 | for($i=0; $i < $n; ++$i)
|
---|
758 | $ca[$i] = $colors[$idx_a[$i]];
|
---|
759 | }
|
---|
760 | else {
|
---|
761 | $ca = $this->setslicecolors;
|
---|
762 | }
|
---|
763 |
|
---|
764 | if( $this->posx <= 1 && $this->posx > 0 )
|
---|
765 | $xc = round($this->posx*$img->width);
|
---|
766 | else
|
---|
767 | $xc = $this->posx ;
|
---|
768 |
|
---|
769 | if( $this->posy <= 1 && $this->posy > 0 )
|
---|
770 | $yc = round($this->posy*$img->height);
|
---|
771 | else
|
---|
772 | $yc = $this->posy ;
|
---|
773 |
|
---|
774 | if( $this->radius <= 1 ) {
|
---|
775 | $width = floor($this->radius*min($img->width,$img->height));
|
---|
776 | // Make sure that the pie doesn't overflow the image border
|
---|
777 | // The 0.9 factor is simply an extra margin to leave some space
|
---|
778 | // between the pie an the border of the image.
|
---|
779 | $width = min($width,min($xc*0.9,($yc*90/$this->angle-$width/4)*0.9));
|
---|
780 | }
|
---|
781 | else {
|
---|
782 | $width = $this->radius * ($aaoption === 1 ? 2 : 1 ) ;
|
---|
783 | }
|
---|
784 |
|
---|
785 | // Add a sanity check for width
|
---|
786 | if( $width < 1 ) {
|
---|
787 | JpGraphError::Raise("Width for 3D Pie is 0. Specify a size > 0");
|
---|
788 | exit();
|
---|
789 | }
|
---|
790 |
|
---|
791 | // Establish a thickness. By default the thickness is a fifth of the
|
---|
792 | // pie slice width (=pie radius) but since the perspective depends
|
---|
793 | // on the inclination angle we use some heuristics to make the edge
|
---|
794 | // slightly thicker the less the angle.
|
---|
795 |
|
---|
796 | // Has user specified an absolute thickness? In that case use
|
---|
797 | // that instead
|
---|
798 |
|
---|
799 | if( $this->iThickness ) {
|
---|
800 | $thick = $this->iThickness;
|
---|
801 | $thick *= ($aaoption === 1 ? 2 : 1 );
|
---|
802 | }
|
---|
803 | else
|
---|
804 | $thick = $width/12;
|
---|
805 | $a = $this->angle;
|
---|
806 | if( $a <= 30 ) $thick *= 1.6;
|
---|
807 | elseif( $a <= 40 ) $thick *= 1.4;
|
---|
808 | elseif( $a <= 50 ) $thick *= 1.2;
|
---|
809 | elseif( $a <= 60 ) $thick *= 1.0;
|
---|
810 | elseif( $a <= 70 ) $thick *= 0.8;
|
---|
811 | elseif( $a <= 80 ) $thick *= 0.7;
|
---|
812 | else $thick *= 0.6;
|
---|
813 |
|
---|
814 | $thick = floor($thick);
|
---|
815 |
|
---|
816 | if( $this->explode_all )
|
---|
817 | for($i=0;$i<count($this->data);++$i)
|
---|
818 | $this->explode_radius[$i]=$this->explode_r;
|
---|
819 |
|
---|
820 | $this->Pie3D($aaoption,$img,$this->data, $ca, $xc, $yc, $width, $this->angle,
|
---|
821 | $thick, 0.65, $this->startangle, $this->edgecolor, $this->edgeweight);
|
---|
822 |
|
---|
823 | // Adjust title position
|
---|
824 | if( $aaoption != 1 ) {
|
---|
825 | $this->title->Pos($xc,$yc-$this->title->GetFontHeight($img)-$width/2-$this->title->margin, "center","bottom");
|
---|
826 | $this->title->Stroke($img);
|
---|
827 | }
|
---|
828 | }
|
---|
829 |
|
---|
830 | //---------------
|
---|
831 | // PRIVATE METHODS
|
---|
832 |
|
---|
833 | // Position the labels of each slice
|
---|
834 | function StrokeLabels($label,$img,$a,$xp,$yp,$z) {
|
---|
835 | $this->value->halign="left";
|
---|
836 | $this->value->valign="top";
|
---|
837 | $this->value->margin=0;
|
---|
838 |
|
---|
839 | // Position the axis title.
|
---|
840 | // dx, dy is the offset from the top left corner of the bounding box that sorrounds the text
|
---|
841 | // that intersects with the extension of the corresponding axis. The code looks a little
|
---|
842 | // bit messy but this is really the only way of having a reasonable position of the
|
---|
843 | // axis titles.
|
---|
844 | $img->SetFont($this->value->ff,$this->value->fs,$this->value->fsize);
|
---|
845 | $h=$img->GetTextHeight($label);
|
---|
846 | // For numeric values the format of the display value
|
---|
847 | // must be taken into account
|
---|
848 | if( is_numeric($label) ) {
|
---|
849 | if( $label > 0 )
|
---|
850 | $w=$img->GetTextWidth(sprintf($this->value->format,$label));
|
---|
851 | else
|
---|
852 | $w=$img->GetTextWidth(sprintf($this->value->negormat,$label));
|
---|
853 | }
|
---|
854 | else
|
---|
855 | $w=$img->GetTextWidth($label);
|
---|
856 | while( $a > 2*M_PI ) $a -= 2*M_PI;
|
---|
857 | if( $a>=7*M_PI/4 || $a <= M_PI/4 ) $dx=0;
|
---|
858 | if( $a>=M_PI/4 && $a <= 3*M_PI/4 ) $dx=($a-M_PI/4)*2/M_PI;
|
---|
859 | if( $a>=3*M_PI/4 && $a <= 5*M_PI/4 ) $dx=1;
|
---|
860 | if( $a>=5*M_PI/4 && $a <= 7*M_PI/4 ) $dx=(1-($a-M_PI*5/4)*2/M_PI);
|
---|
861 |
|
---|
862 | if( $a>=7*M_PI/4 ) $dy=(($a-M_PI)-3*M_PI/4)*2/M_PI;
|
---|
863 | if( $a<=M_PI/4 ) $dy=(1-$a*2/M_PI);
|
---|
864 | if( $a>=M_PI/4 && $a <= 3*M_PI/4 ) $dy=1;
|
---|
865 | if( $a>=3*M_PI/4 && $a <= 5*M_PI/4 ) $dy=(1-($a-3*M_PI/4)*2/M_PI);
|
---|
866 | if( $a>=5*M_PI/4 && $a <= 7*M_PI/4 ) $dy=0;
|
---|
867 |
|
---|
868 | $x = round($xp-$dx*$w);
|
---|
869 | $y = round($yp-$dy*$h);
|
---|
870 |
|
---|
871 | /*
|
---|
872 | // Mark anchor point for debugging
|
---|
873 | $img->SetColor('red');
|
---|
874 | $img->Line($xp-10,$yp,$xp+10,$yp);
|
---|
875 | $img->Line($xp,$yp-10,$xp,$yp+10);
|
---|
876 | */
|
---|
877 |
|
---|
878 | $this->value->Stroke($img,$label,$x,$y);
|
---|
879 | }
|
---|
880 | } // Class
|
---|
881 |
|
---|
882 | /* EOF */
|
---|
883 | ?>
|
---|