PageRenderTime 62ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/plugin/jpgraph/jpgraph_windrose.php

https://bitbucket.org/kkfd008/simplephp
PHP | 1566 lines | 1143 code | 252 blank | 171 comment | 209 complexity | 26e83764bd9720c5a413bb9752824f71 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

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

Large files files are truncated, but you can click here to view the full file