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

Last change on this file since 42 was 42, checked in by marrucho, 10 years ago
File size: 53.8 KB
Line 
1<?php
2/*=======================================================================
3 // File:        JPGRAPH_WINDROSE.PHP
4 // Description: Windrose extension for JpGraph
5 // Created:     2003-09-17
6 // Ver:         $Id: jpgraph_windrose.php 1928 2010-01-11 19:56:51Z ljp $
7 //
8 // Copyright (c) Asial Corporation. All rights reserved.
9 //========================================================================
10 */
11
12require_once('jpgraph_glayout_vh.inc.php');
13
14//------------------------------------------------------------------------
15// Determine how many compass directions to show
16//------------------------------------------------------------------------
17define('WINDROSE_TYPE4',1);
18define('WINDROSE_TYPE8',2);
19define('WINDROSE_TYPE16',3);
20define('WINDROSE_TYPEFREE',4);
21
22//------------------------------------------------------------------------
23// How should the labels for the circular grids be aligned
24//------------------------------------------------------------------------
25define('LBLALIGN_CENTER',1);
26define('LBLALIGN_TOP',2);
27
28//------------------------------------------------------------------------
29// How should the labels around the plot be align
30//------------------------------------------------------------------------
31define('LBLPOSITION_CENTER',1);
32define('LBLPOSITION_EDGE',2);
33
34//------------------------------------------------------------------------
35// Interpretation of ordinal values in the data
36//------------------------------------------------------------------------
37define('KEYENCODING_CLOCKWISE',1);
38define('KEYENCODING_ANTICLOCKWISE',2);
39
40// Internal debug flag
41define('__DEBUG',false);
42
43
44//===================================================
45// CLASS WindrosePlotScale
46//===================================================
47class WindrosePlotScale {
48    private $iMax,$iDelta=5;
49    private $iNumCirc=3;
50    public $iMaxNum=0;
51    private $iLblFmt='%.0f%%';
52    public $iFontFamily=FF_VERDANA,$iFontStyle=FS_NORMAL,$iFontSize=10;
53    public $iZFontFamily=FF_ARIAL,$iZFontStyle=FS_NORMAL,$iZFontSize=10;
54    public $iFontColor='black',$iZFontColor='black';
55    private $iFontFrameColor=false, $iFontBkgColor=false;
56    private $iLblZeroTxt=null;
57    private $iLblAlign=LBLALIGN_CENTER;
58    public $iAngle='auto';
59    private $iManualScale = false;
60    private $iHideLabels = false;
61
62    function __construct($aData) {
63        $max=0;
64        $totlegsum = 0;
65        $maxnum=0;
66        $this->iZeroSum=0;
67        foreach( $aData as $idx => $legdata ) {
68            $legsum = array_sum($legdata);
69            $maxnum = max($maxnum,count($legdata)-1);
70            $max = max($legsum-$legdata[0],$max);
71            $totlegsum += $legsum;
72            $this->iZeroSum += $legdata[0] ;
73        }
74        if( round($totlegsum) > 100 ) {
75            JpGraphError::RaiseL(22001,$legsum);
76            //("Total percentage for all windrose legs in a windrose plot can not exceed  100% !\n(Current max is: ".$legsum.')');
77        }
78        $this->iMax = $max ;
79        $this->iMaxNum = $maxnum;
80        $this->iNumCirc = $this->GetNumCirc();
81        $this->iMaxVal = $this->iNumCirc * $this->iDelta ;
82    }
83
84    // Return number of grid circles
85    function GetNumCirc() {
86        // Never return less than 1 circles
87        $num = ceil($this->iMax / $this->iDelta);
88        return max(1,$num) ;
89    }
90
91    function SetMaxValue($aMax) {
92        $this->iMax = $aMax;
93        $this->iNumCirc = $this->GetNumCirc();
94        $this->iMaxVal = $this->iNumCirc * $this->iDelta ;
95    }
96
97    // Set step size for circular grid
98    function Set($aMax,$aDelta=null) {
99        if( $aDelta==null ) {
100            $this->SetMaxValue($aMax);
101            return;
102        }
103        $this->iDelta = $aDelta;
104        $this->iNumCirc = ceil($aMax/$aDelta); //$this->GetNumCirc();
105        $this->iMaxVal = $this->iNumCirc * $this->iDelta ;
106        $this->iMax=$aMax;
107        // Remember that user has specified interval so don't
108        // do autoscaling
109        $this->iManualScale = true;
110    }
111
112    function AutoScale($aRadius,$aMinDist=30) {
113
114        if( $this->iManualScale ) return;
115
116        // Make sure distance (in pixels) between two circles
117        // is never less than $aMinDist pixels
118        $tst = ceil($aRadius / $this->iNumCirc) ;
119
120        while( $tst <= $aMinDist && $this->iDelta < 100 ) {
121            $this->iDelta += 5;
122            $tst = ceil($aRadius / $this->GetNumCirc()) ;
123        }
124
125        if( $this->iDelta >= 100 ) {
126            JpGraphError::RaiseL(22002);//('Graph is too small to have a scale. Please make the graph larger.');
127        }
128
129        // If the distance is to large try with multiples of 2 instead
130        if( $tst > $aMinDist * 3 ) {
131            $this->iDelta = 2;
132            $tst = ceil($aRadius / $this->iNumCirc) ;
133
134            while( $tst <= $aMinDist && $this->iDelta < 100 ) {
135                $this->iDelta += 2;
136                $tst = ceil($aRadius / $this->GetNumCirc()) ;
137            }
138
139            if( $this->iDelta >= 100 ) {
140                JpGraphError::RaiseL(22002); //('Graph is too small to have a scale. Please make the graph larger.');
141            }
142        }
143
144        $this->iNumCirc = $this->GetNumCirc();
145        $this->iMaxVal = $this->iNumCirc * $this->iDelta ;
146    }
147
148    // Return max of all leg values
149    function GetMax() {
150        return $this->iMax;
151    }
152
153    function Hide($aFlg=true) {
154        $this->iHideLabels = $aFlg;
155    }
156
157    function SetAngle($aAngle) {
158        $this->iAngle = $aAngle ;
159    }
160
161    // Translate a Leg value to radius distance
162    function RelTranslate($aVal,$r,$ri) {
163        $tv = round($aVal/$this->iMaxVal*($r-$ri));
164        return $tv ;
165    }
166
167    function SetLabelAlign($aAlign) {
168        $this->iLblAlign = $aAlign ;
169    }
170
171    function SetLabelFormat($aFmt) {
172        $this->iLblFmt = $aFmt ;
173    }
174
175    function SetLabelFillColor($aBkgColor,$aBorderColor=false) {
176
177        $this->iFontBkgColor = $aBkgColor;
178        if( $aBorderColor === false ) {
179            $this->iFontFrameColor = $aBkgColor;
180        }
181        else {
182            $this->iFontFrameColor = $aBorderColor;
183        }
184    }
185
186    function SetFontColor($aColor) {
187        $this->iFontColor = $aColor ;
188        $this->iZFontColor = $aColor ;
189    }
190
191    function SetFont($aFontFamily,$aFontStyle=FS_NORMAL,$aFontSize=10) {
192        $this->iFontFamily = $aFontFamily ;
193        $this->iFontStyle = $aFontStyle ;
194        $this->iFontSize = $aFontSize ;
195        $this->SetZFont($aFontFamily,$aFontStyle,$aFontSize);
196    }
197
198    function SetZFont($aFontFamily,$aFontStyle=FS_NORMAL,$aFontSize=10) {
199        $this->iZFontFamily = $aFontFamily ;
200        $this->iZFontStyle = $aFontStyle ;
201        $this->iZFontSize = $aFontSize ;
202    }
203
204    function SetZeroLabel($aTxt) {
205        $this->iLblZeroTxt = $aTxt ;
206    }
207
208    function SetZFontColor($aColor) {
209        $this->iZFontColor = $aColor ;
210    }
211
212    function StrokeLabels($aImg,$xc,$yc,$ri,$rr) {
213
214        if( $this->iHideLabels ) return;
215
216        // Setup some convinient vairables
217        $a = $this->iAngle * M_PI/180.0;
218        $n = $this->iNumCirc;
219        $d = $this->iDelta;
220
221        // Setup the font and font color
222        $val = new Text();
223        $val->SetFont($this->iFontFamily,$this->iFontStyle,$this->iFontSize);
224        $val->SetColor($this->iFontColor);
225
226        if( $this->iFontBkgColor !== false ) {
227            $val->SetBox($this->iFontBkgColor,$this->iFontFrameColor);
228        }
229
230        // Position the labels relative to the radiant circles
231        if( $this->iLblAlign == LBLALIGN_TOP ) {
232            if( $a > 0 && $a <= M_PI/2 ) {
233                $val->SetAlign('left','bottom');
234            }
235            elseif( $a > M_PI/2 && $a <= M_PI ) {
236                $val->SetAlign('right','bottom');
237            }
238        }
239        elseif( $this->iLblAlign == LBLALIGN_CENTER ) {
240            $val->SetAlign('center','center');
241        }
242
243        // Stroke the labels close to each circle
244        $v = $d ;
245        $si = sin($a);
246        $co = cos($a);
247        for( $i=0; $i < $n; ++$i, $v += $d ) {
248            $r = $ri + ($i+1) * $rr;
249            $x = $xc + $co * $r;
250            $y = $yc - $si * $r;
251            $val->Set(sprintf($this->iLblFmt,$v));
252            $val->Stroke($aImg,$x,$y);
253        }
254
255        // Print the text in the zero circle
256        if( $this->iLblZeroTxt === null ) {
257            $this->iLblZeroTxt = sprintf($this->iLblFmt,$this->iZeroSum);
258        }
259        else {
260            $this->iLblZeroTxt = sprintf($this->iLblZeroTxt,$this->iZeroSum);
261        }
262
263        $val->Set($this->iLblZeroTxt);
264        $val->SetAlign('center','center');
265        $val->SetParagraphAlign('center');
266        $val->SetColor($this->iZFontColor);
267        $val->SetFont($this->iZFontFamily,$this->iZFontStyle,$this->iZFontSize);
268        $val->Stroke($aImg,$xc,$yc);
269    }
270}
271
272//===================================================
273// CLASS LegendStyle
274//===================================================
275class LegendStyle {
276    public $iLength = 40, $iMargin = 20 , $iBottomMargin=5;
277    public $iCircleWeight=2,  $iCircleRadius = 18, $iCircleColor='black';
278    public $iTxtFontFamily=FF_VERDANA,$iTxtFontStyle=FS_NORMAL,$iTxtFontSize=8;
279    public $iLblFontFamily=FF_VERDANA,$iLblFontStyle=FS_NORMAL,$iLblFontSize=8;
280    public $iCircleFontFamily=FF_VERDANA,$iCircleFontStyle=FS_NORMAL,$iCircleFontSize=8;
281    public $iLblFontColor='black',$iTxtFontColor='black',$iCircleFontColor='black';
282    public $iShow=true;
283    public $iFormatString='%.1f';
284    public $iTxtMargin=6, $iTxt='';
285    public $iZCircleTxt='Calm';
286
287    function SetFont($aFontFamily,$aFontStyle=FS_NORMAL,$aFontSize=10) {
288        $this->iLblFontFamily = $aFontFamily ;
289        $this->iLblFontStyle = $aFontStyle ;
290        $this->iLblFontSize = $aFontSize ;
291        $this->iTxtFontFamily = $aFontFamily ;
292        $this->iTxtFontStyle = $aFontStyle ;
293        $this->iTxtFontSize = $aFontSize ;
294        $this->iCircleFontFamily = $aFontFamily ;
295        $this->iCircleFontStyle = $aFontStyle ;
296        $this->iCircleFontSize = $aFontSize ;
297    }
298
299    function SetLFont($aFontFamily,$aFontStyle=FS_NORMAL,$aFontSize=10) {
300        $this->iLblFontFamily = $aFontFamily ;
301        $this->iLblFontStyle = $aFontStyle ;
302        $this->iLblFontSize = $aFontSize ;
303    }
304
305    function SetTFont($aFontFamily,$aFontStyle=FS_NORMAL,$aFontSize=10) {
306        $this->iTxtFontFamily = $aFontFamily ;
307        $this->iTxtFontStyle = $aFontStyle ;
308        $this->iTxtFontSize = $aFontSize ;
309    }
310
311    function SetCFont($aFontFamily,$aFontStyle=FS_NORMAL,$aFontSize=10) {
312        $this->iCircleFontFamily = $aFontFamily ;
313        $this->iCircleFontStyle = $aFontStyle ;
314        $this->iCircleFontSize = $aFontSize ;
315    }
316
317
318    function SetFontColor($aColor) {
319        $this->iTxtFontColor = $aColor ;
320        $this->iLblFontColor = $aColor ;
321        $this->iCircleFontColor = $aColor ;
322    }
323
324    function SetTFontColor($aColor) {
325        $this->iTxtFontColor = $aColor ;
326    }
327
328    function SetLFontColor($aColor) {
329        $this->iLblFontColor = $aColor ;
330    }
331
332    function SetCFontColor($aColor) {
333        $this->iCircleFontColor = $aColor ;
334    }
335
336    function SetCircleWeight($aWeight) {
337        $this->iCircleWeight = $aWeight;
338    }
339
340    function SetCircleRadius($aRadius) {
341        $this->iCircleRadius = $aRadius;
342    }
343
344    function SetCircleColor($aColor) {
345        $this->iCircleColor = $aColor ;
346    }
347
348    function SetCircleText($aTxt) {
349        $this->iZCircleTxt = $aTxt;
350    }
351
352    function SetMargin($aMarg,$aBottomMargin=5) {
353        $this->iMargin=$aMarg;
354        $this->iBottomMargin=$aBottomMargin;
355    }
356
357    function SetLength($aLength) {
358        $this->iLength = $aLength ;
359    }
360
361    function Show($aFlg=true) {
362        $this->iShow = $aFlg;
363    }
364
365    function Hide($aFlg=true) {
366        $this->iShow = ! $aFlg;
367    }
368
369    function SetFormat($aFmt) {
370        $this->iFormatString=$aFmt;
371    }
372
373    function SetText($aTxt) {
374        $this->iTxt = $aTxt ;
375    }
376
377}
378
379define('RANGE_OVERLAPPING',0);
380define('RANGE_DISCRETE',1);
381
382//===================================================
383// CLASS WindrosePlot
384//===================================================
385class WindrosePlot {
386    private $iAntiAlias=true;
387    private $iData=array();
388    public $iX=0.5,$iY=0.5;
389    public $iSize=0.55;
390    private $iGridColor1='gray',$iGridColor2='darkgreen';
391    private $iRadialColorArray=array();
392    private $iRadialWeightArray=array();
393    private $iRadialStyleArray=array();
394    private $iRanges = array(1,2,3,5,6,10,13.5,99.0);
395    private $iRangeStyle = RANGE_OVERLAPPING ;
396    public $iCenterSize=60;
397    private $iType = WINDROSE_TYPE16;
398    public $iFontFamily=FF_VERDANA,$iFontStyle=FS_NORMAL,$iFontSize=10;
399    public $iFontColor='darkgray';
400    private $iRadialGridStyle='longdashed';
401    private $iAllDirectionLabels =  array('E','ENE','NE','NNE','N','NNW','NW','WNW','W','WSW','SW','SSW','S','SSE','SE','ESE');
402    private $iStandardDirections = array();
403    private $iCircGridWeight=3, $iRadialGridWeight=1;
404    private $iLabelMargin=12;
405    private $iLegweights = array(2,4,6,8,10,12,14,16,18,20);
406    private $iLegColors = array('orange','black','blue','red','green','purple','navy','yellow','brown');
407    private $iLabelFormatString='', $iLabels=array();
408    private $iLabelPositioning = LBLPOSITION_EDGE;
409    private $iColor='white';
410    private $iShowBox=false, $iBoxColor='black',$iBoxWeight=1,$iBoxStyle='solid';
411    private $iOrdinalEncoding=KEYENCODING_ANTICLOCKWISE;
412    public $legend=null;
413
414    function __construct($aData) {
415        $this->iData = $aData;
416        $this->legend = new LegendStyle();
417
418        // Setup the scale
419        $this->scale = new WindrosePlotScale($this->iData);
420
421        // default label for free type i agle and a degree sign
422        $this->iLabelFormatString = '%.1f'.SymChar::Get('degree');
423
424        $delta = 2*M_PI/16;
425        for( $i=0, $a=0; $i < 16; ++$i, $a += $delta ) {
426            $this->iStandardDirections[$this->iAllDirectionLabels[$i]] = $a;
427        }
428    }
429
430    // Dummy method to make window plots have the same signature as the
431    // layout classes since windrose plots are "leaf" classes in the hierarchy
432    function LayoutSize() {
433        return 1;
434    }
435
436    function SetSize($aSize) {
437        $this->iSize = $aSize;
438    }
439
440    function SetDataKeyEncoding($aEncoding) {
441        $this->iOrdinalEncoding = $aEncoding;
442    }
443
444    function SetColor($aColor) {
445        $this->iColor = $aColor;
446    }
447
448    function SetRadialColors($aColors) {
449        $this->iRadialColorArray = $aColors;
450    }
451
452    function SetRadialWeights($aWeights) {
453        $this->iRadialWeightArray = $aWeights;
454    }
455
456    function SetRadialStyles($aStyles) {
457        $this->iRadialStyleArray = $aStyles;
458    }
459
460    function SetBox($aColor='black',$aWeight=1, $aStyle='solid', $aShow=true) {
461        $this->iShowBox = $aShow ;
462        $this->iBoxColor = $aColor ;
463        $this->iBoxWeight = $aWeight ;
464        $this->iBoxStyle = $aStyle;
465    }
466
467    function SetLabels($aLabels) {
468        $this->iLabels = $aLabels ;
469    }
470
471    function SetLabelMargin($aMarg) {
472        $this->iLabelMargin = $aMarg ;
473    }
474
475    function SetLabelFormat($aLblFormat) {
476        $this->iLabelFormatString = $aLblFormat ;
477    }
478
479    function SetCompassLabels($aLabels) {
480        if( count($aLabels) != 16 ) {
481            JpgraphError::RaiseL(22004); //('Label specification for windrose directions must have 16 values (one for each compass direction).');
482        }
483        $this->iAllDirectionLabels = $aLabels ;
484
485        $delta = 2*M_PI/16;
486        for( $i=0, $a=0; $i < 16; ++$i, $a += $delta ) {
487            $this->iStandardDirections[$this->iAllDirectionLabels[$i]] = $a;
488        }
489
490    }
491
492    function SetCenterSize($aSize) {
493        $this->iCenterSize = $aSize;
494    }
495    // Alias for SetCenterSize
496    function SetZCircleSize($aSize) {
497        $this->iCenterSize = $aSize;
498    }
499
500    function SetFont($aFFam,$aFStyle=FS_NORMAL,$aFSize=10) {
501        $this->iFontFamily = $aFFam ;
502        $this->iFontStyle = $aFStyle ;
503        $this->iFontSize = $aFSize ;
504    }
505
506    function SetFontColor($aColor) {
507        $this->iFontColor=$aColor;
508    }
509
510    function SetGridColor($aColor1,$aColor2) {
511        $this->iGridColor1 = $aColor1;
512        $this->iGridColor2 = $aColor2;
513    }
514
515    function SetGridWeight($aGrid1=1,$aGrid2=2) {
516        $this->iCircGridWeight = $aGrid1 ;
517        $this->iRadialGridWeight = $aGrid2 ;
518    }
519
520    function SetRadialGridStyle($aStyle) {
521        $aStyle = strtolower($aStyle);
522        if( !in_array($aStyle,array('solid','dotted','dashed','longdashed')) ) {
523            JpGraphError::RaiseL(22005); //("Line style for radial lines must be on of ('solid','dotted','dashed','longdashed') ");
524        }
525        $this->iRadialGridStyle=$aStyle;
526    }
527
528    function SetRanges($aRanges) {
529        $this->iRanges = $aRanges;
530    }
531
532    function SetRangeStyle($aStyle) {
533        $this->iRangeStyle = $aStyle;
534    }
535
536    function SetRangeColors($aLegColors) {
537        $this->iLegColors = $aLegColors;
538    }
539
540    function SetRangeWeights($aWeights) {
541        $n=count($aWeights);
542        for($i=0; $i< $n; ++$i ) {
543            $aWeights[$i] = floor($aWeights[$i]/2);
544        }
545        $this->iLegweights = $aWeights;
546
547    }
548
549    function SetType($aType) {
550        if( $aType < WINDROSE_TYPE4 || $aType > WINDROSE_TYPEFREE ) {
551            JpGraphError::RaiseL(22006); //('Illegal windrose type specified.');
552        }
553        $this->iType = $aType;
554    }
555
556    // Alias for SetPos()
557    function SetCenterPos($aX,$aY) {
558        $this->iX = $aX;
559        $this->iY = $aY;       
560    }
561   
562    function SetPos($aX,$aY) {
563        $this->iX = $aX;
564        $this->iY = $aY;
565    }
566
567    function SetAntiAlias($aFlag) {
568        $this->iAntiAlias = $aFlag ;
569        if( ! $aFlag )
570        $this->iCircGridWeight = 1;
571    }
572
573    function _ThickCircle($aImg,$aXC,$aYC,$aRad,$aWeight=2,$aColor) {
574
575        $aImg->SetColor($aColor);
576        $aRad *= 2 ;
577        $aImg->Ellipse($aXC,$aYC,$aRad,$aRad);
578        if( $aWeight > 1 ) {
579            $aImg->Ellipse($aXC,$aYC,$aRad+1,$aRad+1);
580            $aImg->Ellipse($aXC,$aYC,$aRad+2,$aRad+2);
581            if( $aWeight > 2 ) {
582                $aImg->Ellipse($aXC,$aYC,$aRad+3,$aRad+3);
583                $aImg->Ellipse($aXC,$aYC,$aRad+3,$aRad+4);
584                $aImg->Ellipse($aXC,$aYC,$aRad+4,$aRad+3);
585            }
586        }
587    }
588
589    function _StrokeWindLeg($aImg,$xc,$yc,$a,$ri,$r,$weight,$color) {
590
591        // If less than 1 px long then we assume this has been caused by rounding problems
592        // and should not be stroked
593        if( $r < 1 ) return;
594
595        $xt = $xc + cos($a)*$ri;
596        $yt = $yc - sin($a)*$ri;
597        $xxt = $xc + cos($a)*($ri+$r);
598        $yyt = $yc - sin($a)*($ri+$r);
599
600        $x1 = $xt - $weight*sin($a);
601        $y1 = $yt - $weight*cos($a);
602        $x2 = $xxt - $weight*sin($a);
603        $y2 = $yyt - $weight*cos($a);
604
605        $x3 = $xxt + $weight*sin($a);
606        $y3 = $yyt + $weight*cos($a);
607        $x4 = $xt + $weight*sin($a);
608        $y4 = $yt + $weight*cos($a);
609
610        $pts = array($x1,$y1,$x2,$y2,$x3,$y3,$x4,$y4);
611        $aImg->SetColor($color);
612        $aImg->FilledPolygon($pts);
613
614    }
615
616    function _StrokeLegend($aImg,$x,$y,$scaling=1,$aReturnWidth=false) {
617
618        if( ! $this->legend->iShow ) return 0;
619
620        $nlc = count($this->iLegColors);
621        $nlw = count($this->iLegweights);
622
623        // Setup font for ranges
624        $value = new Text();
625        $value->SetAlign('center','bottom');
626        $value->SetFont($this->legend->iLblFontFamily,
627        $this->legend->iLblFontStyle,
628        $this->legend->iLblFontSize*$scaling);
629        $value->SetColor($this->legend->iLblFontColor);
630
631        // Remember x-center
632        $xcenter = $x ;
633
634        // Construct format string
635        $fmt = $this->legend->iFormatString.'-'.$this->legend->iFormatString;
636
637        // Make sure that the length of each range is enough to cover the
638        // size of the labels
639        $tst = sprintf($fmt,$this->iRanges[0],$this->iRanges[1]);
640        $value->Set($tst);
641        $w = $value->GetWidth($aImg);
642        $l = round(max($this->legend->iLength * $scaling,$w*1.5));
643
644        $r = $this->legend->iCircleRadius * $scaling ;
645        $len = 2*$r + $this->scale->iMaxNum * $l;
646
647        // We are called just to find out the width
648        if( $aReturnWidth ) return $len;
649
650        $x -= round($len/2);
651        $x += $r;
652
653        // 4 pixels extra vertical margin since the circle sometimes is +/- 1 pixel of the
654        // theorethical radius due to imperfection in the GD library
655        //$y -= round(max($r,$scaling*$this->iLegweights[($this->scale->iMaxNum-1) % $nlw])+4*$scaling);
656        $y -= ($this->legend->iCircleRadius + 2)*$scaling+$this->legend->iBottomMargin*$scaling;
657
658        // Adjust for bottom text
659        if( $this->legend->iTxt != '' ) {
660            // Setup font for text
661            $value->Set($this->legend->iTxt);
662            $y -= /*$this->legend->iTxtMargin + */ $value->GetHeight($aImg);
663        }
664
665        // Stroke 0-circle
666        $this->_ThickCircle($aImg,$x,$y,$r,$this->legend->iCircleWeight,
667        $this->legend->iCircleColor);
668
669        // Remember the center of the circe
670        $xc=$x; $yc=$y;
671
672        $value->SetAlign('center','bottom');
673        $x += $r+1;
674
675        // Stroke all used ranges
676        $txty = $y -
677        round($this->iLegweights[($this->scale->iMaxNum-1)%$nlw]*$scaling) - 4*$scaling;
678        if( $this->scale->iMaxNum >= count($this->iRanges) ) {
679            JpGraphError::RaiseL(22007); //('To few values for the range legend.');
680        }
681        $i=0;$idx=0;
682        while( $i < $this->scale->iMaxNum ) {
683            $y1 = $y - round($this->iLegweights[$i % $nlw]*$scaling);
684            $y2 = $y + round($this->iLegweights[$i % $nlw]*$scaling);
685            $x2 = $x + $l ;
686            $aImg->SetColor($this->iLegColors[$i % $nlc]);
687            $aImg->FilledRectangle($x,$y1,$x2,$y2);
688            if( $this->iRangeStyle == RANGE_OVERLAPPING ) {
689                $lbl = sprintf($fmt,$this->iRanges[$idx],$this->iRanges[$idx+1]);
690            }
691            else {
692                $lbl = sprintf($fmt,$this->iRanges[$idx],$this->iRanges[$idx+1]);
693                ++$idx;
694            }
695            $value->Set($lbl);
696            $value->Stroke($aImg,$x+$l/2,$txty);
697            $x = $x2;
698            ++$i;++$idx;
699        }
700
701        // Setup circle font
702        $value->SetFont($this->legend->iCircleFontFamily,
703        $this->legend->iCircleFontStyle,
704        $this->legend->iCircleFontSize*$scaling);
705        $value->SetColor($this->legend->iCircleFontColor);
706
707        // Stroke 0-circle text
708        $value->Set($this->legend->iZCircleTxt);
709        $value->SetAlign('center','center');
710        $value->ParagraphAlign('center');
711        $value->Stroke($aImg,$xc,$yc);
712
713        // Setup circle font
714        $value->SetFont($this->legend->iTxtFontFamily,
715        $this->legend->iTxtFontStyle,
716        $this->legend->iTxtFontSize*$scaling);
717        $value->SetColor($this->legend->iTxtFontColor);
718
719        // Draw the text under the legend
720        $value->Set($this->legend->iTxt);
721        $value->SetAlign('center','top');
722        $value->SetParagraphAlign('center');
723        $value->Stroke($aImg,$xcenter,$y2+$this->legend->iTxtMargin*$scaling);
724    }
725
726    function SetAutoScaleAngle($aIsRegRose=true) {
727
728        // If the user already has manually set an angle don't
729        // trye to find a position
730        if( is_numeric($this->scale->iAngle) )
731            return;
732
733        if( $aIsRegRose ) {
734
735            // Create a complete data for all directions
736            // and translate string directions to ordinal values.
737            // This will much simplify the logic below
738            for( $i=0; $i < 16; ++$i ) {
739                $dtxt = $this->iAllDirectionLabels[$i];
740                if( !empty($this->iData[$dtxt]) ) {
741                    $data[$i] = $this->iData[$dtxt];
742                }
743                elseif( !empty($this->iData[strtolower($dtxt)]) ) {
744                    $data[$i] = $this->iData[strtolower($dtxt)];
745                }
746                elseif( !empty($this->iData[$i]) ) {
747                    $data[$i] = $this->iData[$i];
748                }
749                else {
750                    $data[$i] = array();
751                }
752            }
753
754            // Find the leg which has the lowest weighted sum of number of data around it
755            $c0 = array_sum($data[0]);
756            $c1 = array_sum($data[1]);
757            $found = 1;
758            $min = $c0+$c1*100; // Initialize to a high value
759            for( $i=1; $i < 15; ++$i ) {
760                $c2 = array_sum($data[$i+1]);
761
762                // Weight the leg we will use more to give preference
763                // to a short middle leg even if the 3 way sum is similair
764                $w = $c0 + 3*$c1 + $c2 ;
765                if( $w < $min ) {
766                    $min = $w;
767                    $found = $i;
768                }
769                $c0 = $c1;
770                $c1 = $c2;
771            }
772            $this->scale->iAngle = $found*22.5;
773        }
774        else {
775            $n = count($this->iData);
776            foreach( $this->iData as $dir => $leg ) {
777                if( !is_numeric($dir) ) {
778                    $pos = array_search(strtoupper($dir),$this->iAllDirectionLabels);
779                    if( $pos !== false ) {
780                        $dir = $pos*22.5;
781                    }
782                }
783                $data[round($dir)] = $leg;
784            }
785
786            // Get all the angles for the data and sort it
787            $keys = array_keys($data);
788            sort($keys, SORT_NUMERIC);
789
790            $n = count($data);
791            $found = false;
792            $max = 0 ;
793            for( $i=0; $i < 15; ++$i ) {
794                $try_a = round(22.5*$i);
795
796                if( $try_a > $keys[$n-1] ) break;
797
798                if( in_array($try_a,$keys) ) continue;
799
800                // Find the angle just lower than this
801                $j=0;
802                while( $j < $n && $keys[$j] <= $try_a ) ++$j;
803                if( $j == 0 ) {
804                    $kj = 0; $keys[$n-1];
805                    $d1 = 0; abs($kj-$try_a);
806                }
807                else {
808                    --$j;
809                    $kj = $keys[$j];
810                    $d1 = abs($kj-$try_a);
811                }
812
813                // Find the angle just larger than this
814                $l=$n-1;
815                while( $l >= 0 && $keys[$l] >= $try_a ) --$l;
816                if( $l == $n-1) {
817                    $kl = $keys[0];
818                    $d2 = abs($kl-$try_a);
819                }
820                else {
821                    ++$l;
822                    $kl = $keys[$l];
823                    $d2 = abs($kl-$try_a);
824                }
825
826                // Weight the distance so that legs with large spread
827                // gets a better weight
828                $w = $d1 + $d2;
829                if( $i == 0 ) {
830                    $w = round(1.4 * $w);
831                }
832                $diff = abs($d1 - $d2);
833                $w *= (360-$diff);
834                if( $w > $max ) {
835                    $found = $i;
836                    $max = $w;
837                }
838            }
839
840            $a = $found*22.5;
841
842            // Some heuristics to have some preferred positions
843            if( $keys[$n-1] < 25 ) $a = 45;
844            elseif( $keys[0] > 60 ) $a = 45;
845            elseif( $keys[0] > 25 && $keys[$n-1] < 340 ) $a = 0;
846            elseif( $keys[$n-1] < 75 ) $a = 90;
847            elseif( $keys[$n-1] < 120 ) $a = 135;
848            elseif( $keys[$n-1] < 160 ) $a = 180;
849
850            $this->scale->iAngle = $a ;
851        }
852    }
853
854    function NormAngle($a) {
855        while( $a > 360 ) {
856            $a -= 360;
857        }
858        return $a;
859    }
860
861    function SetLabelPosition($aPos) {
862        $this->iLabelPositioning  = $aPos ;
863    }
864
865    function _StrokeFreeRose($dblImg,$value,$scaling,$xc,$yc,$r,$ri) {
866
867        // Plot radial grid lines and remember the end position
868        // and the angle for later use when plotting the labels
869        if( $this->iType != WINDROSE_TYPEFREE ) {
870            JpGraphError::RaiseL(22008); //('Internal error: Trying to plot free Windrose even though type is not a free windorose');
871        }
872
873        // Check if we should auto-position the angle for the
874        // labels. Basically we try to find a firection with smallest
875        // (or none) data.
876        $this->SetAutoScaleAngle(false);
877
878        $nlc = count($this->iLegColors);
879        $nlw = count($this->iLegweights);
880
881        // Stroke grid lines for directions and remember the
882        // position for the labels
883        $txtpos=array();
884        $num = count($this->iData);
885
886        $keys = array_keys($this->iData);
887
888        foreach( $this->iData as $dir => $legdata ) {
889            if( in_array($dir,$this->iAllDirectionLabels,true) === true) {
890                $a = $this->iStandardDirections[strtoupper($dir)];
891                if( in_array($a*180/M_PI,$keys) ) {
892                    JpGraphError::RaiseL(22009,round($a*180/M_PI));
893                    //('You have specified the same direction twice, once with an angle and once with a compass direction ('.$a*180/M_PI.' degrees.)');
894                }
895            }
896            elseif( is_numeric($dir) ) {
897                $this->NormAngle($dir);
898
899                if( $this->iOrdinalEncoding == KEYENCODING_CLOCKWISE ) {
900                    $dir = 360-$dir;
901                }
902
903                $a = $dir * M_PI/180;
904            }
905            else {
906                JpGraphError::RaiseL(22010);//('Direction must either be a numeric value or one of the 16 compass directions');
907            }
908
909            $xxc = round($xc + cos($a)*$ri);
910            $yyc = round($yc - sin($a)*$ri);
911            $x = round($xc + cos($a)*$r);
912            $y = round($yc - sin($a)*$r);
913            if( empty($this->iRadialColorArray[$dir]) ) {
914                $dblImg->SetColor($this->iGridColor2);
915            }
916            else {
917                $dblImg->SetColor($this->iRadialColorArray[$dir]);
918            }
919            if( empty($this->iRadialWeightArray[$dir]) ) {
920                $dblImg->SetLineWeight($this->iRadialGridWeight);
921            }
922            else {
923                $dblImg->SetLineWeight($this->iRadialWeightArray[$dir]);
924            }
925            if( empty($this->iRadialStyleArray[$dir]) ) {
926                $dblImg->SetLineStyle($this->iRadialGridStyle);
927            }
928            else {
929                $dblImg->SetLineStyle($this->iRadialStyleArray[$dir]);
930            }
931            $dblImg->StyleLine($xxc,$yyc,$x,$y);
932            $txtpos[] = array($x,$y,$a);
933        }
934        $dblImg->SetLineWeight(1);
935
936        // Setup labels
937        $lr = $scaling * $this->iLabelMargin;
938
939        if( $this->iLabelPositioning == LBLPOSITION_EDGE ) {
940            $value->SetAlign('left','top');
941        }
942        else {
943            $value->SetAlign('center','center');
944            $value->SetMargin(0);
945        }
946
947        for($i=0; $i < $num; ++$i ) {
948
949            list($x,$y,$a) = $txtpos[$i];
950
951            // Determine the label
952
953            $da = $a*180/M_PI;
954            if( $this->iOrdinalEncoding == KEYENCODING_CLOCKWISE ) {
955                $da = 360 - $da;
956            }
957
958            //$da = 360-$da;
959           
960            if( !empty($this->iLabels[$keys[$i]]) ) {
961                $lbl = $this->iLabels[$keys[$i]];
962            }
963            else {
964                $lbl = sprintf($this->iLabelFormatString,$da);
965            }
966
967            if( $this->iLabelPositioning == LBLPOSITION_CENTER ) {
968                $dx = $dy = 0;
969            }
970            else {
971                // LBLPOSIITON_EDGE
972                if( $a>=7*M_PI/4 || $a <= M_PI/4 ) $dx=0;
973                if( $a>=M_PI/4 && $a <= 3*M_PI/4 ) $dx=($a-M_PI/4)*2/M_PI;
974                if( $a>=3*M_PI/4 && $a <= 5*M_PI/4 ) $dx=1;
975                if( $a>=5*M_PI/4 && $a <= 7*M_PI/4 ) $dx=(1-($a-M_PI*5/4)*2/M_PI);
976
977                if( $a>=7*M_PI/4 ) $dy=(($a-M_PI)-3*M_PI/4)*2/M_PI;
978                if( $a<=M_PI/4 ) $dy=(0.5+$a*2/M_PI);
979                if( $a>=M_PI/4 && $a <= 3*M_PI/4 ) $dy=1;
980                if( $a>=3*M_PI/4 && $a <= 5*M_PI/4 ) $dy=(1-($a-3*M_PI/4)*2/M_PI);
981                if( $a>=5*M_PI/4 && $a <= 7*M_PI/4 ) $dy=0;
982            }
983
984            $value->Set($lbl);
985            $th = $value->GetHeight($dblImg);
986            $tw = $value->GetWidth($dblImg);
987            $xt=round($lr*cos($a)+$x) - $dx*$tw;
988            $yt=round($y-$lr*sin($a)) - $dy*$th;
989
990            $value->Stroke($dblImg,$xt,$yt);
991        }
992
993        if( __DEBUG ) {
994            $dblImg->SetColor('red');
995            $dblImg->Circle($xc,$yc,$lr+$r);
996        }
997
998        // Stroke all the legs
999        reset($this->iData);
1000        $i=0;
1001        foreach($this->iData as $dir => $legdata) {
1002            $legdata = array_slice($legdata,1);
1003            $nn = count($legdata);
1004
1005            $a = $txtpos[$i][2];
1006            $rri = $ri/$scaling;
1007            for( $j=0; $j < $nn; ++$j ) {
1008                // We want the non scaled original radius
1009                $legr = $this->scale->RelTranslate($legdata[$j],$r/$scaling,$ri/$scaling) ;
1010                $this->_StrokeWindLeg($dblImg, $xc, $yc, $a,
1011                $rri *$scaling,
1012                $legr *$scaling,
1013                $this->iLegweights[$j % $nlw] * $scaling,
1014                $this->iLegColors[$j % $nlc]);
1015                $rri += $legr;
1016            }
1017            ++$i;
1018        }
1019    }
1020
1021    // Translate potential string specified compass labels to their
1022    // corresponding index.
1023    function FixupIndexes($aDataArray,$num) {
1024        $ret = array();
1025        $keys = array_keys($aDataArray);
1026        foreach($aDataArray as $idx => $data) {
1027            if( is_string($idx) ) {
1028                $idx = strtoupper($idx);
1029                $res = array_search($idx,$this->iAllDirectionLabels);
1030                if( $res === false ) {
1031                    JpGraphError::RaiseL(22011,$idx); //('Windrose index must be numeric or direction label. You have specified index='.$idx);
1032                }
1033                $idx = $res;
1034                if( $idx % (16 / $num) !== 0 ) {
1035                    JpGraphError::RaiseL(22012); //('Windrose radial axis specification contains a direction which is not enabled.');
1036                }
1037                $idx /= (16/$num) ;
1038
1039                if( in_array($idx,$keys,1) ) {
1040                    JpgraphError::RaiseL(22013,$idx); //('You have specified the look&feel for the same compass direction twice, once with text and once with index (Index='.$idx.')');
1041                }
1042            }
1043            if( $idx < 0 || $idx > 15 ) {
1044                JpgraphError::RaiseL(22014); //('Index for copmass direction must be between 0 and 15.');
1045            }
1046            $ret[$idx] = $data;
1047        }
1048        return $ret;
1049    }
1050
1051    function _StrokeRegularRose($dblImg,$value,$scaling,$xc,$yc,$r,$ri) {
1052        // _StrokeRegularRose($dblImg,$xc,$yc,$r,$ri)
1053        // Plot radial grid lines and remember the end position
1054        // and the angle for later use when plotting the labels
1055        switch( $this->iType ) {
1056            case WINDROSE_TYPE4:
1057                $num = 4; break;
1058            case WINDROSE_TYPE8:
1059                $num = 8; break;
1060            case WINDROSE_TYPE16:
1061                $num = 16; break;
1062            default:
1063                JpGraphError::RaiseL(22015);//('You have specified an undefined Windrose plot type.');
1064        }
1065
1066        // Check if we should auto-position the angle for the
1067        // labels. Basically we try to find a firection with smallest
1068        // (or none) data.
1069        $this->SetAutoScaleAngle(true);
1070
1071        $nlc = count($this->iLegColors);
1072        $nlw = count($this->iLegweights);
1073
1074        $this->iRadialColorArray = $this->FixupIndexes($this->iRadialColorArray,$num);
1075        $this->iRadialWeightArray = $this->FixupIndexes($this->iRadialWeightArray,$num);
1076        $this->iRadialStyleArray = $this->FixupIndexes($this->iRadialStyleArray,$num);
1077
1078        $txtpos=array();
1079        $a = 2*M_PI/$num;
1080        $dblImg->SetColor($this->iGridColor2);
1081        $dblImg->SetLineStyle($this->iRadialGridStyle);
1082        $dblImg->SetLineWeight($this->iRadialGridWeight);
1083
1084        // Translate any name specified directions to the index
1085        // so we can easily use it in the loop below
1086        for($i=0; $i < $num; ++$i ) {
1087            $xxc = round($xc + cos($a*$i)*$ri);
1088            $yyc = round($yc - sin($a*$i)*$ri);
1089            $x = round($xc + cos($a*$i)*$r);
1090            $y = round($yc - sin($a*$i)*$r);
1091            if( empty($this->iRadialColorArray[$i]) ) {
1092                $dblImg->SetColor($this->iGridColor2);
1093            }
1094            else {
1095                $dblImg->SetColor($this->iRadialColorArray[$i]);
1096            }
1097            if( empty($this->iRadialWeightArray[$i]) ) {
1098                $dblImg->SetLineWeight($this->iRadialGridWeight);
1099            }
1100            else {
1101                $dblImg->SetLineWeight($this->iRadialWeightArray[$i]);
1102            }
1103            if( empty($this->iRadialStyleArray[$i]) ) {
1104                $dblImg->SetLineStyle($this->iRadialGridStyle);
1105            }
1106            else {
1107                $dblImg->SetLineStyle($this->iRadialStyleArray[$i]);
1108            }
1109
1110            $dblImg->StyleLine($xxc,$yyc,$x,$y);
1111            $txtpos[] = array($x,$y,$a*$i);
1112        }
1113        $dblImg->SetLineWeight(1);
1114
1115        $lr = $scaling * $this->iLabelMargin;
1116        if( $this->iLabelPositioning == LBLPOSITION_CENTER ) {
1117            $value->SetAlign('center','center');
1118        }
1119        else {
1120            $value->SetAlign('left','top');
1121            $value->SetMargin(0);
1122            $lr /= 2 ;
1123        }
1124
1125        for($i=0; $i < $num; ++$i ) {
1126            list($x,$y,$a) = $txtpos[$i];
1127
1128            // Set the position of the label
1129            if( $this->iLabelPositioning == LBLPOSITION_CENTER ) {
1130                $dx = $dy = 0;
1131            }
1132            else {
1133                // LBLPOSIITON_EDGE
1134                if( $a>=7*M_PI/4 || $a <= M_PI/4 ) $dx=0;
1135                if( $a>=M_PI/4 && $a <= 3*M_PI/4 ) $dx=($a-M_PI/4)*2/M_PI;
1136                if( $a>=3*M_PI/4 && $a <= 5*M_PI/4 ) $dx=1;
1137                if( $a>=5*M_PI/4 && $a <= 7*M_PI/4 ) $dx=(1-($a-M_PI*5/4)*2/M_PI);
1138
1139                if( $a>=7*M_PI/4 ) $dy=(($a-M_PI)-3*M_PI/4)*2/M_PI;
1140                if( $a<=M_PI/4 ) $dy=(0.5+$a*2/M_PI);
1141                if( $a>=M_PI/4 && $a <= 3*M_PI/4 ) $dy=1;
1142                if( $a>=3*M_PI/4 && $a <= 5*M_PI/4 ) $dy=(1-($a-3*M_PI/4)*2/M_PI);
1143                if( $a>=5*M_PI/4 && $a <= 7*M_PI/4 ) $dy=0;
1144            }
1145
1146            $value->Set($this->iAllDirectionLabels[$i*(16/$num)]);
1147            $th = $value->GetHeight($dblImg);
1148            $tw = $value->GetWidth($dblImg);
1149            $xt=round($lr*cos($a)+$x) - $dx*$tw;
1150            $yt=round($y-$lr*sin($a)) - $dy*$th;
1151
1152            $value->Stroke($dblImg,$xt,$yt);
1153        }
1154
1155        if( __DEBUG ) {
1156            $dblImg->SetColor("red");
1157            $dblImg->Circle($xc,$yc,$lr+$r);
1158        }
1159
1160        // Stroke all the legs
1161        reset($this->iData);
1162        $keys = array_keys($this->iData);
1163        foreach($this->iData as $idx => $legdata) {
1164            $legdata = array_slice($legdata,1);
1165            $nn = count($legdata);
1166            if( is_string($idx) ) {
1167                $idx = strtoupper($idx);
1168                $idx = array_search($idx,$this->iAllDirectionLabels);
1169                if( $idx === false ) {
1170                    JpGraphError::RaiseL(22016);//('Windrose leg index must be numeric or direction label.');
1171                }
1172                if( $idx % (16 / $num) !== 0 ) {
1173                    JpGraphError::RaiseL(22017);//('Windrose data contains a direction which is not enabled. Please adjust what labels are displayed.');
1174                }
1175                $idx /= (16/$num) ;
1176
1177                if( in_array($idx,$keys,1) ) {
1178                    JpgraphError::RaiseL(22018,$idx);//('You have specified data for the same compass direction twice, once with text and once with index (Index='.$idx.')');
1179
1180                }
1181            }
1182            if( $idx < 0 || $idx > 15 ) {
1183                JpgraphError::RaiseL(22019);//('Index for direction must be between 0 and 15. You can\'t specify angles for a Regular Windplot, only index and compass directions.');
1184            }
1185            $a = $idx * (360 / $num) ;
1186            $a *= M_PI/180.0;
1187            $rri = $ri/$scaling;
1188            for( $j=0; $j < $nn; ++$j ) {
1189                // We want the non scaled original radius
1190                $legr = $this->scale->RelTranslate($legdata[$j], $r/$scaling,$ri/$scaling) ;
1191                $this->_StrokeWindLeg($dblImg, $xc, $yc, $a,
1192                $rri *$scaling,
1193                $legr *$scaling,
1194                $this->iLegweights[$j % $nlw] * $scaling,
1195                $this->iLegColors[$j % $nlc]);
1196                $rri += $legr;
1197            }
1198        }
1199    }
1200
1201
1202    function getWidth($aImg) {
1203
1204        $scaling = 1;//$this->iAntiAlias ? 2 : 1 ;
1205        if( $this->iSize > 0 && $this->iSize < 1 ) {
1206                        $this->iSize *= min($aImg->width,$aImg->height);
1207        }
1208
1209
1210        $value = new Text();
1211        $value->SetFont($this->iFontFamily,$this->iFontStyle,$this->iFontSize*$scaling);
1212        $value->SetColor($this->iFontColor);
1213        // Setup extra size around the graph needed so that the labels
1214        // doesn't get cut. For this we need to find the largest label.
1215        // The code below gives a possible a little to large margin. The
1216        // really, really proper way would be to account for what angle
1217        // the label are at
1218        $n = count($this->iLabels);
1219        if( $n > 0 ) {
1220            $maxh=0;$maxw=0;
1221            foreach($this->iLabels as $key => $lbl) {
1222                $value->Set($lbl);
1223                $maxw = max($maxw,$value->GetWidth($aImg));
1224            }
1225        }
1226        else {
1227            $value->Set('888.888'); // Dummy value to get width/height
1228            $maxw = $value->GetWidth($aImg);
1229        }
1230        // Add an extra margin of 50% the font size
1231        $maxw += round($this->iFontSize*$scaling * 0.4) ;
1232
1233        $valxmarg = 1.5*$maxw+2*$this->iLabelMargin*$scaling;
1234        $w = round($this->iSize*$scaling + $valxmarg);
1235
1236        // Make sure that the width of the legend fits
1237        $legendwidth = $this->_StrokeLegend($aImg,0,0,$scaling,true)+10*$scaling;
1238        $w = max($w,$legendwidth);
1239
1240        return $w;
1241    }
1242
1243    function getHeight($aImg) {
1244
1245        $scaling = 1;//$this->iAntiAlias ? 2 : 1 ;
1246        if( $this->iSize > 0 && $this->iSize < 1 ) {
1247                        $this->iSize *= min($aImg->width,$aImg->height);
1248        }
1249
1250        $value = new Text();
1251        $value->SetFont($this->iFontFamily,$this->iFontStyle,$this->iFontSize*$scaling);
1252        $value->SetColor($this->iFontColor);
1253        // Setup extra size around the graph needed so that the labels
1254        // doesn't get cut. For this we need to find the largest label.
1255        // The code below gives a possible a little to large margin. The
1256        // really, really proper way would be to account for what angle
1257        // the label are at
1258        $n = count($this->iLabels);
1259        if( $n > 0 ) {
1260            $maxh=0;$maxw=0;
1261            foreach($this->iLabels as $key => $lbl) {
1262                $value->Set($lbl);
1263                $maxh = max($maxh,$value->GetHeight($aImg));
1264            }
1265        }
1266        else {
1267            $value->Set('180.8'); // Dummy value to get width/height
1268            $maxh = $value->GetHeight($aImg);
1269        }
1270        // Add an extra margin of 50% the font size
1271        //$maxh += round($this->iFontSize*$scaling * 0.5) ;
1272        $valymarg = 2*$maxh+2*$this->iLabelMargin*$scaling;
1273
1274        $legendheight = round($this->legend->iShow ? 1 : 0);
1275        $legendheight *= max($this->legend->iCircleRadius*2,$this->legend->iTxtFontSize*2)+
1276                                         $this->legend->iMargin + $this->legend->iBottomMargin + 2;
1277        $legendheight *= $scaling;
1278        $h = round($this->iSize*$scaling + $valymarg) + $legendheight ;
1279
1280        return $h;
1281    }
1282
1283    function Stroke($aGraph) {
1284
1285                $aImg = $aGraph->img;
1286
1287                if( $this->iX > 0 && $this->iX < 1 ) {
1288                        $this->iX = round( $aImg->width * $this->iX ) ;
1289                }
1290
1291        if( $this->iY > 0 && $this->iY < 1 ) {
1292                $this->iY = round( $aImg->height * $this->iY ) ;
1293        }
1294
1295        if( $this->iSize > 0 && $this->iSize < 1 ) {
1296                        $this->iSize *= min($aImg->width,$aImg->height);
1297        }
1298
1299        if( $this->iCenterSize > 0 && $this->iCenterSize < 1 ) {
1300                        $this->iCenterSize *= $this->iSize;
1301        }
1302
1303        $this->scale->AutoScale(($this->iSize - $this->iCenterSize)/2, round(2.5*$this->scale->iFontSize));
1304
1305        $scaling = $this->iAntiAlias ? 2 : 1 ;
1306
1307        $value = new Text();
1308        $value->SetFont($this->iFontFamily,$this->iFontStyle,$this->iFontSize*$scaling);
1309        $value->SetColor($this->iFontColor);
1310
1311        $legendheight = round($this->legend->iShow ? 1 : 0);
1312        $legendheight *= max($this->legend->iCircleRadius*2,$this->legend->iTxtFontSize*2)+
1313        $this->legend->iMargin + $this->legend->iBottomMargin + 2;
1314        $legendheight *= $scaling;
1315
1316        $w = $scaling*$this->getWidth($aImg);
1317        $h = $scaling*$this->getHeight($aImg);
1318
1319        // Copy back the double buffered image to the proper canvas
1320        $ww = $w / $scaling ;
1321        $hh = $h / $scaling ;
1322
1323        // Create the double buffer
1324        if( $this->iAntiAlias ) {
1325            $dblImg = new RotImage($w,$h);
1326            // Set the background color
1327            $dblImg->SetColor($this->iColor);
1328            $dblImg->FilledRectangle(0,0,$w,$h);
1329        }
1330        else {
1331            $dblImg = $aImg ;
1332            // Make sure the ix and it coordinates correpond to the new top left center
1333            $dblImg->SetTranslation($this->iX-$w/2, $this->iY-$h/2);
1334        }
1335
1336        if( __DEBUG ) {
1337            $dblImg->SetColor('red');
1338            $dblImg->Rectangle(0,0,$w-1,$h-1);
1339        }
1340
1341        $dblImg->SetColor('black');
1342
1343        if( $this->iShowBox ) {
1344            $dblImg->SetColor($this->iBoxColor);
1345            $old = $dblImg->SetLineWeight($this->iBoxWeight);
1346            $dblImg->SetLineStyle($this->iBoxStyle);
1347            $dblImg->Rectangle(0,0,$w-1,$h-1);
1348            $dblImg->SetLineWeight($old);
1349        }
1350
1351        $xc = round($w/2);
1352        $yc = round(($h-$legendheight)/2);
1353
1354        if( __DEBUG ) {
1355            $dblImg->SetColor('red');
1356            $old = $dblImg->SetLineWeight(2);
1357            $dblImg->Line($xc-5,$yc-5,$xc+5,$yc+5);
1358                        $dblImg->Line($xc+5,$yc-5,$xc-5,$yc+5);
1359                        $dblImg->SetLineWeight($old);
1360        }
1361
1362        $this->iSize *= $scaling;
1363
1364        // Inner circle size
1365        $ri = $this->iCenterSize/2 ;
1366
1367        // Full circle radius
1368        $r = round( $this->iSize/2 );
1369
1370        // Get number of grid circles
1371        $n = $this->scale->GetNumCirc();
1372
1373        // Plot circle grids
1374        $ri *= $scaling ;
1375        $rr = round(($r-$ri)/$n);
1376        for( $i = 1; $i <= $n; ++$i ) {
1377            $this->_ThickCircle($dblImg,$xc,$yc,$rr*$i+$ri,
1378            $this->iCircGridWeight,$this->iGridColor1);
1379        }
1380
1381        $num = 0 ;
1382
1383        if( $this->iType == WINDROSE_TYPEFREE ) {
1384            $this->_StrokeFreeRose($dblImg,$value,$scaling,$xc,$yc,$r,$ri);
1385        }
1386        else {
1387            // Check if we need to re-code the interpretation of the ordinal
1388            // number in the data. Internally ordinal value 0 is East and then
1389            // counted anti-clockwise. The user might choose an encoding
1390            // that have 0 being the first axis to the right of the "N" axis and then
1391            // counted clock-wise
1392            if( $this->iOrdinalEncoding == KEYENCODING_CLOCKWISE ) {
1393                if( $this->iType == WINDROSE_TYPE16 ) {
1394                    $const1 = 19; $const2 = 16;
1395                }
1396                elseif( $this->iType == WINDROSE_TYPE8 ) {
1397                    $const1 = 9; $const2 = 8;
1398                }
1399                else {
1400                    $const1 = 4; $const2 = 4;
1401                }
1402                $tmp = array();
1403                $n=count($this->iData);
1404                foreach( $this->iData as $key => $val ) {
1405                    if( is_numeric($key) ) {
1406                        $key = ($const1 - $key) % $const2 ;
1407                    }
1408                    $tmp[$key] = $val;
1409                }
1410                $this->iData = $tmp;
1411            }
1412            $this->_StrokeRegularRose($dblImg,$value,$scaling,$xc,$yc,$r,$ri);
1413        }
1414
1415        // Stroke the labels
1416        $this->scale->iFontSize *= $scaling;
1417        $this->scale->iZFontSize *= $scaling;
1418        $this->scale->StrokeLabels($dblImg,$xc,$yc,$ri,$rr);
1419
1420        // Stroke the inner circle again since the legs
1421        // might have written over it
1422        $this->_ThickCircle($dblImg,$xc,$yc,$ri,$this->iCircGridWeight,$this->iGridColor1);
1423
1424        if( $ww > $aImg->width ) {
1425            JpgraphError::RaiseL(22020);
1426            //('Windrose plot is too large to fit the specified Graph size. Please use WindrosePlot::SetSize() to make the plot smaller or increase the size of the Graph in the initial WindroseGraph() call.');
1427        }
1428
1429        $x = $xc;
1430        $y = $h;
1431        $this->_StrokeLegend($dblImg,$x,$y,$scaling);
1432
1433        if( $this->iAntiAlias ) {
1434            $aImg->Copy($dblImg->img, $this->iX-$ww/2, $this->iY-$hh/2, 0, 0, $ww,$hh, $w,$h);
1435        }
1436
1437        // We need to restore the translation matrix
1438        $aImg->SetTranslation(0,0);
1439
1440    }
1441
1442}
1443
1444//============================================================
1445// CLASS WindroseGraph
1446//============================================================
1447class WindroseGraph extends Graph {
1448    private $posx, $posy;
1449    public $plots=array();
1450
1451    function __construct($width=300,$height=200,$cachedName="",$timeout=0,$inline=1) {
1452        parent::__construct($width,$height,$cachedName,$timeout,$inline);
1453        $this->posx=$width/2;
1454        $this->posy=$height/2;
1455        $this->SetColor('white');
1456        $this->title->SetFont(FF_VERDANA,FS_NORMAL,12);
1457        $this->title->SetMargin(8);
1458        $this->subtitle->SetFont(FF_VERDANA,FS_NORMAL,10);
1459        $this->subtitle->SetMargin(0);
1460        $this->subsubtitle->SetFont(FF_VERDANA,FS_NORMAL,8);
1461        $this->subsubtitle->SetMargin(0);
1462    }
1463
1464    function StrokeTexts() {
1465        if( $this->texts != null ) {
1466            $n = count($this->texts);
1467            for($i=0; $i < $n; ++$i ) {
1468                $this->texts[$i]->Stroke($this->img);
1469            }
1470        }
1471    }
1472
1473    function StrokeIcons() {
1474        if( $this->iIcons != null ) {
1475            $n = count($this->iIcons);
1476            for( $i=0; $i < $n; ++$i ) {
1477                // Since Windrose graphs doesn't have any linear scale the position of
1478                // each icon has to be given as absolute coordinates
1479                $this->iIcons[$i]->_Stroke($this->img);
1480            }
1481        }
1482    }
1483
1484    //---------------
1485    // PUBLIC METHODS
1486    function Add($aObj) {
1487        if( is_array($aObj) && count($aObj) > 0 ) {
1488            $cl = $aObj[0];
1489        }
1490        else {
1491            $cl = $aObj;
1492        }
1493        if( $cl instanceof Text ) {
1494            $this->AddText($aObj);
1495        }
1496        elseif( $cl instanceof IconPlot ) {
1497            $this->AddIcon($aObj);
1498        }
1499        elseif( ($cl instanceof WindrosePlot) || ($cl instanceof LayoutRect) || ($cl instanceof LayoutHor)) {
1500            $this->plots[] = $aObj;
1501        }
1502        else {
1503            JpgraphError::RaiseL(22021);
1504        }
1505    }
1506
1507    function AddText($aTxt,$aToY2=false) {
1508        parent::AddText($aTxt);
1509    }
1510
1511    function SetColor($c) {
1512        $this->SetMarginColor($c);
1513    }
1514
1515    // Method description
1516    function Stroke($aStrokeFileName="") {
1517
1518        // If the filename is the predefined value = '_csim_special_'
1519        // we assume that the call to stroke only needs to do enough
1520        // to correctly generate the CSIM maps.
1521        // We use this variable to skip things we don't strictly need
1522        // to do to generate the image map to improve performance
1523        // as best we can. Therefore you will see a lot of tests !$_csim in the
1524        // code below.
1525        $_csim = ($aStrokeFileName===_CSIM_SPECIALFILE);
1526
1527        // We need to know if we have stroked the plot in the
1528        // GetCSIMareas. Otherwise the CSIM hasn't been generated
1529        // and in the case of GetCSIM called before stroke to generate
1530        // CSIM without storing an image to disk GetCSIM must call Stroke.
1531        $this->iHasStroked = true;
1532
1533        if( $this->background_image != "" || $this->background_cflag != "" ) {
1534            $this->StrokeFrameBackground();
1535        }
1536        else {
1537            $this->StrokeFrame();
1538        }
1539
1540        // n holds number of plots
1541        $n = count($this->plots);
1542        for($i=0; $i < $n ; ++$i) {
1543                $this->plots[$i]->Stroke($this);
1544        }
1545
1546        $this->footer->Stroke($this->img);
1547        $this->StrokeIcons();
1548        $this->StrokeTexts();
1549        $this->StrokeTitles();
1550
1551        // If the filename is given as the special "__handle"
1552        // then the image handler is returned and the image is NOT
1553        // streamed back
1554        if( $aStrokeFileName == _IMG_HANDLER ) {
1555            return $this->img->img;
1556        }
1557        else {
1558            // Finally stream the generated picture
1559            $this->cache->PutAndStream($this->img,$this->cache_name,$this->inline,
1560            $aStrokeFileName);
1561        }
1562    }
1563
1564} // Class
1565
1566?>
Note: See TracBrowser for help on using the repository browser.