Subversion Repositories Sites.obs-saisons.fr

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

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