1<?php
2/*=======================================================================
3// File:	JPGRAPH_PIE.PHP
4// Description:	Pie plot extension for JpGraph
5// Created: 	2001-02-14
6// Author:	Johan Persson (johanp@aditus.nu)
7// Ver:		$Id: jpgraph_pie.php,v 1.1.1.1 2005/11/30 23:01:52 gth2 Exp $
8//
9// Copyright (c) Aditus Consulting. All rights reserved.
10//========================================================================
11*/
12
13
14// Defines for PiePlot::SetLabelType()
15DEFINE("PIE_VALUE_ABS",1);
16DEFINE("PIE_VALUE_PER",0);
17DEFINE("PIE_VALUE_PERCENTAGE",0);
18DEFINE("PIE_VALUE_ADJPERCENTAGE",2);
19DEFINE("PIE_VALUE_ADJPER",2);
20
21//===================================================
22// CLASS PiePlot
23// Description: Draws a pie plot
24//===================================================
25class PiePlot {
26    var $posx=0.5,$posy=0.5;
27    var $radius=0.3;
28    var $explode_radius=array(),$explode_all=false,$explode_r=20;
29    var $labels=null, $legends=null;
30    var $csimtargets=null;  // Array of targets for CSIM
31    var $csimareas='';		// Generated CSIM text
32    var $csimalts=null;		// ALT tags for corresponding target
33    var $data=null;
34    var $title;
35    var $startangle=0;
36    var $weight=1, $color="black";
37    var $legend_margin=6,$show_labels=true;
38    var $themearr = array(
39	"earth" 	=> array(136,34,40,45,46,62,63,134,74,10,120,136,141,168,180,77,209,218,346,395,89,430),
40	"pastel" => array(27,415,128,59,66,79,105,110,42,147,152,230,236,240,331,337,405,38),
41	"water"  => array(8,370,24,40,335,56,213,237,268,14,326,387,10,388),
42	"sand"   => array(27,168,34,170,19,50,65,72,131,209,46,393));
43    var $theme="earth";
44    var $setslicecolors=array();
45    var $labeltype=0; // Default to percentage
46    var $pie_border=true,$pie_interior_border=true;
47    var $value;
48    var $ishadowcolor='',$ishadowdrop=4;
49    var $ilabelposadj=1;
50    var $legendcsimtargets = array();
51    var $legendcsimalts = array();
52    var $adjusted_data = array();
53    var $guideline = null,$guidelinemargin=10;
54    var $iShowGuideLineForSingle = false;
55    var $iGuideLineCurve = false,$iGuideVFactor=1.4,$iGuideLineRFactor=0.8;
56//---------------
57// CONSTRUCTOR
58    function PiePlot($data) {
59	$this->data = array_reverse($data);
60	$this->title = new Text("");
61	$this->title->SetFont(FF_FONT1,FS_BOLD);
62	$this->value = new DisplayValue();
63	$this->value->Show();
64	$this->value->SetFormat('%.1f%%');
65	$this->guideline = new LineProperty();
66    }
67
68//---------------
69// PUBLIC METHODS
70    function SetCenter($x,$y=0.5) {
71	$this->posx = $x;
72	$this->posy = $y;
73    }
74
75    // Enable guideline and set drwaing policy
76    function SetGuideLines($aFlg=true,$aCurved=true,$aAlways=false) {
77	$this->guideline->Show($aFlg);
78	$this->iShowGuideLineForSingle = $aAlways;
79	$this->iGuideLineCurve = $aCurved;
80    }
81
82    // Adjuste the distance between labels and labels and pie
83    function SetGuideLinesAdjust($aVFactor,$aRFactor=0.8) {
84	$this->iGuideVFactor=$aVFactor;
85	$this->iGuideLineRFactor=$aRFactor;
86    }
87
88    function SetColor($aColor) {
89	$this->color = $aColor;
90    }
91
92    function SetSliceColors($aColors) {
93	$this->setslicecolors = $aColors;
94    }
95
96    function SetShadow($aColor='darkgray',$aDropWidth=4) {
97	$this->ishadowcolor = $aColor;
98	$this->ishadowdrop = $aDropWidth;
99    }
100
101    function SetCSIMTargets($targets,$alts=null) {
102	$this->csimtargets=array_reverse($targets);
103	if( is_array($alts) )
104	    $this->csimalts=array_reverse($alts);
105    }
106
107    function GetCSIMareas() {
108	return $this->csimareas;
109    }
110
111    function AddSliceToCSIM($i,$xc,$yc,$radius,$sa,$ea) {
112
113        //Slice number, ellipse centre (x,y), height, width, start angle, end angle
114	while( $sa > 2*M_PI ) $sa = $sa - 2*M_PI;
115	while( $ea > 2*M_PI ) $ea = $ea - 2*M_PI;
116
117	$sa = 2*M_PI - $sa;
118	$ea = 2*M_PI - $ea;
119
120	//add coordinates of the centre to the map
121	$coords = "$xc, $yc";
122
123	//add coordinates of the first point on the arc to the map
124	$xp = floor(($radius*cos($ea))+$xc);
125	$yp = floor($yc-$radius*sin($ea));
126	$coords.= ", $xp, $yp";
127	//add coordinates every 0.2 radians
128	$a=$ea+0.2;
129
130	// If we cross the 260-limit with a slice we need to handle
131	// the fact that end angle is smaller than start
132	if( $sa < $ea ) {
133	    while ($a <= 2*M_PI) {
134		$xp = floor($radius*cos($a)+$xc);
135		$yp = floor($yc-$radius*sin($a));
136		$coords.= ", $xp, $yp";
137		$a += 0.2;
138	    }
139	    $a -= 2*M_PI;
140	}
141
142	while ($a < $sa) {
143	    $xp = floor($radius*cos($a)+$xc);
144	    $yp = floor($yc-$radius*sin($a));
145	    $coords.= ", $xp, $yp";
146	    $a += 0.2;
147	}
148
149	//Add the last point on the arc
150	$xp = floor($radius*cos($sa)+$xc);
151	$yp = floor($yc-$radius*sin($sa));
152	$coords.= ", $xp, $yp";
153	if( !empty($this->csimtargets[$i]) ) {
154	    $this->csimareas .= "<area shape=\"poly\" coords=\"$coords\" href=\"".
155		$this->csimtargets[$i]."\"";
156	    if( !empty($this->csimalts[$i]) ) {
157		$tmp=sprintf($this->csimalts[$i],$this->data[$i]);
158		$this->csimareas .= " alt=\"$tmp\" title=\"$tmp\"";
159	    }
160	    $this->csimareas .= ">\n";
161	}
162    }
163
164
165    function SetTheme($aTheme) {
166	if( in_array($aTheme,array_keys($this->themearr)) )
167	    $this->theme = $aTheme;
168	else
169	    JpGraphError::Raise("PiePLot::SetTheme() Unknown theme: $aTheme");
170    }
171
172    function ExplodeSlice($e,$radius=20) {
173	if( ! is_integer($e) )
174	    JpGraphError::Raise('Argument to PiePlot::ExplodeSlice() must be an integer');
175	$this->explode_radius[$e]=$radius;
176    }
177
178    function ExplodeAll($radius=20) {
179	$this->explode_all=true;
180	$this->explode_r = $radius;
181    }
182
183    function Explode($aExplodeArr) {
184	if( !is_array($aExplodeArr) ) {
185	    JpGraphError::Raise("Argument to PiePlot::Explode() must be an array with integer distances.");
186	}
187	$this->explode_radius = $aExplodeArr;
188    }
189
190    function SetStartAngle($aStart) {
191	if( $aStart < 0 || $aStart > 360 ) {
192	    JpGraphError::Raise('Slice start angle must be between 0 and 360 degrees.');
193	}
194	$this->startangle = 360-$aStart;
195	$this->startangle *= M_PI/180;
196    }
197
198    function SetFont($family,$style=FS_NORMAL,$size=10) {
199		JpGraphError::Raise('PiePlot::SetFont() is deprecated. Use PiePlot->value->SetFont() instead.');
200    }
201
202    // Size in percentage
203    function SetSize($aSize) {
204	if( ($aSize>0 && $aSize<=0.5) || ($aSize>10 && $aSize<1000) )
205	    $this->radius = $aSize;
206	else
207	    JpGraphError::Raise("PiePlot::SetSize() Radius for pie must either be specified as a fraction
208                                [0, 0.5] of the size of the image or as an absolute size in pixels
209                                in the range [10, 1000]");
210    }
211
212    function SetFontColor($aColor) {
213	JpGraphError::Raise('PiePlot::SetFontColor() is deprecated. Use PiePlot->value->SetColor() instead.');
214    }
215
216    // Set label arrays
217    function SetLegends($aLegend) {
218	$this->legends = $aLegend;
219    }
220
221    // Set text labels for slices
222    function SetLabels($aLabels,$aLblPosAdj="auto") {
223	$this->labels = array_reverse($aLabels);
224	$this->ilabelposadj=$aLblPosAdj;
225    }
226
227    function SetLabelPos($aLblPosAdj) {
228	$this->ilabelposadj=$aLblPosAdj;
229    }
230
231    // Should we display actual value or percentage?
232    function SetLabelType($t) {
233	if( $t < 0 || $t > 2 )
234	    JpGraphError::Raise("PiePlot::SetLabelType() Type for pie plots must be 0 or 1 (not $t).");
235	$this->labeltype=$t;
236    }
237
238    // Deprecated.
239    function SetValueType($aType) {
240	$this->SetLabelType($aType);
241    }
242
243    // Should the circle around a pie plot be displayed
244    function ShowBorder($exterior=true,$interior=true) {
245	$this->pie_border = $exterior;
246	$this->pie_interior_border = $interior;
247    }
248
249    // Setup the legends (Framework method)
250    function Legend(&$graph) {
251	$colors = array_keys($graph->img->rgb->rgb_table);
252   	sort($colors);
253   	$ta=$this->themearr[$this->theme];
254   	$n = count($this->data);
255
256   	if( $this->setslicecolors==null ) {
257	    $numcolors=count($ta);
258	    if( get_class($this)==='pieplot3d' ) {
259		$ta = array_reverse(array_slice($ta,0,$n));
260	    }
261	}
262   	else {
263	    $this->setslicecolors = array_slice($this->setslicecolors,0,$n);
264	    $numcolors=count($this->setslicecolors);
265	    if( $graph->pieaa && get_class($this)==='pieplot' ) {
266		$this->setslicecolors = array_reverse($this->setslicecolors);
267	    }
268	}
269
270	$sum=0;
271	for($i=0; $i < $n; ++$i)
272	    $sum += $this->data[$i];
273
274	// Bail out with error if the sum is 0
275	if( $sum==0 )
276	    JpGraphError::Raise("Illegal pie plot. Sum of all data is zero for Pie!");
277
278	// Make sure we don't plot more values than data points
279	// (in case the user added more legends than data points)
280	$n = min(count($this->legends),count($this->data));
281	if( $this->legends != "" ) {
282	    $this->legends = array_reverse(array_slice($this->legends,0,$n));
283	}
284	for( $i=$n-1; $i >= 0; --$i ) {
285	    $l = $this->legends[$i];
286	    // Replace possible format with actual values
287	    if( $this->labeltype==0 ) {
288		$l = sprintf($l,100*$this->data[$i]/$sum);
289		$alt = sprintf($this->csimalts[$i],$this->data[$i]);
290
291	    }
292	    elseif( $this->labeltype == 1)  {
293		$l = sprintf($l,$this->data[$i]);
294		$alt = sprintf($this->csimalts[$i],$this->data[$i]);
295
296	    }
297	    else {
298		$l = sprintf($l,$this->adjusted_data[$i]);
299		$alt = sprintf($this->csimalts[$i],$this->adjusted_data[$i]);
300	    }
301
302
303	    if( $this->setslicecolors==null ) {
304		$graph->legend->Add($l,$colors[$ta[$i%$numcolors]],"",0,
305				    $this->csimtargets[$i],$alt);
306	    }
307	    else {
308		$graph->legend->Add($l,$this->setslicecolors[$i%$numcolors],"",0,
309				    $this->csimtargets[$i],$alt);
310	    }
311	}
312    }
313
314    // Adjust the rounded percetage value so that the sum of
315    // of the pie slices are always 100%
316    // Using the Hare/Niemeyer method
317    function AdjPercentage($aData,$aPrec=0) {
318	$mul=100;
319	if( $aPrec > 0 && $aPrec < 3 ) {
320	    if( $aPrec == 1 )
321		$mul=1000;
322		else
323		    $mul=10000;
324	}
325
326	$tmp = array();
327	$result = array();
328	$quote_sum=0;
329	$n = count($aData) ;
330	for( $i=0, $sum=0; $i < $n; ++$i )
331	    $sum+=$aData[$i];
332	foreach($aData as $index => $value) {
333	    $tmp_percentage=$value/$sum*$mul;
334	    $result[$index]=floor($tmp_percentage);
335	    $tmp[$index]=$tmp_percentage-$result[$index];
336	    $quote_sum+=$result[$index];
337	}
338	if( $quote_sum == $mul) {
339	    if( $mul > 100 ) {
340		$tmp = $mul / 100;
341		for( $i=0; $i < $n; ++$i ) {
342		    $result[$i] /= $tmp ;
343		}
344	    }
345	    return $result;
346	}
347	arsort($tmp,SORT_NUMERIC);
348	reset($tmp);
349	for($i=0; $i < $mul-$quote_sum; $i++)
350	{
351	    $result[key($tmp)]++;
352	    next($tmp);
353	}
354	if( $mul > 100 ) {
355	    $tmp = $mul / 100;
356	    for( $i=0; $i < $n; ++$i ) {
357		$result[$i] /= $tmp ;
358	    }
359	}
360	return $result;
361    }
362
363
364    function Stroke(&$img,$aaoption=0) {
365	// aaoption is used to handle antialias
366	// aaoption == 0 a normal pie
367	// aaoption == 1 just the body
368	// aaoption == 2 just the values
369
370	// Explode scaling. If anti anti alias we scale the image
371	// twice and we also need to scale the exploding distance
372	$expscale = $aaoption === 1 ? 2 : 1;
373
374	if( $this->labeltype == 2 ) {
375	    // Adjust the data so that it will add up to 100%
376	    $this->adjusted_data = $this->AdjPercentage($this->data);
377	}
378
379	$colors = array_keys($img->rgb->rgb_table);
380   	sort($colors);
381   	$ta=$this->themearr[$this->theme];
382	$n = count($this->data);
383
384   	if( $this->setslicecolors==null ) {
385	    $numcolors=count($ta);
386	}
387   	else {
388	    $this->setslicecolors = array_reverse(array_slice($this->setslicecolors,0,$n));
389	    $numcolors=count($this->setslicecolors);
390	    $tt = array_slice($this->setslicecolors,$n % $numcolors);
391	    $tt2 = array_slice($this->setslicecolors,0,$n % $numcolors);
392	    $tt2 = array_merge($tt, $tt2);
393	    $this->setslicecolors = $tt + $tt2;
394	}
395
396	// Draw the slices
397	$sum=0;
398	for($i=0; $i < $n; ++$i)
399	    $sum += $this->data[$i];
400
401	// Bail out with error if the sum is 0
402	if( $sum==0 )
403	    JpGraphError::Raise("Sum of all data is 0 for Pie.");
404
405	// Set up the pie-circle
406	if( $this->radius <= 1 )
407	    $radius = floor($this->radius*min($img->width,$img->height));
408	else {
409	    $radius = $aaoption === 1 ? $this->radius*2 : $this->radius;
410	}
411
412	if( $this->posx <= 1 && $this->posx > 0 )
413	    $xc = round($this->posx*$img->width);
414	else
415	    $xc = $this->posx ;
416
417	if( $this->posy <= 1 && $this->posy > 0 )
418	    $yc = round($this->posy*$img->height);
419	else
420	    $yc = $this->posy ;
421
422	$n = count($this->data);
423
424	if( $this->explode_all )
425	    for($i=0; $i < $n; ++$i)
426		$this->explode_radius[$i]=$this->explode_r;
427
428	if( $this->ishadowcolor != "" && $aaoption !== 2) {
429	    $accsum=0;
430	    $angle2 = $this->startangle;
431	    $img->SetColor($this->ishadowcolor);
432	    for($i=0; $sum > 0 && $i < $n; ++$i) {
433		$j = $n-$i-1;
434		$d = $this->data[$i];
435		$angle1 = $angle2;
436		$accsum += $d;
437		$angle2 = $this->startangle+2*M_PI*$accsum/$sum;
438		if( empty($this->explode_radius[$j]) )
439		    $this->explode_radius[$j]=0;
440
441		$la = 2*M_PI - (abs($angle2-$angle1)/2.0+$angle1);
442
443		$xcm = $xc + $this->explode_radius[$j]*cos($la)*$expscale;
444		$ycm = $yc - $this->explode_radius[$j]*sin($la)*$expscale;
445
446		$xcm += $this->ishadowdrop*$expscale;
447		$ycm += $this->ishadowdrop*$expscale;
448
449		$img->CakeSlice($xcm,$ycm,$radius,$radius,
450				$angle1*180/M_PI,$angle2*180/M_PI,$this->ishadowcolor);
451
452	    }
453	}
454
455	$accsum=0;
456	$angle2 = $this->startangle;
457	$img->SetColor($this->color);
458	for($i=0; $sum>0 && $i < $n; ++$i) {
459	    $j = $n-$i-1;
460	    if( empty($this->explode_radius[$j]) )
461		$this->explode_radius[$j]=0;
462	    $d = $this->data[$i];
463	    $angle1 = $angle2;
464	    $accsum += $d;
465	    $angle2 = $this->startangle+2*M_PI*$accsum/$sum;
466	    $this->la[$i] = 2*M_PI - (abs($angle2-$angle1)/2.0+$angle1);
467
468	    if( $d == 0 ) continue;
469
470	    if( $this->setslicecolors==null )
471		$slicecolor=$colors[$ta[$i%$numcolors]];
472	    else
473		$slicecolor=$this->setslicecolors[$i%$numcolors];
474
475	    if( $this->pie_interior_border && $aaoption===0 )
476		$img->SetColor($this->color);
477	    else
478		$img->SetColor($slicecolor);
479
480	    $arccolor = $this->pie_border && $aaoption===0 ? $this->color : "";
481
482	    $xcm = $xc + $this->explode_radius[$j]*cos($this->la[$i])*$expscale;
483	    $ycm = $yc - $this->explode_radius[$j]*sin($this->la[$i])*$expscale;
484
485	    if( $aaoption !== 2 ) {
486		$img->CakeSlice($xcm,$ycm,$radius-1,$radius-1,
487				$angle1*180/M_PI,$angle2*180/M_PI,$slicecolor,$arccolor);
488	    }
489
490	    if( $this->csimtargets && $aaoption !== 1 )
491		$this->AddSliceToCSIM($i,$xcm,$ycm,$radius,$angle1,$angle2);
492	}
493
494	// Format the titles for each slice
495	for( $i=0; $i < $n; ++$i) {
496	    if( $this->labeltype==0 ) {
497		if( $sum != 0 )
498		    $l = 100.0*$this->data[$i]/$sum;
499		else
500		    $l = 0.0;
501	    }
502	    elseif( $this->labeltype==1 ) {
503		$l = $this->data[$i]*1.0;
504	    }
505	    else {
506		$l = $this->adjusted_data[$i];
507	    }
508	    if( isset($this->labels[$i]) && is_string($this->labels[$i]) )
509		$this->labels[$i]=sprintf($this->labels[$i],$l);
510	    else
511		$this->labels[$i]=$l;
512	}
513
514	if( $this->value->show && $aaoption !== 1 ) {
515	    $this->StrokeAllLabels($img,$xc,$yc,$radius);
516	}
517
518	// Adjust title position
519	if( $aaoption !== 1 ) {
520	    $this->title->Pos($xc,
521			  $yc-$this->title->GetFontHeight($img)-$radius-$this->title->margin,
522			  "center","bottom");
523	    $this->title->Stroke($img);
524	}
525
526    }
527
528//---------------
529// PRIVATE METHODS
530
531    function NormAngle($a) {
532	while( $a < 0 ) $a += 2*M_PI;
533	while( $a > 2*M_PI ) $a -= 2*M_PI;
534	return $a;
535    }
536
537    function Quadrant($a) {
538	$a=$this->NormAngle($a);
539	if( $a > 0 && $a <= M_PI/2 )
540	    return 0;
541	if( $a > M_PI/2 && $a <= M_PI )
542	    return 1;
543	if( $a > M_PI && $a <= 1.5*M_PI )
544	    return 2;
545	if( $a > 1.5*M_PI )
546	    return 3;
547    }
548
549    function StrokeGuideLabels($img,$xc,$yc,$radius) {
550	$n = count($this->labels);
551
552	//-----------------------------------------------------------------------
553	// Step 1 of the algorithm is to construct a number of clusters
554	// a cluster is defined as all slices within the same quadrant (almost)
555	// that has an angualr distance less than the treshold
556	//-----------------------------------------------------------------------
557	$tresh_hold=25 * M_PI/180; // 25 degrees difference to be in a cluster
558	$incluster=false;	// flag if we are currently in a cluster or not
559	$clusters = array();	// array of clusters
560	$cidx=-1;		// running cluster index
561
562	// Go through all the labels and construct a number of clusters
563	for($i=0; $i < $n-1; ++$i) {
564	    // Calc the angle distance between two consecutive slices
565	    $a1=$this->la[$i];
566	    $a2=$this->la[$i+1];
567	    $q1 = $this->Quadrant($a1);
568	    $q2 = $this->Quadrant($a2);
569	    $diff = abs($a1-$a2);
570	    if( $diff < $tresh_hold ) {
571		if( $incluster ) {
572		    $clusters[$cidx][1]++;
573		    // Each cluster can only cover one quadrant
574		    // Do we cross a quadrant ( and must break the cluster)
575		    if( $q1 !=  $q2 ) {
576			// If we cross a quadrant boundary we normally start a
577			// new cluster. However we need to take the 12'a clock
578			// and 6'a clock positions into a special consideration.
579			// Case 1: WE go from q=1 to q=2 if the last slice on
580			// the cluster for q=1 is close to 12'a clock and the
581			// first slice in q=0 is small we extend the previous
582			// cluster
583			if( $q1 == 1 && $q2 == 0 && $a2 > (90-15)*M_PI/180 ) {
584			    if( $i < $n-2 ) {
585				$a3 = $this->la[$i+2];
586				// If there isn't a cluster coming up with the next-next slice
587				// we extend the previous cluster to cover this slice as well
588				if( abs($a3-$a2) >= $tresh_hold ) {
589				    $clusters[$cidx][1]++;
590				    $i++;
591				}
592			    }
593			}
594			elseif( $q1 == 3 && $q2 == 2 && $a2 > (270-15)*M_PI/180 ) {
595			    if( $i < $n-2 ) {
596				$a3 = $this->la[$i+2];
597				// If there isn't a cluster coming up with the next-next slice
598				// we extend the previous cluster to cover this slice as well
599				if( abs($a3-$a2) >= $tresh_hold ) {
600				    $clusters[$cidx][1]++;
601				    $i++;
602				}
603			    }
604			}
605
606			if( $q1==2 && $q2==1 && $a2 > (180-15)*M_PI/180 ) {
607			    $clusters[$cidx][1]++;
608			    $i++;
609			}
610
611			$incluster = false;
612		    }
613		}
614		elseif( $q1 == $q2)  {
615		    $incluster = true;
616		    // Now we have a special case for quadrant 0. If we previously
617		    // have a cluster of one in quadrant 0 we just extend that
618		    // cluster. If we don't do this then we risk that the label
619		    // for the cluster of one will cross the guide-line
620		    if( $q1 == 0 && $cidx > -1 &&
621			$clusters[$cidx][1] == 1 &&
622			$this->Quadrant($this->la[$clusters[$cidx][0]]) == 0 ) {
623			$clusters[$cidx][1]++;
624		    }
625		    else {
626			$cidx++;
627			$clusters[$cidx][0] = $i;
628			$clusters[$cidx][1] = 1;
629		    }
630		}
631		else {
632		    // Create a "cluster" of one since we are just crossing
633		    // a quadrant
634		    $cidx++;
635		    $clusters[$cidx][0] = $i;
636		    $clusters[$cidx][1] = 1;
637		}
638	    }
639	    else {
640		if( $incluster ) {
641		    // Add the last slice
642		    $clusters[$cidx][1]++;
643		    $incluster = false;
644		}
645		else { // Create a "cluster" of one
646		    $cidx++;
647		    $clusters[$cidx][0] = $i;
648		    $clusters[$cidx][1] = 1;
649		}
650	    }
651	}
652	// Handle the very last slice
653	if( $incluster ) {
654	    $clusters[$cidx][1]++;
655	}
656	else { // Create a "cluster" of one
657	    $cidx++;
658	    $clusters[$cidx][0] = $i;
659	    $clusters[$cidx][1] = 1;
660	}
661
662	/*
663	if( true ) {
664	    // Debug printout in labels
665	    for( $i=0; $i <= $cidx; ++$i ) {
666		for( $j=0; $j < $clusters[$i][1]; ++$j ) {
667		    $a = $this->la[$clusters[$i][0]+$j];
668		    $aa = round($a*180/M_PI);
669		    $q = $this->Quadrant($a);
670		    $this->labels[$clusters[$i][0]+$j]="[$q:$aa] $i:$j";
671		}
672	    }
673	}
674	*/
675
676	//-----------------------------------------------------------------------
677	// Step 2 of the algorithm is use the clusters and draw the labels
678	// and guidelines
679	//-----------------------------------------------------------------------
680
681	// We use the font height as the base factor for how far we need to
682	// spread the labels in the Y-direction.
683	$img->SetFont($this->value->ff,$this->value->fs,$this->value->fsize);
684	$fh = $img->GetFontHeight();
685	$origvstep=$fh*$this->iGuideVFactor;
686	$this->value->SetMargin(0);
687
688	// Number of clusters found
689	$nc = count($clusters);
690
691	// Walk through all the clusters
692	for($i=0; $i < $nc; ++$i) {
693
694	    // Start angle and number of slices in this cluster
695	    $csize = $clusters[$i][1];
696	    $a = $this->la[$clusters[$i][0]];
697	    $q = $this->Quadrant($a);
698
699	    // Now set up the start and end conditions to make sure that
700	    // in each cluster we walk through the all the slices starting with the slice
701	    // closest to the equator. Since all slices are numbered clockwise from "3'a clock"
702	    // we have different conditions depending on in which quadrant the slice lies within.
703	    if( $q == 0 ) {
704		$start = $csize-1; $idx = $start; $step = -1; $vstep = -$origvstep;
705	    }
706	    elseif( $q == 1 ) {
707		$start = 0; $idx = $start; $step = 1; $vstep = -$origvstep;
708	    }
709	    elseif( $q == 2 ) {
710		$start = $csize-1; $idx = $start; $step = -1; $vstep = $origvstep;
711	    }
712	    elseif( $q == 3 ) {
713		$start = 0; $idx = $start; $step = 1; $vstep = $origvstep;
714	    }
715
716	    // Walk through all slices within this cluster
717	    for($j=0; $j < $csize; ++$j) {
718		// Now adjust the position of the labels in each cluster starting
719		// with the slice that is closest to the equator of the pie
720		$a = $this->la[$clusters[$i][0]+$idx];
721
722		// Guide line start in the center of the arc of the slice
723		$r = $radius+$this->explode_radius[$n-1-($clusters[$i][0]+$idx)];
724		$x = round($r*cos($a)+$xc);
725		$y = round($yc-$r*sin($a));
726
727		// The distance from the arc depends on chosen font and the "R-Factor"
728		$r += $fh*$this->iGuideLineRFactor;
729
730		// Should the labels be placed curved along the pie or in straight columns
731		// outside the pie?
732		if( $this->iGuideLineCurve )
733		    $xt=round($r*cos($a)+$xc);
734
735		// If this is the first slice in the cluster we need some first time
736		// proessing
737		if( $idx == $start ) {
738		    if( ! $this->iGuideLineCurve )
739			$xt=round($r*cos($a)+$xc);
740		    $yt=round($yc-$r*sin($a));
741
742		    // Some special consideration in case this cluster starts
743		    // in quadrant 1 or 3 very close to the "equator" (< 20 degrees)
744		    // and the previous clusters last slice is within the tolerance.
745		    // In that case we add a font height to this labels Y-position
746		    // so it doesn't collide with
747		    // the slice in the previous cluster
748		    $prevcluster = ($i + ($nc-1) ) % $nc;
749		    $previdx=$clusters[$prevcluster][0]+$clusters[$prevcluster][1]-1;
750		    if( $q == 1 && $a > 160*M_PI/180 ) {
751			// Get the angle for the previous clusters last slice
752			$diff = abs($a-$this->la[$previdx]);
753			 if( $diff < $tresh_hold ) {
754			     $yt -= $fh;
755			 }
756		    }
757		    elseif( $q == 3 && $a > 340*M_PI/180 ) {
758			// We need to subtract 360 to compare angle distance between
759			// q=0 and q=3
760			$diff = abs($a-$this->la[$previdx]-360*M_PI/180);
761			if( $diff < $tresh_hold ) {
762			     $yt += $fh;
763			}
764		    }
765
766		}
767		else {
768		    // The step is at minimum $vstep but if the slices are relatively large
769		    // we make sure that we add at least a step that corresponds to the vertical
770		    // distance between the centers at the arc on the slice
771		    $prev_a = $this->la[$clusters[$i][0]+($idx-$step)];
772		    $dy = abs($radius*(sin($a)-sin($prev_a))*1.2);
773		    if( $vstep > 0 )
774			$yt += max($vstep,$dy);
775		    else
776			$yt += min($vstep,-$dy);
777		}
778
779		$label = $this->labels[$clusters[$i][0]+$idx];
780
781		if( $csize == 1 ) {
782		    // A "meta" cluster with only one slice
783		    $r = $radius+$this->explode_radius[$n-1-($clusters[$i][0]+$idx)];
784		    $rr = $r+$img->GetFontHeight()/2;
785		    $xt=round($rr*cos($a)+$xc);
786		    $yt=round($yc-$rr*sin($a));
787		    $this->StrokeLabel($label,$img,$xc,$yc,$a,$r);
788		    if( $this->iShowGuideLineForSingle )
789			$this->guideline->Stroke($img,$x,$y,$xt,$yt);
790		}
791		else {
792		    $this->guideline->Stroke($img,$x,$y,$xt,$yt);
793		    if( $q==1 || $q==2 ) {
794			// Left side of Pie
795			$this->guideline->Stroke($img,$xt,$yt,$xt-$this->guidelinemargin,$yt);
796			$lbladj = -$this->guidelinemargin-5;
797			$this->value->halign = "right";
798			$this->value->valign = "center";
799		    }
800		    else {
801			// Right side of pie
802			$this->guideline->Stroke($img,$xt,$yt,$xt+$this->guidelinemargin,$yt);
803			$lbladj = $this->guidelinemargin+5;
804			$this->value->halign = "left";
805			$this->value->valign = "center";
806		    }
807		    $this->value->Stroke($img,$label,$xt+$lbladj,$yt);
808		}
809
810		// Udate idx to point to next slice in the cluster to process
811		$idx += $step;
812	    }
813	}
814    }
815
816    function StrokeAllLabels($img,$xc,$yc,$radius) {
817	// First normalize all angles for labels
818	$n = count($this->la);
819	for($i=0; $i < $n; ++$i) {
820	    $this->la[$i] = $this->NormAngle($this->la[$i]);
821	}
822	if( $this->guideline->iShow ) {
823	    $this->StrokeGuideLabels($img,$xc,$yc,$radius);
824	}
825	else {
826	    $n = count($this->labels);
827	    for($i=0; $i < $n; ++$i) {
828		$this->StrokeLabel($this->labels[$i],$img,$xc,$yc,
829				   $this->la[$i],
830				   $radius + $this->explode_radius[$n-1-$i]);
831	    }
832	}
833    }
834
835    // Position the labels of each slice
836    function StrokeLabel($label,$img,$xc,$yc,$a,$radius) {
837
838	// Default value
839	if( $this->ilabelposadj === 'auto' )
840	    $this->ilabelposadj = 0.65;
841	$r = $radius;
842
843	// We position the values diferently depending on if they are inside
844	// or outside the pie
845	if( $this->ilabelposadj < 1.0 ) {
846
847	    $this->value->SetAlign('center','center');
848	    $this->value->margin = 0;
849
850	    $xt=round($this->ilabelposadj*$r*cos($a)+$xc);
851	    $yt=round($yc-$this->ilabelposadj*$r*sin($a));
852
853	    $this->value->Stroke($img,$label,$xt,$yt);
854	}
855	else {
856
857	    $this->value->halign = "left";
858	    $this->value->valign = "top";
859	    $this->value->margin = 0;
860
861	    // Position the axis title.
862	    // dx, dy is the offset from the top left corner of the bounding box that sorrounds the text
863	    // that intersects with the extension of the corresponding axis. The code looks a little
864	    // bit messy but this is really the only way of having a reasonable position of the
865	    // axis titles.
866	    $img->SetFont($this->value->ff,$this->value->fs,$this->value->fsize);
867	    $h=$img->GetTextHeight($label);
868	    // For numeric values the format of the display value
869	    // must be taken into account
870	    if( is_numeric($label) ) {
871		if( $label > 0 )
872		    $w=$img->GetTextWidth(sprintf($this->value->format,$label));
873		else
874		    $w=$img->GetTextWidth(sprintf($this->value->negformat,$label));
875	    }
876	    else
877		$w=$img->GetTextWidth($label);
878	    if( $this->ilabelposadj > 1.0 && $this->ilabelposadj < 5.0) {
879		$r *= $this->ilabelposadj;
880	    }
881
882	    $r += $img->GetFontHeight()/1.5;
883
884	    $xt=round($r*cos($a)+$xc);
885	    $yt=round($yc-$r*sin($a));
886
887	    // Normalize angle
888	    while( $a < 0 ) $a += 2*M_PI;
889	    while( $a > 2*M_PI ) $a -= 2*M_PI;
890
891	    if( $a>=7*M_PI/4 || $a <= M_PI/4 ) $dx=0;
892	    if( $a>=M_PI/4 && $a <= 3*M_PI/4 ) $dx=($a-M_PI/4)*2/M_PI;
893	    if( $a>=3*M_PI/4 && $a <= 5*M_PI/4 ) $dx=1;
894	    if( $a>=5*M_PI/4 && $a <= 7*M_PI/4 ) $dx=(1-($a-M_PI*5/4)*2/M_PI);
895
896	    if( $a>=7*M_PI/4 ) $dy=(($a-M_PI)-3*M_PI/4)*2/M_PI;
897	    if( $a<=M_PI/4 ) $dy=(1-$a*2/M_PI);
898	    if( $a>=M_PI/4 && $a <= 3*M_PI/4 ) $dy=1;
899	    if( $a>=3*M_PI/4 && $a <= 5*M_PI/4 ) $dy=(1-($a-3*M_PI/4)*2/M_PI);
900	    if( $a>=5*M_PI/4 && $a <= 7*M_PI/4 ) $dy=0;
901
902	    $this->value->Stroke($img,$label,$xt-$dx*$w,$yt-$dy*$h);
903	}
904    }
905} // Class
906
907
908//===================================================
909// CLASS PiePlotC
910// Description: Same as a normal pie plot but with a
911// filled circle in the center
912//===================================================
913class PiePlotC extends PiePlot {
914    var $imidsize=0.5;		// Fraction of total width
915    var $imidcolor='white';
916    var $midtitle='';
917    var $middlecsimtarget="",$middlecsimalt="";
918
919    function PiePlotC($data,$aCenterTitle='') {
920	parent::PiePlot($data);
921	$this->midtitle = new Text();
922	$this->midtitle->ParagraphAlign('center');
923    }
924
925    function SetMid($aTitle,$aColor='white',$aSize=0.5) {
926	$this->midtitle->Set($aTitle);
927	$this->imidsize = $aSize ;
928	$this->imidcolor = $aColor ;
929    }
930
931    function SetMidTitle($aTitle) {
932	$this->midtitle->Set($aTitle);
933    }
934
935    function SetMidSize($aSize) {
936	$this->imidsize = $aSize ;
937    }
938
939    function SetMidColor($aColor) {
940	$this->imidcolor = $aColor ;
941    }
942
943    function SetMidCSIM($aTarget,$aAlt) {
944	$this->middlecsimtarget = $aTarget;
945	$this->middlecsimalt = $aAlt;
946    }
947
948    function AddSliceToCSIM($i,$xc,$yc,$radius,$sa,$ea) {
949        //Slice number, ellipse centre (x,y), radius, start angle, end angle
950	while( $sa > 2*M_PI ) $sa = $sa - 2*M_PI;
951	while( $ea > 2*M_PI ) $ea = $ea - 2*M_PI;
952
953	$sa = 2*M_PI - $sa;
954	$ea = 2*M_PI - $ea;
955
956	// Add inner circle first point
957	$xp = floor(($this->imidsize*$radius*cos($ea))+$xc);
958	$yp = floor($yc-($this->imidsize*$radius*sin($ea)));
959	$coords = "$xp, $yp";
960
961	//add coordinates every 0.25 radians
962	$a=$ea+0.25;
963
964	// If we cross the 260-limit with a slice we need to handle
965	// the fact that end angle is smaller than start
966	if( $sa < $ea ) {
967	    while ($a <= 2*M_PI) {
968		$xp = floor($radius*cos($a)+$xc);
969		$yp = floor($yc-$radius*sin($a));
970		$coords.= ", $xp, $yp";
971		$a += 0.25;
972	    }
973	    $a -= 2*M_PI;
974	}
975
976	while ($a < $sa) {
977	    $xp = floor(($this->imidsize*$radius*cos($a)+$xc));
978	    $yp = floor($yc-($this->imidsize*$radius*sin($a)));
979	    $coords.= ", $xp, $yp";
980	    $a += 0.25;
981	}
982
983	// Make sure we end at the last point
984	$xp = floor(($this->imidsize*$radius*cos($sa)+$xc));
985	$yp = floor($yc-($this->imidsize*$radius*sin($sa)));
986	$coords.= ", $xp, $yp";
987
988	// Straight line to outer circle
989	$xp = floor($radius*cos($sa)+$xc);
990	$yp = floor($yc-$radius*sin($sa));
991	$coords.= ", $xp, $yp";
992
993	//add coordinates every 0.25 radians
994	$a=$sa - 0.25;
995	while ($a > $ea) {
996	    $xp = floor($radius*cos($a)+$xc);
997	    $yp = floor($yc-$radius*sin($a));
998	    $coords.= ", $xp, $yp";
999	    $a -= 0.25;
1000	}
1001
1002	//Add the last point on the arc
1003	$xp = floor($radius*cos($ea)+$xc);
1004	$yp = floor($yc-$radius*sin($ea));
1005	$coords.= ", $xp, $yp";
1006
1007	// Close the arc
1008	$xp = floor(($this->imidsize*$radius*cos($ea))+$xc);
1009	$yp = floor($yc-($this->imidsize*$radius*sin($ea)));
1010	$coords .= ", $xp, $yp";
1011
1012	if( !empty($this->csimtargets[$i]) ) {
1013	    $this->csimareas .= "<area shape=\"poly\" coords=\"$coords\" href=\"".
1014		$this->csimtargets[$i]."\"";
1015	    if( !empty($this->csimalts[$i]) ) {
1016		$tmp=sprintf($this->csimalts[$i],$this->data[$i]);
1017		$this->csimareas .= " alt=\"$tmp\" title=\"$tmp\"";
1018	    }
1019	    $this->csimareas .= ">\n";
1020	}
1021    }
1022
1023
1024    function Stroke($img,$aaoption=0) {
1025
1026	// Stroke the pie but don't stroke values
1027	$tmp =  $this->value->show;
1028	$this->value->show = false;
1029	parent::Stroke($img,$aaoption);
1030	$this->value->show = $tmp;
1031
1032 	$xc = round($this->posx*$img->width);
1033	$yc = round($this->posy*$img->height);
1034
1035	$radius = floor($this->radius * min($img->width,$img->height)) ;
1036
1037
1038	if( $this->imidsize > 0 && $aaoption !== 2 ) {
1039
1040	    if( $this->ishadowcolor != "" ) {
1041		$img->SetColor($this->ishadowcolor);
1042		$img->FilledCircle($xc+$this->ishadowdrop,$yc+$this->ishadowdrop,
1043				   round($radius*$this->imidsize));
1044	    }
1045
1046	    $img->SetColor($this->imidcolor);
1047	    $img->FilledCircle($xc,$yc,round($radius*$this->imidsize));
1048
1049	    if(  $this->pie_border && $aaoption === 0 ) {
1050		$img->SetColor($this->color);
1051		$img->Circle($xc,$yc,round($radius*$this->imidsize));
1052	    }
1053
1054	    if( !empty($this->middlecsimtarget) )
1055		$this->AddMiddleCSIM($xc,$yc,round($radius*$this->imidsize));
1056
1057	}
1058
1059	if( $this->value->show && $aaoption !== 1) {
1060	    $this->StrokeAllLabels($img,$xc,$yc,$radius);
1061	    $this->midtitle->Pos($xc,$yc,'center','center');
1062	    $this->midtitle->Stroke($img);
1063	}
1064
1065    }
1066
1067    function AddMiddleCSIM($xc,$yc,$r) {
1068	$this->csimareas .= "<area shape=\"circle\" coords=\"$xc,$yc,$r\" href=\"".
1069	    $this->middlecsimtarget."\"";
1070	if( !empty($this->middlecsimalt) ) {
1071	    $tmp = $this->middlecsimalt;
1072	    $this->csimareas .= " alt=\"$tmp\" title=\"$tmp\"";
1073	}
1074	$this->csimareas .= ">\n";
1075    }
1076
1077    function StrokeLabel($label,$img,$xc,$yc,$a,$r) {
1078
1079	if( $this->ilabelposadj === 'auto' )
1080	    $this->ilabelposadj = (1-$this->imidsize)/2+$this->imidsize;
1081
1082	parent::StrokeLabel($label,$img,$xc,$yc,$a,$r);
1083
1084    }
1085
1086}
1087
1088
1089//===================================================
1090// CLASS PieGraph
1091// Description:
1092//===================================================
1093class PieGraph extends Graph {
1094    var $posx, $posy, $radius;
1095    var $legends=array();
1096    var $plots=array();
1097    var $pieaa = false ;
1098//---------------
1099// CONSTRUCTOR
1100    function PieGraph($width=300,$height=200,$cachedName="",$timeout=0,$inline=1) {
1101	$this->Graph($width,$height,$cachedName,$timeout,$inline);
1102	$this->posx=$width/2;
1103	$this->posy=$height/2;
1104	$this->SetColor(array(255,255,255));
1105    }
1106
1107//---------------
1108// PUBLIC METHODS
1109    function Add($aObj) {
1110
1111	if( is_array($aObj) && count($aObj) > 0 )
1112	    $cl = get_class($aObj[0]);
1113	else
1114	    $cl = get_class($aObj);
1115
1116	if( $cl == 'text' )
1117	    $this->AddText($aObj);
1118	elseif( $cl == 'iconplot' )
1119	    $this->AddIcon($aObj);
1120	else {
1121	    if( is_array($aObj) ) {
1122		$n = count($aObj);
1123		for($i=0; $i < $n; ++$i ) {
1124		    $this->plots[] = $aObj[$i];
1125		}
1126	    }
1127	    else {
1128		$this->plots[] = $aObj;
1129	    }
1130	}
1131    }
1132
1133    function SetAntiAliasing($aFlg=true) {
1134	$this->pieaa = $aFlg;
1135    }
1136
1137    function SetColor($c) {
1138	$this->SetMarginColor($c);
1139    }
1140
1141
1142    function DisplayCSIMAreas() {
1143	    $csim="";
1144	    foreach($this->plots as $p ) {
1145		$csim .= $p->GetCSIMareas();
1146	    }
1147	    //$csim.= $this->legend->GetCSIMareas();
1148	    if (preg_match_all("/area shape=\"(\w+)\" coords=\"([0-9\, ]+)\"/", $csim, $coords)) {
1149		$this->img->SetColor($this->csimcolor);
1150		for ($i=0; $i<count($coords[0]); $i++) {
1151		    if ($coords[1][$i]=="poly") {
1152			preg_match_all('/\s*([0-9]+)\s*,\s*([0-9]+)\s*,*/',$coords[2][$i],$pts);
1153			$this->img->SetStartPoint($pts[1][count($pts[0])-1],$pts[2][count($pts[0])-1]);
1154			for ($j=0; $j<count($pts[0]); $j++) {
1155			    $this->img->LineTo($pts[1][$j],$pts[2][$j]);
1156			}
1157		    } else if ($coords[1][$i]=="rect") {
1158			$pts = preg_split('/,/', $coords[2][$i]);
1159			$this->img->SetStartPoint($pts[0],$pts[1]);
1160			$this->img->LineTo($pts[2],$pts[1]);
1161			$this->img->LineTo($pts[2],$pts[3]);
1162			$this->img->LineTo($pts[0],$pts[3]);
1163			$this->img->LineTo($pts[0],$pts[1]);
1164
1165		    }
1166		}
1167	    }
1168    }
1169
1170    // Method description
1171    function Stroke($aStrokeFileName="") {
1172	// If the filename is the predefined value = '_csim_special_'
1173	// we assume that the call to stroke only needs to do enough
1174	// to correctly generate the CSIM maps.
1175	// We use this variable to skip things we don't strictly need
1176	// to do to generate the image map to improve performance
1177	// a best we can. Therefor you will see a lot of tests !$_csim in the
1178	// code below.
1179	$_csim = ($aStrokeFileName===_CSIM_SPECIALFILE);
1180
1181	// We need to know if we have stroked the plot in the
1182	// GetCSIMareas. Otherwise the CSIM hasn't been generated
1183	// and in the case of GetCSIM called before stroke to generate
1184	// CSIM without storing an image to disk GetCSIM must call Stroke.
1185	$this->iHasStroked = true;
1186
1187
1188
1189	$n = count($this->plots);
1190
1191	if( $this->pieaa ) {
1192
1193	    if( !$_csim ) {
1194		if( $this->background_image != "" ) {
1195		    $this->StrokeFrameBackground();
1196		}
1197		else {
1198		    $this->StrokeFrame();
1199		}
1200	    }
1201
1202
1203	    $w = $this->img->width;
1204	    $h = $this->img->height;
1205	    $oldimg = $this->img->img;
1206
1207	    $this->img->CreateImgCanvas(2*$w,2*$h);
1208
1209	    $this->img->SetColor( $this->margin_color );
1210	    $this->img->FilledRectangle(0,0,2*$w-1,2*$h-1);
1211
1212	    // Make all icons *2 i size since we will be scaling down the
1213	    // imahe to do the anti aliasing
1214	    $ni = count($this->iIcons);
1215	    for($i=0; $i < $ni; ++$i) {
1216		$this->iIcons[$i]->iScale *= 2 ;
1217	    }
1218	    $this->StrokeIcons();
1219
1220	    for($i=0; $i < $n; ++$i) {
1221		if( $this->plots[$i]->posx > 1 )
1222		    $this->plots[$i]->posx *= 2 ;
1223		if( $this->plots[$i]->posy > 1 )
1224		    $this->plots[$i]->posy *= 2 ;
1225
1226		$this->plots[$i]->Stroke($this->img,1);
1227
1228		if( $this->plots[$i]->posx > 1 )
1229		    $this->plots[$i]->posx /= 2 ;
1230		if( $this->plots[$i]->posy > 1 )
1231		    $this->plots[$i]->posy /= 2 ;
1232	    }
1233
1234	    $indent = $this->doframe ? ($this->frame_weight + ($this->doshadow ? $this->shadow_width : 0 )) : 0 ;
1235	    $indent += $this->framebevel ? $this->framebeveldepth + 1 : 0 ;
1236	    $this->img->CopyCanvasH($oldimg,$this->img->img,$indent,$indent,$indent,$indent,
1237				    $w-2*$indent,$h-2*$indent,2*($w-$indent),2*($h-$indent));
1238
1239	    $this->img->img = $oldimg ;
1240	    $this->img->width = $w ;
1241	    $this->img->height = $h ;
1242
1243	    for($i=0; $i < $n; ++$i) {
1244		$this->plots[$i]->Stroke($this->img,2); // Stroke labels
1245		$this->plots[$i]->Legend($this);
1246	    }
1247
1248	}
1249	else {
1250
1251	    if( !$_csim ) {
1252		if( $this->background_image != "" ) {
1253		    $this->StrokeFrameBackground();
1254		}
1255		else {
1256		    $this->StrokeFrame();
1257		}
1258	    }
1259
1260	    $this->StrokeIcons();
1261
1262	    for($i=0; $i < $n; ++$i) {
1263		$this->plots[$i]->Stroke($this->img);
1264		$this->plots[$i]->Legend($this);
1265	    }
1266	}
1267
1268
1269	$this->legend->Stroke($this->img);
1270	$this->footer->Stroke($this->img);
1271
1272	if( !$_csim ) {
1273	    $this->StrokeTitles();
1274
1275	    // Stroke texts
1276	    if( $this->texts != null ) {
1277		$n = count($this->texts);
1278		for($i=0; $i < $n; ++$i ) {
1279		    $this->texts[$i]->Stroke($this->img);
1280		}
1281	    }
1282
1283	    if( _JPG_DEBUG ) {
1284		$this->DisplayCSIMAreas();
1285	    }
1286
1287	    // Should we do any final image transformation
1288	    if( $this->iImgTrans ) {
1289		if( !class_exists('ImgTrans') ) {
1290		    require_once('jpgraph_imgtrans.php');
1291		    //JpGraphError::Raise('In order to use image transformation you must include the file jpgraph_imgtrans.php in your script.');
1292		}
1293
1294		$tform = new ImgTrans($this->img->img);
1295		$this->img->img = $tform->Skew3D($this->iImgTransHorizon,$this->iImgTransSkewDist,
1296						 $this->iImgTransDirection,$this->iImgTransHighQ,
1297						 $this->iImgTransMinSize,$this->iImgTransFillColor,
1298						 $this->iImgTransBorder);
1299	    }
1300
1301
1302	    // If the filename is given as the special "__handle"
1303	    // then the image handler is returned and the image is NOT
1304	    // streamed back
1305	    if( $aStrokeFileName == _IMG_HANDLER ) {
1306		return $this->img->img;
1307	    }
1308	    else {
1309		// Finally stream the generated picture
1310		$this->cache->PutAndStream($this->img,$this->cache_name,$this->inline,
1311					   $aStrokeFileName);
1312	    }
1313	}
1314    }
1315} // Class
1316
1317/* EOF */
1318?>
1319