source: trunk/client/modules/Elezioni/grafici/jpgraph_windrose.php@ 266

Last change on this file since 266 was 266, checked in by roby, 5 years ago
File size: 52.2 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.