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 | //=================================================== |
---|
17 | class 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 | ?> |
---|