PageRenderTime 110ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 1ms

/code/ryzom/tools/server/admin/jpgraph/jpgraph.php

https://bitbucket.org/mattraykowski/ryzomcore_demoshard
PHP | 8563 lines | 6475 code | 998 blank | 1090 comment | 1307 complexity | cf2757885786d4194abb37ba5582567a MD5 | raw file
Possible License(s): AGPL-3.0, GPL-3.0, LGPL-2.1
  1. <?php
  2. //=======================================================================
  3. // File: JPGRAPH.PHP
  4. // Description: PHP Graph Plotting library. Base module.
  5. // Created: 2001-01-08
  6. // Author: Johan Persson (johanp@aditus.nu)
  7. // Ver: $Id: jpgraph.php,v 1.1 2006/07/07 13:37:14 powles Exp $
  8. //
  9. // Copyright (c) Aditus Consulting. All rights reserved.
  10. //========================================================================
  11. require_once('jpg-config.inc');
  12. // Version info
  13. DEFINE('JPG_VERSION','1.20.3');
  14. // For internal use only
  15. DEFINE("_JPG_DEBUG",false);
  16. DEFINE("_FORCE_IMGTOFILE",false);
  17. DEFINE("_FORCE_IMGDIR",'/tmp/jpgimg/');
  18. //------------------------------------------------------------------------
  19. // Automatic settings of path for cache and font directory
  20. // if they have not been previously specified
  21. //------------------------------------------------------------------------
  22. if(USE_CACHE) {
  23. if (!defined('CACHE_DIR')) {
  24. if ( strstr( PHP_OS, 'WIN') ) {
  25. if( empty($_SERVER['TEMP']) ) {
  26. $t = new ErrMsgText();
  27. $msg = $t->Get(11,$file,$lineno);
  28. die($msg);
  29. }
  30. else {
  31. DEFINE('CACHE_DIR', $_SERVER['TEMP'] . '/');
  32. }
  33. } else {
  34. DEFINE('CACHE_DIR','/tmp/jpgraph_cache/');
  35. }
  36. }
  37. }
  38. elseif( !defined('CACHE_DIR') ) {
  39. DEFINE('CACHE_DIR', '');
  40. }
  41. if (!defined('TTF_DIR')) {
  42. if (strstr( PHP_OS, 'WIN') ) {
  43. $sroot = getenv('SystemRoot');
  44. if( empty($sroot) ) {
  45. $t = new ErrMsgText();
  46. $msg = $t->Get(12,$file,$lineno);
  47. die($msg);
  48. }
  49. else {
  50. DEFINE('TTF_DIR', $sroot.'/fonts/');
  51. }
  52. } else {
  53. DEFINE('TTF_DIR','/usr/X11R6/lib/X11/fonts/truetype/');
  54. }
  55. }
  56. //------------------------------------------------------------------
  57. // Constants which are used as parameters for the method calls
  58. //------------------------------------------------------------------
  59. // TTF Font families
  60. DEFINE("FF_COURIER",10);
  61. DEFINE("FF_VERDANA",11);
  62. DEFINE("FF_TIMES",12);
  63. DEFINE("FF_COMIC",14);
  64. DEFINE("FF_ARIAL",15);
  65. DEFINE("FF_GEORGIA",16);
  66. DEFINE("FF_TREBUCHE",17);
  67. // Gnome Vera font
  68. // Available from http://www.gnome.org/fonts/
  69. DEFINE("FF_VERA",19);
  70. DEFINE("FF_VERAMONO",20);
  71. DEFINE("FF_VERASERIF",21);
  72. // Chinese font
  73. DEFINE("FF_SIMSUN",30);
  74. DEFINE("FF_CHINESE",31);
  75. DEFINE("FF_BIG5",31);
  76. // Japanese font
  77. DEFINE("FF_MINCHO",40);
  78. DEFINE("FF_PMINCHO",41);
  79. DEFINE("FF_GOTHIC",42);
  80. DEFINE("FF_PGOTHIC",43);
  81. // Limits for TTF fonts
  82. DEFINE('_FF_FIRST',10);
  83. DEFINE('_FF_LAST',43);
  84. // Older deprecated fonts
  85. DEFINE("FF_BOOK",91); // Deprecated fonts from 1.9
  86. DEFINE("FF_HANDWRT",92); // Deprecated fonts from 1.9
  87. // TTF Font styles
  88. DEFINE("FS_NORMAL",9001);
  89. DEFINE("FS_BOLD",9002);
  90. DEFINE("FS_ITALIC",9003);
  91. DEFINE("FS_BOLDIT",9004);
  92. DEFINE("FS_BOLDITALIC",9004);
  93. //Definitions for internal font, new style
  94. DEFINE("FF_FONT0",1);
  95. DEFINE("FF_FONT1",2);
  96. DEFINE("FF_FONT2",4);
  97. // Tick density
  98. DEFINE("TICKD_DENSE",1);
  99. DEFINE("TICKD_NORMAL",2);
  100. DEFINE("TICKD_SPARSE",3);
  101. DEFINE("TICKD_VERYSPARSE",4);
  102. // Side for ticks and labels.
  103. DEFINE("SIDE_LEFT",-1);
  104. DEFINE("SIDE_RIGHT",1);
  105. DEFINE("SIDE_DOWN",-1);
  106. DEFINE("SIDE_BOTTOM",-1);
  107. DEFINE("SIDE_UP",1);
  108. DEFINE("SIDE_TOP",1);
  109. // Legend type stacked vertical or horizontal
  110. DEFINE("LEGEND_VERT",0);
  111. DEFINE("LEGEND_HOR",1);
  112. // Mark types for plot marks
  113. DEFINE("MARK_SQUARE",1);
  114. DEFINE("MARK_UTRIANGLE",2);
  115. DEFINE("MARK_DTRIANGLE",3);
  116. DEFINE("MARK_DIAMOND",4);
  117. DEFINE("MARK_CIRCLE",5);
  118. DEFINE("MARK_FILLEDCIRCLE",6);
  119. DEFINE("MARK_CROSS",7);
  120. DEFINE("MARK_STAR",8);
  121. DEFINE("MARK_X",9);
  122. DEFINE("MARK_LEFTTRIANGLE",10);
  123. DEFINE("MARK_RIGHTTRIANGLE",11);
  124. DEFINE("MARK_FLASH",12);
  125. DEFINE("MARK_IMG",13);
  126. DEFINE("MARK_FLAG1",14);
  127. DEFINE("MARK_FLAG2",15);
  128. DEFINE("MARK_FLAG3",16);
  129. DEFINE("MARK_FLAG4",17);
  130. // Builtin images
  131. DEFINE("MARK_IMG_PUSHPIN",50);
  132. DEFINE("MARK_IMG_SPUSHPIN",50);
  133. DEFINE("MARK_IMG_LPUSHPIN",51);
  134. DEFINE("MARK_IMG_DIAMOND",52);
  135. DEFINE("MARK_IMG_SQUARE",53);
  136. DEFINE("MARK_IMG_STAR",54);
  137. DEFINE("MARK_IMG_BALL",55);
  138. DEFINE("MARK_IMG_SBALL",55);
  139. DEFINE("MARK_IMG_MBALL",56);
  140. DEFINE("MARK_IMG_LBALL",57);
  141. DEFINE("MARK_IMG_BEVEL",58);
  142. // Inline defines
  143. DEFINE("INLINE_YES",1);
  144. DEFINE("INLINE_NO",0);
  145. // Format for background images
  146. DEFINE("BGIMG_FILLPLOT",1);
  147. DEFINE("BGIMG_FILLFRAME",2);
  148. DEFINE("BGIMG_COPY",3);
  149. DEFINE("BGIMG_CENTER",4);
  150. // Depth of objects
  151. DEFINE("DEPTH_BACK",0);
  152. DEFINE("DEPTH_FRONT",1);
  153. // Direction
  154. DEFINE("VERTICAL",1);
  155. DEFINE("HORIZONTAL",0);
  156. // Axis styles for scientific style axis
  157. DEFINE('AXSTYLE_SIMPLE',1);
  158. DEFINE('AXSTYLE_BOXIN',2);
  159. DEFINE('AXSTYLE_BOXOUT',3);
  160. DEFINE('AXSTYLE_YBOXIN',4);
  161. DEFINE('AXSTYLE_YBOXOUT',5);
  162. // Style for title backgrounds
  163. DEFINE('TITLEBKG_STYLE1',1);
  164. DEFINE('TITLEBKG_STYLE2',2);
  165. DEFINE('TITLEBKG_STYLE3',3);
  166. DEFINE('TITLEBKG_FRAME_NONE',0);
  167. DEFINE('TITLEBKG_FRAME_FULL',1);
  168. DEFINE('TITLEBKG_FRAME_BOTTOM',2);
  169. DEFINE('TITLEBKG_FRAME_BEVEL',3);
  170. DEFINE('TITLEBKG_FILLSTYLE_HSTRIPED',1);
  171. DEFINE('TITLEBKG_FILLSTYLE_VSTRIPED',2);
  172. DEFINE('TITLEBKG_FILLSTYLE_SOLID',3);
  173. // Style for background gradient fills
  174. DEFINE('BGRAD_FRAME',1);
  175. DEFINE('BGRAD_MARGIN',2);
  176. DEFINE('BGRAD_PLOT',3);
  177. // Width of tab titles
  178. DEFINE('TABTITLE_WIDTHFIT',0);
  179. DEFINE('TABTITLE_WIDTHFULL',-1);
  180. // Defines for 3D skew directions
  181. DEFINE('SKEW3D_UP',0);
  182. DEFINE('SKEW3D_DOWN',1);
  183. DEFINE('SKEW3D_LEFT',2);
  184. DEFINE('SKEW3D_RIGHT',3);
  185. //
  186. // Get hold of gradient class (In Version 2.x)
  187. // A client of the library has to manually include this
  188. //
  189. require_once 'jpgraph_gradient.php';
  190. class ErrMsgText {
  191. var $lt=NULL;
  192. var $supportedLocales = array('en');
  193. function ErrMsgText() {
  194. GLOBAL $__jpg_err_locale;
  195. if( !in_array($__jpg_err_locale,$this->supportedLocales) )
  196. $aLoc = 'en';
  197. require_once('lang/'.$__jpg_err_locale.'.inc.php');
  198. $this->lt = $_jpg_messages;
  199. }
  200. function Get($errnbr,$a1=null,$a2=null,$a3=null,$a4=null,$a5=null) {
  201. if( !isset($this->lt[$errnbr]) ) {
  202. return 'Internal error: The specified error message does not exist in the chosen locale. (Please blame the translator...))';
  203. }
  204. $ea = $this->lt[$errnbr];
  205. $j=0;
  206. if( $a1 !== null ) {
  207. $argv[$j++] = $a1;
  208. if( $a2 !== null ) {
  209. $argv[$j++] = $a2;
  210. if( $a3 !== null ) {
  211. $argv[$j++] = $a3;
  212. if( $a4 !== null ) {
  213. $argv[$j++] = $a4;
  214. if( $a5 !== null ) {
  215. $argv[$j++] = $a5;
  216. }
  217. }
  218. }
  219. }
  220. }
  221. $numargs = $j;
  222. if( $ea[1] != $numargs ) {
  223. // Error message argument count do not match.
  224. // Just return the error message without arguments.
  225. return $ea[0];
  226. }
  227. switch( $numargs ) {
  228. case 1:
  229. $msg = sprintf($ea[0],$argv[0]);
  230. break;
  231. case 2:
  232. $msg = sprintf($ea[0],$argv[0],$argv[1]);
  233. break;
  234. case 3:
  235. $msg = sprintf($ea[0],$argv[0],$argv[1],$argv[2]);
  236. break;
  237. case 4:
  238. $msg = sprintf($ea[0],$argv[0],$argv[1],$argv[2],$argv[3]);
  239. break;
  240. case 5:
  241. $msg = sprintf($ea[0],$argv[0],$argv[1],$argv[2],$argv[3],$argv[4]);
  242. break;
  243. case 0:
  244. default:
  245. $msg = sprintf($ea[0]);
  246. break;
  247. }
  248. return $msg;
  249. }
  250. }
  251. //
  252. // A wrapper class that is used to access the specified error object
  253. // (to hide the global error parameter and avoid having a GLOBAL directive
  254. // in all methods.
  255. //
  256. GLOBAL $__jpg_err;
  257. GLOBAL $__jpg_err_locale ;
  258. $__jpg_err_locale = 'en';
  259. class JpGraphError {
  260. function Install($aErrObject) {
  261. GLOBAL $__jpg_err;
  262. $__jpg_err = $aErrObject;
  263. }
  264. function Raise($aMsg,$aHalt=true){
  265. GLOBAL $__jpg_err;
  266. $tmp = new $__jpg_err;
  267. $tmp->Raise($aMsg,$aHalt);
  268. }
  269. function RaiseL($errnbr,$a1=null,$a2=null,$a3=null,$a4=null,$a5=null) {
  270. GLOBAL $__jpg_err;
  271. $t = new ErrMsgText();
  272. $msg = $t->Get($errnbr,$a1,$a2,$a3,$a4,$a5);
  273. $tmp = new $__jpg_err;
  274. $tmp->Raise($msg);
  275. }
  276. }
  277. //
  278. // ... and install the default error handler
  279. //
  280. if( USE_IMAGE_ERROR_HANDLER ) {
  281. $__jpg_err = "JpGraphErrObjectImg";
  282. }
  283. else {
  284. $__jpg_err = "JpGraphErrObject";
  285. }
  286. //
  287. // Make GD sanity check
  288. //
  289. if( !function_exists("imagetypes") || !function_exists('imagecreatefromstring') ) {
  290. JpGraphError::RaiseL(25001);
  291. //("This PHP installation is not configured with the GD library. Please recompile PHP with GD support to run JpGraph. (Neither function imagetypes() nor imagecreatefromstring() does exist)");
  292. }
  293. //
  294. // Routine to determine if GD1 or GD2 is installed
  295. //
  296. function CheckGDVersion() {
  297. $GDfuncList = get_extension_funcs('gd');
  298. if( !$GDfuncList ) return 0 ;
  299. else {
  300. if( in_array('imagegd2',$GDfuncList) &&
  301. in_array('imagecreatetruecolor',$GDfuncList))
  302. return 2;
  303. else
  304. return 1;
  305. }
  306. }
  307. //
  308. // Check what version of the GD library is installed.
  309. //
  310. $GLOBALS['gd2'] = false;
  311. if( USE_LIBRARY_GD2 === 'auto' ) {
  312. $gdversion = CheckGDVersion();
  313. if( $gdversion == 2 ) {
  314. $GLOBALS['gd2'] = true;
  315. $GLOBALS['copyfunc'] = 'imagecopyresampled';
  316. }
  317. elseif( $gdversion == 1 ) {
  318. $GLOBALS['gd2'] = false;
  319. $GLOBALS['copyfunc'] = 'imagecopyresized';
  320. }
  321. else {
  322. JpGraphError::RaiseL(25002);
  323. //(" Your PHP installation does not seem to have the required GD library. Please see the PHP documentation on how to install and enable the GD library.");
  324. }
  325. }
  326. else {
  327. $GLOBALS['gd2'] = USE_LIBRARY_GD2;
  328. $GLOBALS['copyfunc'] = USE_LIBRARY_GD2 ? 'imagecopyresampled' : 'imagecopyresized';
  329. }
  330. //
  331. // First of all set up a default error handler
  332. //
  333. //=============================================================
  334. // The default trivial text error handler.
  335. //=============================================================
  336. class JpGraphErrObject {
  337. var $iTitle = "JpGraph Error";
  338. var $iDest = false;
  339. function JpGraphErrObject() {
  340. // Empty. Reserved for future use
  341. }
  342. function SetTitle($aTitle) {
  343. $this->iTitle = $aTitle;
  344. }
  345. function SetStrokeDest($aDest) {
  346. $this->iDest = $aDest;
  347. }
  348. // If aHalt is true then execution can't continue. Typical used for fatal errors.
  349. function Raise($aMsg,$aHalt=true) {
  350. $aMsg = $this->iTitle.' '.$aMsg;
  351. if ($this->iDest) {
  352. $f = @fopen($this->iDest,'a');
  353. if( $f ) {
  354. @fwrite($f,$aMsg);
  355. @fclose($f);
  356. }
  357. }
  358. else {
  359. echo $aMsg;
  360. }
  361. if( $aHalt )
  362. die();
  363. }
  364. }
  365. //==============================================================
  366. // An image based error handler
  367. //==============================================================
  368. class JpGraphErrObjectImg extends JpGraphErrObject {
  369. function Raise($aMsg,$aHalt=true) {
  370. $img_iconerror =
  371. 'iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAMAAAC7IEhfAAAAaV'.
  372. 'BMVEX//////2Xy8mLl5V/Z2VvMzFi/v1WyslKlpU+ZmUyMjEh/'.
  373. 'f0VyckJlZT9YWDxMTDjAwMDy8sLl5bnY2K/MzKW/v5yyspKlpY'.
  374. 'iYmH+MjHY/PzV/f2xycmJlZVlZWU9MTEXY2Ms/PzwyMjLFTjea'.
  375. 'AAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACx'.
  376. 'IAAAsSAdLdfvwAAAAHdElNRQfTBgISOCqusfs5AAABLUlEQVR4'.
  377. '2tWV3XKCMBBGWfkranCIVClKLd/7P2Q3QsgCxjDTq+6FE2cPH+'.
  378. 'xJ0Ogn2lQbsT+Wrs+buAZAV4W5T6Bs0YXBBwpKgEuIu+JERAX6'.
  379. 'wM2rHjmDdEITmsQEEmWADgZm6rAjhXsoMGY9B/NZBwJzBvn+e3'.
  380. 'wHntCAJdGu9SviwIwoZVDxPB9+Rc0TSEbQr0j3SA1gwdSn6Db0'.
  381. '6Tm1KfV6yzWGQO7zdpvyKLKBDmRFjzeB3LYgK7r6A/noDAfjtS'.
  382. 'IXaIzbJSv6WgUebTMV4EoRB8a2mQiQjgtF91HdKDKZ1gtFtQjk'.
  383. 'YcWaR5OKOhkYt+ZsTFdJRfPAApOpQYJTNHvCRSJR6SJngQadfc'.
  384. 'vd69OLMddVOPCGVnmrFD8bVYd3JXfxXPtLR/+mtv59/ALWiiMx'.
  385. 'qL72fwAAAABJRU5ErkJggg==' ;
  386. if( function_exists("imagetypes") )
  387. $supported = imagetypes();
  388. else
  389. $supported = 0;
  390. if( !function_exists('imagecreatefromstring') )
  391. $supported = 0;
  392. if( ob_get_length() || headers_sent() || !($supported & IMG_PNG) ) {
  393. // Special case for headers already sent or that the installation doesn't support
  394. // the PNG format (which the error icon is encoded in).
  395. // Dont return an image since it can't be displayed
  396. die($this->iTitle.' '.$aMsg);
  397. }
  398. $aMsg = wordwrap($aMsg,55);
  399. $lines = substr_count($aMsg,"\n");
  400. // Create the error icon GD
  401. $erricon = Image::CreateFromString(base64_decode($img_iconerror));
  402. // Create an image that contains the error text.
  403. $w=400;
  404. $h=100 + 15*max(0,$lines-3);
  405. $img = new Image($w,$h);
  406. // Drop shadow
  407. $img->SetColor("gray");
  408. $img->FilledRectangle(5,5,$w-1,$h-1,10);
  409. $img->SetColor("gray:0.7");
  410. $img->FilledRectangle(5,5,$w-3,$h-3,10);
  411. // Window background
  412. $img->SetColor("lightblue");
  413. $img->FilledRectangle(1,1,$w-5,$h-5);
  414. $img->CopyCanvasH($img->img,$erricon,5,30,0,0,40,40);
  415. // Window border
  416. $img->SetColor("black");
  417. $img->Rectangle(1,1,$w-5,$h-5);
  418. $img->Rectangle(0,0,$w-4,$h-4);
  419. // Window top row
  420. $img->SetColor("darkred");
  421. for($y=3; $y < 18; $y += 2 )
  422. $img->Line(1,$y,$w-6,$y);
  423. // "White shadow"
  424. $img->SetColor("white");
  425. // Left window edge
  426. $img->Line(2,2,2,$h-5);
  427. $img->Line(2,2,$w-6,2);
  428. // "Gray button shadow"
  429. $img->SetColor("darkgray");
  430. // Gray window shadow
  431. $img->Line(2,$h-6,$w-5,$h-6);
  432. $img->Line(3,$h-7,$w-5,$h-7);
  433. // Window title
  434. $m = floor($w/2-5);
  435. $l = 100;
  436. $img->SetColor("lightgray:1.3");
  437. $img->FilledRectangle($m-$l,2,$m+$l,16);
  438. // Stroke text
  439. $img->SetColor("darkred");
  440. $img->SetFont(FF_FONT2,FS_BOLD);
  441. $img->StrokeText($m-50,15,$this->iTitle);
  442. $img->SetColor("black");
  443. $img->SetFont(FF_FONT1,FS_NORMAL);
  444. $txt = new Text($aMsg,52,25);
  445. $txt->Align("left","top");
  446. $txt->Stroke($img);
  447. if ($this->iDest) {
  448. $img->Stream($this->iDest);
  449. } else {
  450. $img->Headers();
  451. $img->Stream();
  452. }
  453. if( $aHalt )
  454. die();
  455. }
  456. }
  457. //
  458. // Setup PHP error handler
  459. //
  460. function _phpErrorHandler($errno,$errmsg,$filename, $linenum, $vars) {
  461. // Respect current error level
  462. if( $errno & error_reporting() ) {
  463. JpGraphError::RaiseL(25003,basename($filename),$linenum,$errmsg);
  464. }
  465. }
  466. if( INSTALL_PHP_ERR_HANDLER ) {
  467. set_error_handler("_phpErrorHandler");
  468. }
  469. //
  470. //Check if there were any warnings, perhaps some wrong includes by the user
  471. //
  472. if( isset($GLOBALS['php_errormsg']) && CATCH_PHPERRMSG &&
  473. !preg_match('|Deprecated|', $GLOBALS['php_errormsg'])) {
  474. JpGraphError::RaiseL(25004,$GLOBALS['php_errormsg']);
  475. }
  476. // Useful mathematical function
  477. function sign($a) {return $a >= 0 ? 1 : -1;}
  478. // Utility function to generate an image name based on the filename we
  479. // are running from and assuming we use auto detection of graphic format
  480. // (top level), i.e it is safe to call this function
  481. // from a script that uses JpGraph
  482. function GenImgName() {
  483. global $_SERVER;
  484. // Determine what format we should use when we save the images
  485. $supported = imagetypes();
  486. if( $supported & IMG_PNG ) $img_format="png";
  487. elseif( $supported & IMG_GIF ) $img_format="gif";
  488. elseif( $supported & IMG_JPG ) $img_format="jpeg";
  489. if( !isset($_SERVER['PHP_SELF']) )
  490. JpGraphError::RaiseL(25005);
  491. //(" Can't access PHP_SELF, PHP global variable. You can't run PHP from command line if you want to use the 'auto' naming of cache or image files.");
  492. $fname = basename($_SERVER['PHP_SELF']);
  493. if( !empty($_SERVER['QUERY_STRING']) ) {
  494. $q = @$_SERVER['QUERY_STRING'];
  495. $fname .= '?'.preg_replace("/\W/", "_", $q).'.'.$img_format;
  496. }
  497. else {
  498. $fname = substr($fname,0,strlen($fname)-4).'.'.$img_format;
  499. }
  500. return $fname;
  501. }
  502. class LanguageConv {
  503. var $g2312 = null ;
  504. function Convert($aTxt,$aFF) {
  505. if( LANGUAGE_CYRILLIC ) {
  506. if( CYRILLIC_FROM_WINDOWS ) {
  507. $aTxt = convert_cyr_string($aTxt, "w", "k");
  508. }
  509. $isostring = convert_cyr_string($aTxt, "k", "i");
  510. $unistring = LanguageConv::iso2uni($isostring);
  511. return $unistring;
  512. }
  513. elseif( $aFF === FF_SIMSUN ) {
  514. // Do Chinese conversion
  515. if( $this->g2312 == null ) {
  516. include_once 'jpgraph_gb2312.php' ;
  517. $this->g2312 = new GB2312toUTF8();
  518. }
  519. return $this->g2312->gb2utf8($aTxt);
  520. }
  521. elseif( $aFF === FF_CHINESE ) {
  522. if( !function_exists('iconv') ) {
  523. JpGraphError::RaiseL(25006);
  524. //('Usage of FF_CHINESE (FF_BIG5) font family requires that your PHP setup has the iconv() function. By default this is not compiled into PHP (needs the "--width-iconv" when configured).');
  525. }
  526. return iconv('BIG5','UTF-8',$aTxt);
  527. }
  528. else
  529. return $aTxt;
  530. }
  531. // Translate iso encoding to unicode
  532. function iso2uni ($isoline){
  533. $uniline='';
  534. for ($i=0; $i < strlen($isoline); $i++){
  535. $thischar=substr($isoline,$i,1);
  536. $charcode=ord($thischar);
  537. $uniline.=($charcode>175) ? "&#" . (1040+($charcode-176)). ";" : $thischar;
  538. }
  539. return $uniline;
  540. }
  541. }
  542. //===================================================
  543. // CLASS JpgTimer
  544. // Description: General timing utility class to handle
  545. // time measurement of generating graphs. Multiple
  546. // timers can be started.
  547. //===================================================
  548. class JpgTimer {
  549. var $start;
  550. var $idx;
  551. //---------------
  552. // CONSTRUCTOR
  553. function JpgTimer() {
  554. $this->idx=0;
  555. }
  556. //---------------
  557. // PUBLIC METHODS
  558. // Push a new timer start on stack
  559. function Push() {
  560. list($ms,$s)=explode(" ",microtime());
  561. $this->start[$this->idx++]=floor($ms*1000) + 1000*$s;
  562. }
  563. // Pop the latest timer start and return the diff with the
  564. // current time
  565. function Pop() {
  566. assert($this->idx>0);
  567. list($ms,$s)=explode(" ",microtime());
  568. $etime=floor($ms*1000) + (1000*$s);
  569. $this->idx--;
  570. return $etime-$this->start[$this->idx];
  571. }
  572. } // Class
  573. $gJpgBrandTiming = BRAND_TIMING;
  574. //===================================================
  575. // CLASS DateLocale
  576. // Description: Hold localized text used in dates
  577. //===================================================
  578. class DateLocale {
  579. var $iLocale = 'C'; // environmental locale be used by default
  580. var $iDayAbb = null;
  581. var $iShortDay = null;
  582. var $iShortMonth = null;
  583. var $iMonthName = null;
  584. //---------------
  585. // CONSTRUCTOR
  586. function DateLocale() {
  587. settype($this->iDayAbb, 'array');
  588. settype($this->iShortDay, 'array');
  589. settype($this->iShortMonth, 'array');
  590. settype($this->iMonthName, 'array');
  591. $this->Set('C');
  592. }
  593. //---------------
  594. // PUBLIC METHODS
  595. function Set($aLocale) {
  596. if ( in_array($aLocale, array_keys($this->iDayAbb)) ){
  597. $this->iLocale = $aLocale;
  598. return TRUE; // already cached nothing else to do!
  599. }
  600. $pLocale = setlocale(LC_TIME, 0); // get current locale for LC_TIME
  601. $res = @setlocale(LC_TIME, $aLocale);
  602. if ( ! $res ){
  603. JpGraphError::RaiseL(25007,$aLocale);
  604. //("You are trying to use the locale ($aLocale) which your PHP installation does not support. Hint: Use '' to indicate the default locale for this geographic region.");
  605. return FALSE;
  606. }
  607. $this->iLocale = $aLocale;
  608. for ( $i = 0, $ofs = 0 - strftime('%w'); $i < 7; $i++, $ofs++ ){
  609. $day = strftime('%a', strtotime("$ofs day"));
  610. $day{0} = strtoupper($day{0});
  611. $this->iDayAbb[$aLocale][]= $day{0};
  612. $this->iShortDay[$aLocale][]= $day;
  613. }
  614. for($i=1; $i<=12; ++$i) {
  615. list($short ,$full) = explode('|', strftime("%b|%B",strtotime("2001-$i-01")));
  616. $this->iShortMonth[$aLocale][] = ucfirst($short);
  617. $this->iMonthName [$aLocale][] = ucfirst($full);
  618. }
  619. // Return to original locale
  620. setlocale(LC_TIME, $pLocale);
  621. return TRUE;
  622. }
  623. function GetDayAbb() {
  624. return $this->iDayAbb[$this->iLocale];
  625. }
  626. function GetShortDay() {
  627. return $this->iShortDay[$this->iLocale];
  628. }
  629. function GetShortMonth() {
  630. return $this->iShortMonth[$this->iLocale];
  631. }
  632. function GetShortMonthName($aNbr) {
  633. return $this->iShortMonth[$this->iLocale][$aNbr];
  634. }
  635. function GetLongMonthName($aNbr) {
  636. return $this->iMonthName[$this->iLocale][$aNbr];
  637. }
  638. function GetMonth() {
  639. return $this->iMonthName[$this->iLocale];
  640. }
  641. }
  642. $gDateLocale = new DateLocale();
  643. $gJpgDateLocale = new DateLocale();
  644. //=======================================================
  645. // CLASS Footer
  646. // Description: Encapsulates the footer line in the Graph
  647. //=======================================================
  648. class Footer {
  649. var $left,$center,$right;
  650. var $iLeftMargin = 3;
  651. var $iRightMargin = 3;
  652. var $iBottomMargin = 3;
  653. function Footer() {
  654. $this->left = new Text();
  655. $this->left->ParagraphAlign('left');
  656. $this->center = new Text();
  657. $this->center->ParagraphAlign('center');
  658. $this->right = new Text();
  659. $this->right->ParagraphAlign('right');
  660. }
  661. function Stroke(&$aImg) {
  662. $y = $aImg->height - $this->iBottomMargin;
  663. $x = $this->iLeftMargin;
  664. $this->left->Align('left','bottom');
  665. $this->left->Stroke($aImg,$x,$y);
  666. $x = ($aImg->width - $this->iLeftMargin - $this->iRightMargin)/2;
  667. $this->center->Align('center','bottom');
  668. $this->center->Stroke($aImg,$x,$y);
  669. $x = $aImg->width - $this->iRightMargin;
  670. $this->right->Align('right','bottom');
  671. $this->right->Stroke($aImg,$x,$y);
  672. }
  673. }
  674. //===================================================
  675. // CLASS Graph
  676. // Description: Main class to handle graphs
  677. //===================================================
  678. class Graph {
  679. var $cache=null; // Cache object (singleton)
  680. var $img=null; // Img object (singleton)
  681. var $plots=array(); // Array of all plot object in the graph (for Y 1 axis)
  682. var $y2plots=array();// Array of all plot object in the graph (for Y 2 axis)
  683. var $ynplots=array();
  684. var $xscale=null; // X Scale object (could be instance of LinearScale or LogScale
  685. var $yscale=null,$y2scale=null, $ynscale=array();
  686. var $iIcons = array(); // Array of Icons to add to
  687. var $cache_name; // File name to be used for the current graph in the cache directory
  688. var $xgrid=null; // X Grid object (linear or logarithmic)
  689. var $ygrid=null,$y2grid=null;
  690. var $doframe=true,$frame_color=array(0,0,0), $frame_weight=1; // Frame around graph
  691. var $boxed=false, $box_color=array(0,0,0), $box_weight=1; // Box around plot area
  692. var $doshadow=false,$shadow_width=4,$shadow_color=array(102,102,102); // Shadow for graph
  693. var $xaxis=null; // X-axis (instane of Axis class)
  694. var $yaxis=null, $y2axis=null, $ynaxis=array(); // Y axis (instance of Axis class)
  695. var $margin_color=array(200,200,200); // Margin color of graph
  696. var $plotarea_color=array(255,255,255); // Plot area color
  697. var $title,$subtitle,$subsubtitle; // Title and subtitle(s) text object
  698. var $axtype="linlin"; // Type of axis
  699. var $xtick_factor; // Factot to determine the maximum number of ticks depending on the plot with
  700. var $texts=null, $y2texts=null; // Text object to ge shown in the graph
  701. var $lines=null, $y2lines=null;
  702. var $bands=null, $y2bands=null;
  703. var $text_scale_off=0, $text_scale_abscenteroff=-1; // Text scale offset in fractions and for centering bars in absolute pixels
  704. var $background_image="",$background_image_type=-1,$background_image_format="png";
  705. var $background_image_bright=0,$background_image_contr=0,$background_image_sat=0;
  706. var $image_bright=0, $image_contr=0, $image_sat=0;
  707. var $inline;
  708. var $showcsim=0,$csimcolor="red"; //debug stuff, draw the csim boundaris on the image if <>0
  709. var $grid_depth=DEPTH_BACK; // Draw grid under all plots as default
  710. var $iAxisStyle = AXSTYLE_SIMPLE;
  711. var $iCSIMdisplay=false,$iHasStroked = false;
  712. var $footer;
  713. var $csimcachename = '', $csimcachetimeout = 0;
  714. var $iDoClipping = false;
  715. var $y2orderback=true;
  716. var $tabtitle;
  717. var $bkg_gradtype=-1,$bkg_gradstyle=BGRAD_MARGIN;
  718. var $bkg_gradfrom='navy', $bkg_gradto='silver';
  719. var $titlebackground = false;
  720. var $titlebackground_color = 'lightblue',
  721. $titlebackground_style = 1,
  722. $titlebackground_framecolor = 'blue',
  723. $titlebackground_framestyle = 2,
  724. $titlebackground_frameweight = 1,
  725. $titlebackground_bevelheight = 3 ;
  726. var $titlebkg_fillstyle=TITLEBKG_FILLSTYLE_SOLID;
  727. var $titlebkg_scolor1='black',$titlebkg_scolor2='white';
  728. var $framebevel = false, $framebeveldepth = 2 ;
  729. var $framebevelborder = false, $framebevelbordercolor='black';
  730. var $framebevelcolor1='white@0.4', $framebevelcolor2='black@0.4';
  731. var $background_image_mix=100;
  732. var $background_cflag = '';
  733. var $background_cflag_type = BGIMG_FILLPLOT;
  734. var $background_cflag_mix = 100;
  735. var $iImgTrans=false,
  736. $iImgTransHorizon = 100,$iImgTransSkewDist=150,
  737. $iImgTransDirection = 1, $iImgTransMinSize = true,
  738. $iImgTransFillColor='white',$iImgTransHighQ=false,
  739. $iImgTransBorder=false,$iImgTransHorizonPos=0.5;
  740. var $iYAxisDeltaPos=50;
  741. var $iIconDepth=DEPTH_BACK;
  742. var $iAxisLblBgType = 0,
  743. $iXAxisLblBgFillColor = 'lightgray', $iXAxisLblBgColor = 'black',
  744. $iYAxisLblBgFillColor = 'lightgray', $iYAxisLblBgColor = 'black';
  745. var $iTables=NULL;
  746. //---------------
  747. // CONSTRUCTOR
  748. // aWIdth Width in pixels of image
  749. // aHeight Height in pixels of image
  750. // aCachedName Name for image file in cache directory
  751. // aTimeOut Timeout in minutes for image in cache
  752. // aInline If true the image is streamed back in the call to Stroke()
  753. // If false the image is just created in the cache
  754. function Graph($aWidth=300,$aHeight=200,$aCachedName="",$aTimeOut=0,$aInline=true) {
  755. GLOBAL $gJpgBrandTiming;
  756. // If timing is used create a new timing object
  757. if( $gJpgBrandTiming ) {
  758. global $tim;
  759. $tim = new JpgTimer();
  760. $tim->Push();
  761. }
  762. if( !is_numeric($aWidth) || !is_numeric($aHeight) ) {
  763. JpGraphError::RaiseL(25008);//('Image width/height argument in Graph::Graph() must be numeric');
  764. }
  765. // Automatically generate the image file name based on the name of the script that
  766. // generates the graph
  767. if( $aCachedName=="auto" )
  768. $aCachedName=GenImgName();
  769. // Should the image be streamed back to the browser or only to the cache?
  770. $this->inline=$aInline;
  771. $this->img = new RotImage($aWidth,$aHeight);
  772. $this->cache = new ImgStreamCache($this->img);
  773. $this->cache->SetTimeOut($aTimeOut);
  774. $this->title = new Text();
  775. $this->title->ParagraphAlign('center');
  776. $this->title->SetFont(FF_FONT2,FS_BOLD);
  777. $this->title->SetMargin(3);
  778. $this->title->SetAlign('center');
  779. $this->subtitle = new Text();
  780. $this->subtitle->ParagraphAlign('center');
  781. $this->subtitle->SetMargin(2);
  782. $this->subtitle->SetAlign('center');
  783. $this->subsubtitle = new Text();
  784. $this->subsubtitle->ParagraphAlign('center');
  785. $this->subsubtitle->SetMargin(2);
  786. $this->subsubtitle->SetAlign('center');
  787. $this->legend = new Legend();
  788. $this->footer = new Footer();
  789. // Window doesn't like '?' in the file name so replace it with an '_'
  790. $aCachedName = str_replace("?","_",$aCachedName);
  791. // If the cached version exist just read it directly from the
  792. // cache, stream it back to browser and exit
  793. if( $aCachedName!="" && READ_CACHE && $aInline )
  794. if( $this->cache->GetAndStream($aCachedName) ) {
  795. exit();
  796. }
  797. $this->cache_name = $aCachedName;
  798. $this->SetTickDensity(); // Normal density
  799. $this->tabtitle = new GraphTabTitle();
  800. }
  801. //---------------
  802. // PUBLIC METHODS
  803. // Enable final image perspective transformation
  804. function Set3DPerspective($aDir=1,$aHorizon=100,$aSkewDist=120,$aQuality=false,$aFillColor='#FFFFFF',$aBorder=false,$aMinSize=true,$aHorizonPos=0.5) {
  805. $this->iImgTrans = true;
  806. $this->iImgTransHorizon = $aHorizon;
  807. $this->iImgTransSkewDist= $aSkewDist;
  808. $this->iImgTransDirection = $aDir;
  809. $this->iImgTransMinSize = $aMinSize;
  810. $this->iImgTransFillColor=$aFillColor;
  811. $this->iImgTransHighQ=$aQuality;
  812. $this->iImgTransBorder=$aBorder;
  813. $this->iImgTransHorizonPos=$aHorizonPos;
  814. }
  815. // Set Image format and optional quality
  816. function SetImgFormat($aFormat,$aQuality=75) {
  817. $this->img->SetImgFormat($aFormat,$aQuality);
  818. }
  819. // Should the grid be in front or back of the plot?
  820. function SetGridDepth($aDepth) {
  821. $this->grid_depth=$aDepth;
  822. }
  823. function SetIconDepth($aDepth) {
  824. $this->iIconDepth=$aDepth;
  825. }
  826. // Specify graph angle 0-360 degrees.
  827. function SetAngle($aAngle) {
  828. $this->img->SetAngle($aAngle);
  829. }
  830. function SetAlphaBlending($aFlg=true) {
  831. $this->img->SetAlphaBlending($aFlg);
  832. }
  833. // Shortcut to image margin
  834. function SetMargin($lm,$rm,$tm,$bm) {
  835. $this->img->SetMargin($lm,$rm,$tm,$bm);
  836. }
  837. function SetY2OrderBack($aBack=true) {
  838. $this->y2orderback = $aBack;
  839. }
  840. // Rotate the graph 90 degrees and set the margin
  841. // when we have done a 90 degree rotation
  842. function Set90AndMargin($lm=0,$rm=0,$tm=0,$bm=0) {
  843. $lm = $lm ==0 ? floor(0.2 * $this->img->width) : $lm ;
  844. $rm = $rm ==0 ? floor(0.1 * $this->img->width) : $rm ;
  845. $tm = $tm ==0 ? floor(0.2 * $this->img->height) : $tm ;
  846. $bm = $bm ==0 ? floor(0.1 * $this->img->height) : $bm ;
  847. $adj = ($this->img->height - $this->img->width)/2;
  848. $this->img->SetMargin($tm-$adj,$bm-$adj,$rm+$adj,$lm+$adj);
  849. $this->img->SetCenter(floor($this->img->width/2),floor($this->img->height/2));
  850. $this->SetAngle(90);
  851. if( empty($this->yaxis) || empty($this->xaxis) ) {
  852. JpgraphError::RaiseL(25009);//('You must specify what scale to use with a call to Graph::SetScale()');
  853. }
  854. $this->xaxis->SetLabelAlign('right','center');
  855. $this->yaxis->SetLabelAlign('center','bottom');
  856. }
  857. function SetClipping($aFlg=true) {
  858. $this->iDoClipping = $aFlg ;
  859. }
  860. // Add a plot object to the graph
  861. function Add(&$aPlot) {
  862. if( $aPlot == null )
  863. JpGraphError::RaiseL(25010);//("Graph::Add() You tried to add a null plot to the graph.");
  864. if( is_array($aPlot) && count($aPlot) > 0 )
  865. $cl = $aPlot[0];
  866. else
  867. $cl = $aPlot;
  868. if( is_a($cl,'Text') )
  869. $this->AddText($aPlot);
  870. elseif( is_a($cl,'PlotLine') )
  871. $this->AddLine($aPlot);
  872. elseif( is_a($cl,'PlotBand') )
  873. $this->AddBand($aPlot);
  874. elseif( is_a($cl,'IconPlot') )
  875. $this->AddIcon($aPlot);
  876. elseif( is_a($cl,'GTextTable') )
  877. $this->AddTable($aPlot);
  878. else
  879. $this->plots[] = &$aPlot;
  880. }
  881. function AddTable(&$aTable) {
  882. if( is_array($aTable) ) {
  883. for($i=0; $i < count($aTable); ++$i )
  884. $this->iTables[]=&$aTable[$i];
  885. }
  886. else {
  887. $this->iTables[] = &$aTable ;
  888. }
  889. }
  890. function AddIcon(&$aIcon) {
  891. if( is_array($aIcon) ) {
  892. for($i=0; $i < count($aIcon); ++$i )
  893. $this->iIcons[]=&$aIcon[$i];
  894. }
  895. else {
  896. $this->iIcons[] = &$aIcon ;
  897. }
  898. }
  899. // Add plot to second Y-scale
  900. function AddY2(&$aPlot) {
  901. if( $aPlot == null )
  902. JpGraphError::RaiseL(25011);//("Graph::AddY2() You tried to add a null plot to the graph.");
  903. if( is_array($aPlot) && count($aPlot) > 0 )
  904. $cl = $aPlot[0];
  905. else
  906. $cl = $aPlot;
  907. if( is_a($cl,'Text') )
  908. $this->AddText($aPlot,true);
  909. elseif( is_a($cl,'PlotLine') )
  910. $this->AddLine($aPlot,true);
  911. elseif( is_a($cl,'PlotBand') )
  912. $this->AddBand($aPlot,true);
  913. else
  914. $this->y2plots[] = &$aPlot;
  915. }
  916. // Add plot to second Y-scale
  917. function AddY($aN,&$aPlot) {
  918. if( $aPlot == null )
  919. JpGraphError::RaiseL(25012);//("Graph::AddYN() You tried to add a null plot to the graph.");
  920. if( is_array($aPlot) && count($aPlot) > 0 )
  921. $cl = $aPlot[0];
  922. else
  923. $cl = $aPlot;
  924. if( is_a($cl,'Text') || is_a($cl,'PlotLine') || is_a($cl,'PlotBand') )
  925. JpGraph::RaiseL(25013);//('You can only add standard plots to multiple Y-axis');
  926. else
  927. $this->ynplots[$aN][] = &$aPlot;
  928. }
  929. // Add text object to the graph
  930. function AddText(&$aTxt,$aToY2=false) {
  931. if( $aTxt == null )
  932. JpGraphError::RaiseL(25014);//("Graph::AddText() You tried to add a null text to the graph.");
  933. if( $aToY2 ) {
  934. if( is_array($aTxt) ) {
  935. for($i=0; $i < count($aTxt); ++$i )
  936. $this->y2texts[]=&$aTxt[$i];
  937. }
  938. else
  939. $this->y2texts[] = &$aTxt;
  940. }
  941. else {
  942. if( is_array($aTxt) ) {
  943. for($i=0; $i < count($aTxt); ++$i )
  944. $this->texts[]=&$aTxt[$i];
  945. }
  946. else
  947. $this->texts[] = &$aTxt;
  948. }
  949. }
  950. // Add a line object (class PlotLine) to the graph
  951. function AddLine(&$aLine,$aToY2=false) {
  952. if( $aLine == null )
  953. JpGraphError::RaiseL(25015);//("Graph::AddLine() You tried to add a null line to the graph.");
  954. if( $aToY2 ) {
  955. if( is_array($aLine) ) {
  956. for($i=0; $i < count($aLine); ++$i )
  957. $this->y2lines[]=&$aLine[$i];
  958. }
  959. else
  960. $this->y2lines[] = &$aLine;
  961. }
  962. else {
  963. if( is_array($aLine) ) {
  964. for($i=0; $i < count($aLine); ++$i )
  965. $this->lines[]=&$aLine[$i];
  966. }
  967. else
  968. $this->lines[] = &$aLine;
  969. }
  970. }
  971. // Add vertical or horizontal band
  972. function AddBand(&$aBand,$aToY2=false) {
  973. if( $aBand == null )
  974. JpGraphError::RaiseL(25016);//(" Graph::AddBand() You tried to add a null band to the graph.");
  975. if( $aToY2 ) {
  976. if( is_array($aBand) ) {
  977. for($i=0; $i < count($aBand); ++$i )
  978. $this->y2bands[] = &$aBand[$i];
  979. }
  980. else
  981. $this->y2bands[] = &$aBand;
  982. }
  983. else {
  984. if( is_array($aBand) ) {
  985. for($i=0; $i < count($aBand); ++$i )
  986. $this->bands[] = &$aBand[$i];
  987. }
  988. else
  989. $this->bands[] = &$aBand;
  990. }
  991. }
  992. function SetBackgroundGradient($aFrom='navy',$aTo='silver',$aGradType=2,$aStyle=BGRAD_FRAME) {
  993. $this->bkg_gradtype=$aGradType;
  994. $this->bkg_gradstyle=$aStyle;
  995. $this->bkg_gradfrom = $aFrom;
  996. $this->bkg_gradto = $aTo;
  997. }
  998. // Set a country flag in the background
  999. function SetBackgroundCFlag($aName,$aBgType=BGIMG_FILLPLOT,$aMix=100) {
  1000. $this->background_cflag = $aName;
  1001. $this->background_cflag_type = $aBgType;
  1002. $this->background_cflag_mix = $aMix;
  1003. }
  1004. // Alias for the above method
  1005. function SetBackgroundCountryFlag($aName,$aBgType=BGIMG_FILLPLOT,$aMix=100) {
  1006. $this->background_cflag = $aName;
  1007. $this->background_cflag_type = $aBgType;
  1008. $this->background_cflag_mix = $aMix;
  1009. }
  1010. // Specify a background image
  1011. function SetBackgroundImage($aFileName,$aBgType=BGIMG_FILLPLOT,$aImgFormat="auto") {
  1012. if( $GLOBALS['gd2'] && !USE_TRUECOLOR ) {
  1013. JpGraphError::RaiseL(25017);//("You are using GD 2.x and are trying to use a background images on a non truecolor image. To use background images with GD 2.x you <b>must</b> enable truecolor by setting the USE_TRUECOLOR constant to TRUE. Due to a bug in GD 2.0.1 using any truetype fonts with truecolor images will result in very poor quality fonts.");
  1014. }
  1015. // Get extension to determine image type
  1016. if( $aImgFormat == "auto" ) {
  1017. $e = explode('.',$aFileName);
  1018. if( !$e ) {
  1019. JpGraphError::RaiseL(25018,$aFileName);//('Incorrect file name for Graph::SetBackgroundImage() : '.$aFileName.' Must have a valid image extension (jpg,gif,png) when using autodetection of image type');
  1020. }
  1021. $valid_formats = array('png', 'jpg', 'gif');
  1022. $aImgFormat = strtolower($e[count($e)-1]);
  1023. if ($aImgFormat == 'jpeg') {
  1024. $aImgFormat = 'jpg';
  1025. }
  1026. elseif (!in_array($aImgFormat, $valid_formats) ) {
  1027. JpGraphError::RaiseL(25019,$aImgFormat);//('Unknown file extension ($aImgFormat) in Graph::SetBackgroundImage() for filename: '.$aFileName);
  1028. }
  1029. }
  1030. $this->background_image = $aFileName;
  1031. $this->background_image_type=$aBgType;
  1032. $this->background_image_format=$aImgFormat;
  1033. }
  1034. function SetBackgroundImageMix($aMix) {
  1035. $this->background_image_mix = $aMix ;
  1036. }
  1037. // Adjust brightness and constrast for background image
  1038. function AdjBackgroundImage($aBright,$aContr=0,$aSat=0) {
  1039. $this->background_image_bright=$aBright;
  1040. $this->background_image_contr=$aContr;
  1041. $this->background_image_sat=$aSat;
  1042. }
  1043. // Adjust brightness and constrast for image
  1044. function AdjImage($aBright,$aContr=0,$aSat=0) {
  1045. $this->image_bright=$aBright;
  1046. $this->image_contr=$aContr;
  1047. $this->image_sat=$aSat;
  1048. }
  1049. // Specify axis style (boxed or single)
  1050. function SetAxisStyle($aStyle) {
  1051. $this->iAxisStyle = $aStyle ;
  1052. }
  1053. // Set a frame around the plot area
  1054. function SetBox($aDrawPlotFrame=true,$aPlotFrameColor=array(0,0,0),$aPlotFrameWeight=1) {
  1055. $this->boxed = $aDrawPlotFrame;
  1056. $this->box_weight = $aPlotFrameWeight;
  1057. $this->box_color = $aPlotFrameColor;
  1058. }
  1059. // Specify color for the plotarea (not the margins)
  1060. function SetColor($aColor) {
  1061. $this->plotarea_color=$aColor;
  1062. }
  1063. // Specify color for the margins (all areas outside the plotarea)
  1064. function SetMarginColor($aColor) {
  1065. $this->margin_color=$aColor;
  1066. }
  1067. // Set a frame around the entire image
  1068. function SetFrame($aDrawImgFrame=true,$aImgFrameColor=array(0,0,0),$aImgFrameWeight=1) {
  1069. $this->doframe = $aDrawImgFrame;
  1070. $this->frame_color = $aImgFrameColor;
  1071. $this->frame_weight = $aImgFrameWeight;
  1072. }
  1073. function SetFrameBevel($aDepth=3,$aBorder=false,$aBorderColor='black',$aColor1='white@0.4',$aColor2='darkgray@0.4',$aFlg=true) {
  1074. $this->framebevel = $aFlg ;
  1075. $this->framebeveldepth = $aDepth ;
  1076. $this->framebevelborder = $aBorder ;
  1077. $this->framebevelbordercolor = $aBorderColor ;
  1078. $this->framebevelcolor1 = $aColor1 ;
  1079. $this->framebevelcolor2 = $aColor2 ;
  1080. $this->doshadow = false ;
  1081. }
  1082. // Set the shadow around the whole image
  1083. function SetShadow($aShowShadow=true,$aShadowWidth=5,$aShadowColor=array(102,102,102)) {
  1084. $this->doshadow = $aShowShadow;
  1085. $this->shadow_color = $aShadowColor;
  1086. $this->shadow_width = $aShadowWidth;
  1087. $this->footer->iBottomMargin += $aShadowWidth;
  1088. $this->footer->iRightMargin += $aShadowWidth;
  1089. }
  1090. // Specify x,y scale. Note that if you manually specify the scale
  1091. // you must also specify the tick distance with a call to Ticks::Set()
  1092. function SetScale($aAxisType,$aYMin=1,$aYMax=1,$aXMin=1,$aXMax=1) {
  1093. $this->axtype = $aAxisType;
  1094. if( $aYMax < $aYMin || $aXMax < $aXMin )
  1095. JpGraphError::RaiseL(25020);//('Graph::SetScale(): Specified Max value must be larger than the specified Min value.');
  1096. $yt=substr($aAxisType,-3,3);
  1097. if( $yt=="lin" )
  1098. $this->yscale = new LinearScale($aYMin,$aYMax);
  1099. elseif( $yt == "int" ) {
  1100. $this->yscale = new LinearScale($aYMin,$aYMax);
  1101. $this->yscale->SetIntScale();
  1102. }
  1103. elseif( $yt=="log" )
  1104. $this->yscale = new LogScale($aYMin,$aYMax);
  1105. else
  1106. JpGraphError::RaiseL(25021,$aAxisType);//("Unknown scale specification for Y-scale. ($aAxisType)");
  1107. $xt=substr($aAxisType,0,3);
  1108. if( $xt == "lin" || $xt == "tex" ) {
  1109. $this->xscale = new LinearScale($aXMin,$aXMax,"x");
  1110. $this->xscale->textscale = ($xt == "tex");
  1111. }
  1112. elseif( $xt == "int" ) {
  1113. $this->xscale = new LinearScale($aXMin,$aXMax,"x");
  1114. $this->xscale->SetIntScale();
  1115. }
  1116. elseif( $xt == "dat" ) {
  1117. $this->xscale = new DateScale($aXMin,$aXMax,"x");
  1118. }
  1119. elseif( $xt == "log" )
  1120. $this->xscale = new LogScale($aXMin,$aXMax,"x");
  1121. else
  1122. JpGraphError::RaiseL(25022,$aAxisType);//(" Unknown scale specification for X-scale. ($aAxisType)");
  1123. $this->xaxis = new Axis($this->img,$this->xscale);
  1124. $this->yaxis = new Axis($this->img,$this->yscale);
  1125. $this->xgrid = new Grid($this->xaxis);
  1126. $this->ygrid = new Grid($this->yaxis);
  1127. $this->ygrid->Show();
  1128. }
  1129. // Specify secondary Y scale
  1130. function SetY2Scale($aAxisType="lin",$aY2Min=1,$aY2Max=1) {
  1131. if( $aAxisType=="lin" )
  1132. $this->y2scale = new LinearScale($aY2Min,$aY2Max);
  1133. elseif( $aAxisType == "int" ) {
  1134. $this->y2scale = new LinearScale($aY2Min,$aY2Max);
  1135. $this->y2scale->SetIntScale();
  1136. }
  1137. elseif( $aAxisType=="log" ) {
  1138. $this->y2scale = new LogScale($aY2Min,$aY2Max);
  1139. }
  1140. else JpGraphError::RaiseL(25023,$aAxisType);//("JpGraph: Unsupported Y2 axis type: $aAxisType\nMust be one of (lin,log,int)");
  1141. $this->y2axis = new Axis($this->img,$this->y2scale);
  1142. $this->y2axis->scale->ticks->SetDirection(SIDE_LEFT);
  1143. $this->y2axis->SetLabelSide(SIDE_RIGHT);
  1144. $this->y2axis->SetPos('max');
  1145. $this->y2axis->SetTitleSide(SIDE_RIGHT);
  1146. // Deafult position is the max x-value
  1147. $this->y2grid = new Grid($this->y2axis);
  1148. }
  1149. // Set the delta position (in pixels) between the multiple Y-axis
  1150. function SetYDeltaDist($aDist) {
  1151. $this->iYAxisDeltaPos = $aDist;
  1152. }
  1153. // Specify secondary Y scale
  1154. function SetYScale($aN,$aAxisType="lin",$aYMin=1,$aYMax=1) {
  1155. if( $aAxisType=="lin" )
  1156. $this->ynscale[$aN] = new LinearScale($aYMin,$aYMax);
  1157. elseif( $aAxisType == "int" ) {
  1158. $this->ynscale[$aN] = new LinearScale($aYMin,$aYMax);
  1159. $this->ynscale[$aN]->SetIntScale();
  1160. }
  1161. elseif( $aAxisType=="log" ) {
  1162. $this->ynscale[$aN] = new LogScale($aYMin,$aYMax);
  1163. }
  1164. else JpGraphError::RaiseL(25024,$aAxisType);//("JpGraph: Unsupported Y axis type: $aAxisType\nMust be one of (lin,log,int)");
  1165. $this->ynaxis[$aN] = new Axis($this->img,$this->ynscale[$aN]);
  1166. $this->ynaxis[$aN]->scale->ticks->SetDirection(SIDE_LEFT);
  1167. $this->ynaxis[$aN]->SetLabelSide(SIDE_RIGHT);
  1168. }
  1169. // Specify density of ticks when autoscaling 'normal', 'dense', 'sparse', 'verysparse'
  1170. // The dividing factor have been determined heuristically according to my aesthetic
  1171. // sense (or lack off) y.m.m.v !
  1172. function SetTickDensity($aYDensity=TICKD_NORMAL,$aXDensity=TICKD_NORMAL) {
  1173. $this->xtick_factor=30;
  1174. $this->ytick_factor=25;
  1175. switch( $aYDensity ) {
  1176. case TICKD_DENSE:
  1177. $this->ytick_factor=12;
  1178. break;
  1179. case TICKD_NORMAL:
  1180. $this->ytick_factor=25;
  1181. break;
  1182. case TICKD_SPARSE:
  1183. $this->ytick_factor=40;
  1184. break;
  1185. case TICKD_VERYSPARSE:
  1186. $this->ytick_factor=100;
  1187. break;
  1188. default:
  1189. JpGraphError::RaiseL(25025,$densy);//("JpGraph: Unsupported Tick density: $densy");
  1190. }
  1191. switch( $aXDensity ) {
  1192. case TICKD_DENSE:
  1193. $this->xtick_factor=15;
  1194. break;
  1195. case TICKD_NORMAL:
  1196. $this->xtick_factor=30;
  1197. break;
  1198. case TICKD_SPARSE:
  1199. $this->xtick_factor=45;
  1200. break;
  1201. case TICKD_VERYSPARSE:
  1202. $this->xtick_factor=60;
  1203. break;
  1204. default:
  1205. JpGraphError::RaiseL(25025,$densx);//("JpGraph: Unsupported Tick density: $densx");
  1206. }
  1207. }
  1208. // Get a string of all image map areas
  1209. function GetCSIMareas() {
  1210. if( !$this->iHasStroked )
  1211. $this->Stroke(_CSIM_SPECIALFILE);
  1212. $csim = $this->title->GetCSIMAreas();
  1213. $csim .= $this->subtitle->GetCSIMAreas();
  1214. $csim .= $this->subsubtitle->GetCSIMAreas();
  1215. $csim .= $this->legend->GetCSIMAreas();
  1216. if( $this->y2axis != NULL ) {
  1217. $csim .= $this->y2axis->title->GetCSIMAreas();
  1218. }
  1219. if( $this->texts != null ) {
  1220. $n = count($this->texts);
  1221. for($i=0; $i < $n; ++$i ) {
  1222. $csim .= $this->texts[$i]->GetCSIMAreas();
  1223. }
  1224. }
  1225. if( $this->y2texts != null && $this->y2scale != null ) {
  1226. $n = count($this->y2texts);
  1227. for($i=0; $i < $n; ++$i ) {
  1228. $csim .= $this->y2texts[$i]->GetCSIMAreas();
  1229. }
  1230. }
  1231. if( $this->yaxis != null && $this->xaxis != null ) {
  1232. $csim .= $this->yaxis->title->GetCSIMAreas();
  1233. $csim .= $this->xaxis->title->GetCSIMAreas();
  1234. }
  1235. $n = count($this->plots);
  1236. for( $i=0; $i < $n; ++$i )
  1237. $csim .= $this->plots[$i]->GetCSIMareas();
  1238. $n = count($this->y2plots);
  1239. for( $i=0; $i < $n; ++$i )
  1240. $csim .= $this->y2plots[$i]->GetCSIMareas();
  1241. $n = count($this->ynaxis);
  1242. for( $i=0; $i < $n; ++$i ) {
  1243. $m = count($this->ynplots[$i]);
  1244. for($j=0; $j < $m; ++$j ) {
  1245. $csim .= $this->ynplots[$i][$j]->GetCSIMareas();
  1246. }
  1247. }
  1248. $n = count($this->iTables);
  1249. for( $i=0; $i < $n; ++$i ) {
  1250. $csim .= $this->iTables[$i]->GetCSIMareas();
  1251. }
  1252. return $csim;
  1253. }
  1254. // Get a complete <MAP>..</MAP> tag for the final image map
  1255. function GetHTMLImageMap($aMapName) {
  1256. //$im = "<map name=\"$aMapName\" id=\"$aMapName\">\n";
  1257. $im = "<map name=\"$aMapName\" />\n";
  1258. $im .= $this->GetCSIMareas();
  1259. $im .= "</map>";
  1260. return $im;
  1261. }
  1262. function CheckCSIMCache($aCacheName,$aTimeOut=60) {
  1263. global $_SERVER;
  1264. if( $aCacheName=='auto' )
  1265. $aCacheName=basename($_SERVER['PHP_SELF']);
  1266. $this->csimcachename = CSIMCACHE_DIR.$aCacheName;
  1267. $this->csimcachetimeout = $aTimeOut;
  1268. // First determine if we need to check for a cached version
  1269. // This differs from the standard cache in the sense that the
  1270. // image and CSIM map HTML file is written relative to the directory
  1271. // the script executes in and not the specified cache directory.
  1272. // The reason for this is that the cache directory is not necessarily
  1273. // accessible from the HTTP server.
  1274. if( $this->csimcachename != '' ) {
  1275. $dir = dirname($this->csimcachename);
  1276. $base = basename($this->csimcachename);
  1277. $base = strtok($base,'.');
  1278. $suffix = strtok('.');
  1279. $basecsim = $dir.'/'.$base.'_csim_.html';
  1280. $baseimg = $dir.'/'.$base.'.'.$this->img->img_format;
  1281. $timedout=false;
  1282. // Does it exist at all ?
  1283. if( file_exists($basecsim) && file_exists($baseimg) ) {
  1284. // Check that it hasn't timed out
  1285. $diff=time()-filemtime($basecsim);
  1286. if( $this->csimcachetimeout>0 && ($diff > $this->csimcachetimeout*60) ) {
  1287. $timedout=true;
  1288. @unlink($basecsim);
  1289. @unlink($baseimg);
  1290. }
  1291. else {
  1292. if ($fh = @fopen($basecsim, "r")) {
  1293. fpassthru($fh);
  1294. return true;
  1295. }
  1296. else
  1297. JpGraphError::RaiseL(25027,$basecsim);//(" Can't open cached CSIM \"$basecsim\" for reading.");
  1298. }
  1299. }
  1300. }
  1301. return false;
  1302. }
  1303. function StrokeCSIM($aScriptName='auto',$aCSIMName='',$aBorder=0) {
  1304. if( $aCSIMName=='' ) {
  1305. // create a random map name
  1306. srand ((double) microtime() * 1000000);
  1307. $r = rand(0,100000);
  1308. $aCSIMName='__mapname'.$r.'__';
  1309. }
  1310. if( $aScriptName=='auto' )
  1311. $aScriptName=basename($_SERVER['PHP_SELF']);
  1312. if( empty($_GET[_CSIM_DISPLAY]) ) {
  1313. // First determine if we need to check for a cached version
  1314. // This differs from the standard cache in the sense that the
  1315. // image and CSIM map HTML file is written relative to the directory
  1316. // the script executes in and not the specified cache directory.
  1317. // The reason for this is that the cache directory is not necessarily
  1318. // accessible from the HTTP server.
  1319. if( $this->csimcachename != '' ) {
  1320. $dir = dirname($this->csimcachename);
  1321. $base = basename($this->csimcachename);
  1322. $base = strtok($base,'.');
  1323. $suffix = strtok('.');
  1324. $basecsim = $dir.'/'.$base.'_csim_.html';
  1325. $baseimg = $base.'.'.$this->img->img_format;
  1326. // Check that apache can write to directory specified
  1327. if( file_exists($dir) && !is_writeable($dir) ) {
  1328. JpgraphError::RaiseL(25028,$dir);//('Apache/PHP does not have permission to write to the CSIM cache directory ('.$dir.'). Check permissions.');
  1329. }
  1330. // Make sure directory exists
  1331. $this->cache->MakeDirs($dir);
  1332. // Write the image file
  1333. $this->Stroke(CSIMCACHE_DIR.$baseimg);
  1334. // Construct wrapper HTML and write to file and send it back to browser
  1335. $htmlwrap = $this->GetHTMLImageMap($aCSIMName)."\n".
  1336. '<img src="'.htmlentities(CSIMCACHE_HTTP_DIR.$baseimg).'" ismap usemap="#'.$aCSIMName.'" border='.$aBorder.' width='.$this->img->width.' height='.$this->img->height." alt=\"\" />\n";
  1337. if($fh = @fopen($basecsim,'w') ) {
  1338. fwrite($fh,$htmlwrap);
  1339. fclose($fh);
  1340. echo $htmlwrap;
  1341. }
  1342. else
  1343. JpGraphError::RaiseL(25029,$basecsim);//(" Can't write CSIM \"$basecsim\" for writing. Check free space and permissions.");
  1344. }
  1345. else {
  1346. if( $aScriptName=='' ) {
  1347. JpGraphError::RaiseL(25030);//('Missing script name in call to StrokeCSIM(). You must specify the name of the actual image script as the first parameter to StrokeCSIM().');
  1348. exit();
  1349. }
  1350. // This is a JPGRAPH internal defined that prevents
  1351. // us from recursively coming here again
  1352. $urlarg='?'._CSIM_DISPLAY.'=1';
  1353. // Now reconstruct any user URL argument
  1354. reset($_GET);
  1355. while( list($key,$value) = each($_GET) ) {
  1356. if( is_array($value) ) {
  1357. $n = count($value);
  1358. for( $i=0; $i < $n; ++$i ) {
  1359. $urlarg .= '&'.$key.'%5B%5D='.urlencode($value[$i]);
  1360. }
  1361. }
  1362. else {
  1363. $urlarg .= '&'.$key.'='.urlencode($value);
  1364. }
  1365. }
  1366. // It's not ideal to convert POST argument to GET arguments
  1367. // but there is little else we can do. One idea for the
  1368. // future might be recreate the POST header in case.
  1369. reset($_POST);
  1370. while( list($key,$value) = each($_POST) ) {
  1371. if( is_array($value) ) {
  1372. $n = count($value);
  1373. for( $i=0; $i < $n; ++$i ) {
  1374. $urlarg .= '&'.$key.'%5B%5D='.urlencode($value[$i]);
  1375. }
  1376. }
  1377. else {
  1378. $urlarg .= '&'.$key.'='.urlencode($value);
  1379. }
  1380. }
  1381. echo $this->GetHTMLImageMap($aCSIMName);
  1382. echo "<img src='".htmlentities($aScriptName.$urlarg)."' ismap usemap='#".$aCSIMName.'\' border='.$aBorder.' width='.$this->img->width.' height='.$this->img->height." alt=\"\" />\n";
  1383. }
  1384. }
  1385. else {
  1386. $this->Stroke();
  1387. }
  1388. }
  1389. function GetTextsYMinMax($aY2=false) {
  1390. if( $aY2 )
  1391. $txts = $this->y2texts;
  1392. else
  1393. $txts = $this->texts;
  1394. $n = count($txts);
  1395. $min=null;
  1396. $max=null;
  1397. for( $i=0; $i < $n; ++$i ) {
  1398. if( $txts[$i]->iScalePosY !== null &&
  1399. $txts[$i]->iScalePosX !== null ) {
  1400. if( $min === null ) {
  1401. $min = $max = $txts[$i]->iScalePosY ;
  1402. }
  1403. else {
  1404. $min = min($min,$txts[$i]->iScalePosY);
  1405. $max = max($max,$txts[$i]->iScalePosY);
  1406. }
  1407. }
  1408. }
  1409. if( $min !== null ) {
  1410. return array($min,$max);
  1411. }
  1412. else
  1413. return null;
  1414. }
  1415. function GetTextsXMinMax($aY2=false) {
  1416. if( $aY2 )
  1417. $txts = $this->y2texts;
  1418. else
  1419. $txts = $this->texts;
  1420. $n = count($txts);
  1421. $min=null;
  1422. $max=null;
  1423. for( $i=0; $i < $n; ++$i ) {
  1424. if( $txts[$i]->iScalePosY !== null &&
  1425. $txts[$i]->iScalePosX !== null ) {
  1426. if( $min === null ) {
  1427. $min = $max = $txts[$i]->iScalePosX ;
  1428. }
  1429. else {
  1430. $min = min($min,$txts[$i]->iScalePosX);
  1431. $max = max($max,$txts[$i]->iScalePosX);
  1432. }
  1433. }
  1434. }
  1435. if( $min !== null ) {
  1436. return array($min,$max);
  1437. }
  1438. else
  1439. return null;
  1440. }
  1441. function GetXMinMax() {
  1442. list($min,$ymin) = $this->plots[0]->Min();
  1443. list($max,$ymax) = $this->plots[0]->Max();
  1444. foreach( $this->plots as $p ) {
  1445. list($xmin,$ymin) = $p->Min();
  1446. list($xmax,$ymax) = $p->Max();
  1447. $min = Min($xmin,$min);
  1448. $max = Max($xmax,$max);
  1449. }
  1450. if( $this->y2axis != null ) {
  1451. foreach( $this->y2plots as $p ) {
  1452. list($xmin,$ymin) = $p->Min();
  1453. list($xmax,$ymax) = $p->Max();
  1454. $min = Min($xmin,$min);
  1455. $max = Max($xmax,$max);
  1456. }
  1457. }
  1458. $n = count($this->ynaxis);
  1459. for( $i=0; $i < $n; ++$i ) {
  1460. if( $this->ynaxis[$i] != null) {
  1461. foreach( $this->ynplots[$i] as $p ) {
  1462. list($xmin,$ymin) = $p->Min();
  1463. list($xmax,$ymax) = $p->Max();
  1464. $min = Min($xmin,$min);
  1465. $max = Max($xmax,$max);
  1466. }
  1467. }
  1468. }
  1469. return array($min,$max);
  1470. }
  1471. function AdjustMarginsForTitles() {
  1472. $totrequired =
  1473. ($this->title->t != '' ?
  1474. $this->title->GetTextHeight($this->img) + $this->title->margin + 5 : 0 ) +
  1475. ($this->subtitle->t != '' ?
  1476. $this->subtitle->GetTextHeight($this->img) + $this->subtitle->margin + 5 : 0 ) +
  1477. ($this->subsubtitle->t != '' ?
  1478. $this->subsubtitle->GetTextHeight($this->img) + $this->subsubtitle->margin + 5 : 0 ) ;
  1479. $btotrequired = 0;
  1480. if($this->xaxis != null && !$this->xaxis->hide && !$this->xaxis->hide_labels ) {
  1481. // Minimum bottom margin
  1482. if( $this->xaxis->title->t != '' ) {
  1483. if( $this->img->a == 90 )
  1484. $btotrequired = $this->yaxis->title->GetTextHeight($this->img) + 5 ;
  1485. else
  1486. $btotrequired = $this->xaxis->title->GetTextHeight($this->img) + 5 ;
  1487. }
  1488. else
  1489. $btotrequired = 0;
  1490. if( $this->img->a == 90 ) {
  1491. $this->img->SetFont($this->yaxis->font_family,$this->yaxis->font_style,
  1492. $this->yaxis->font_size);
  1493. $lh = $this->img->GetTextHeight('Mg',$this->yaxis->label_angle);
  1494. }
  1495. else {
  1496. $this->img->SetFont($this->xaxis->font_family,$this->xaxis->font_style,
  1497. $this->xaxis->font_size);
  1498. $lh = $this->img->GetTextHeight('Mg',$this->xaxis->label_angle);
  1499. }
  1500. $btotrequired += $lh + 5;
  1501. }
  1502. if( $this->img->a == 90 ) {
  1503. // DO Nothing. It gets too messy to do this properly for 90 deg...
  1504. }
  1505. else{
  1506. if( $this->img->top_margin < $totrequired ) {
  1507. $this->SetMargin($this->img->left_margin,$this->img->right_margin,
  1508. $totrequired,$this->img->bottom_margin);
  1509. }
  1510. if( $this->img->bottom_margin < $btotrequired ) {
  1511. $this->SetMargin($this->img->left_margin,$this->img->right_margin,
  1512. $this->img->top_margin,$btotrequired);
  1513. }
  1514. }
  1515. }
  1516. // Stroke the graph
  1517. // $aStrokeFileName If != "" the image will be written to this file and NOT
  1518. // streamed back to the browser
  1519. function Stroke($aStrokeFileName="") {
  1520. // Fist make a sanity check that user has specified a scale
  1521. if( empty($this->yscale) ) {
  1522. JpGraphError::RaiseL(25031);//('You must specify what scale to use with a call to Graph::SetScale().');
  1523. }
  1524. // Start by adjusting the margin so that potential titles will fit.
  1525. $this->AdjustMarginsForTitles();
  1526. // Setup scale constants
  1527. if( $this->yscale ) $this->yscale->InitConstants($this->img);
  1528. if( $this->xscale ) $this->xscale->InitConstants($this->img);
  1529. if( $this->y2scale ) $this->y2scale->InitConstants($this->img);
  1530. $n=count($this->ynscale);
  1531. for($i=0; $i < $n; ++$i) {
  1532. if( $this->ynscale[$i] ) $this->ynscale[$i]->InitConstants($this->img);
  1533. }
  1534. // If the filename is the predefined value = '_csim_special_'
  1535. // we assume that the call to stroke only needs to do enough
  1536. // to correctly generate the CSIM maps.
  1537. // We use this variable to skip things we don't strictly need
  1538. // to do to generate the image map to improve performance
  1539. // a best we can. Therefor you will see a lot of tests !$_csim in the
  1540. // code below.
  1541. $_csim = ($aStrokeFileName===_CSIM_SPECIALFILE);
  1542. // We need to know if we have stroked the plot in the
  1543. // GetCSIMareas. Otherwise the CSIM hasn't been generated
  1544. // and in the case of GetCSIM called before stroke to generate
  1545. // CSIM without storing an image to disk GetCSIM must call Stroke.
  1546. $this->iHasStroked = true;
  1547. // Do any pre-stroke adjustment that is needed by the different plot types
  1548. // (i.e bar plots want's to add an offset to the x-labels etc)
  1549. for($i=0; $i < count($this->plots) ; ++$i ) {
  1550. $this->plots[$i]->PreStrokeAdjust($this);
  1551. $this->plots[$i]->DoLegend($this);
  1552. }
  1553. // Any plots on the second Y scale?
  1554. if( $this->y2scale != null ) {
  1555. for($i=0; $i<count($this->y2plots) ; ++$i ) {
  1556. $this->y2plots[$i]->PreStrokeAdjust($this);
  1557. $this->y2plots[$i]->DoLegend($this);
  1558. }
  1559. }
  1560. // Any plots on the extra Y axises?
  1561. $n = count($this->ynaxis);
  1562. for($i=0; $i<$n ; ++$i ) {
  1563. if( $this->ynplots == null || $this->ynplots[$i] == null) {
  1564. JpGraphError::RaiseL(25032,$i);//("No plots for Y-axis nbr:$i");
  1565. }
  1566. $m = count($this->ynplots[$i]);
  1567. for($j=0; $j < $m; ++$j ) {
  1568. $this->ynplots[$i][$j]->PreStrokeAdjust($this);
  1569. $this->ynplots[$i][$j]->DoLegend($this);
  1570. }
  1571. }
  1572. // Bail out if any of the Y-axis not been specified and
  1573. // has no plots. (This means it is impossible to do autoscaling and
  1574. // no other scale was given so we can't possible draw anything). If you use manual
  1575. // scaling you also have to supply the tick steps as well.
  1576. if( (!$this->yscale->IsSpecified() && count($this->plots)==0) ||
  1577. ($this->y2scale!=null && !$this->y2scale->IsSpecified() && count($this->y2plots)==0) ) {
  1578. //$e = "n=".count($this->y2plots)."\n";
  1579. // $e = "Can't draw unspecified Y-scale.<br>\nYou have either:<br>\n";
  1580. // $e .= "1. Specified an Y axis for autoscaling but have not supplied any plots<br>\n";
  1581. // $e .= "2. Specified a scale manually but have forgot to specify the tick steps";
  1582. JpGraphError::RaiseL(25026);
  1583. }
  1584. // Bail out if no plots and no specified X-scale
  1585. if( (!$this->xscale->IsSpecified() && count($this->plots)==0 && count($this->y2plots)==0) )
  1586. JpGraphError::RaiseL(25034);//("<strong>JpGraph: Can't draw unspecified X-scale.</strong><br>No plots.<br>");
  1587. //Check if we should autoscale y-axis
  1588. if( !$this->yscale->IsSpecified() && count($this->plots)>0 ) {
  1589. list($min,$max) = $this->GetPlotsYMinMax($this->plots);
  1590. $lres = $this->GetLinesYMinMax($this->lines);
  1591. if( is_array($lres) ) {
  1592. list($linmin,$linmax) = $lres ;
  1593. $min = min($min,$linmin);
  1594. $max = max($max,$linmax);
  1595. }
  1596. $tres = $this->GetTextsYMinMax();
  1597. if( is_array($tres) ) {
  1598. list($tmin,$tmax) = $tres ;
  1599. $min = min($min,$tmin);
  1600. $max = max($max,$tmax);
  1601. }
  1602. $this->yscale->AutoScale($this->img,$min,$max,
  1603. $this->img->plotheight/$this->ytick_factor);
  1604. }
  1605. elseif( $this->yscale->IsSpecified() &&
  1606. ( $this->yscale->auto_ticks || !$this->yscale->ticks->IsSpecified()) ) {
  1607. // The tick calculation will use the user suplied min/max values to determine
  1608. // the ticks. If auto_ticks is false the exact user specifed min and max
  1609. // values will be used for the scale.
  1610. // If auto_ticks is true then the scale might be slightly adjusted
  1611. // so that the min and max values falls on an even major step.
  1612. $min = $this->yscale->scale[0];
  1613. $max = $this->yscale->scale[1];
  1614. $this->yscale->AutoScale($this->img,$min,$max,
  1615. $this->img->plotheight/$this->ytick_factor,
  1616. $this->yscale->auto_ticks);
  1617. }
  1618. if( $this->y2scale != null) {
  1619. if( !$this->y2scale->IsSpecified() && count($this->y2plots)>0 ) {
  1620. list($min,$max) = $this->GetPlotsYMinMax($this->y2plots);
  1621. $lres = $this->GetLinesYMinMax($this->y2lines);
  1622. if( is_array($lres) ) {
  1623. list($linmin,$linmax) = $lres ;
  1624. $min = min($min,$linmin);
  1625. $max = max($max,$linmax);
  1626. }
  1627. $tres = $this->GetTextsYMinMax(true);
  1628. if( is_array($tres) ) {
  1629. list($tmin,$tmax) = $tres ;
  1630. $min = min($min,$tmin);
  1631. $max = max($max,$tmax);
  1632. }
  1633. $this->y2scale->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor);
  1634. }
  1635. elseif( $this->y2scale->IsSpecified() &&
  1636. ( $this->y2scale->auto_ticks || !$this->y2scale->ticks->IsSpecified()) ) {
  1637. // The tick calculation will use the user suplied min/max values to determine
  1638. // the ticks. If auto_ticks is false the exact user specifed min and max
  1639. // values will be used for the scale.
  1640. // If auto_ticks is true then the scale might be slightly adjusted
  1641. // so that the min and max values falls on an even major step.
  1642. $min = $this->y2scale->scale[0];
  1643. $max = $this->y2scale->scale[1];
  1644. $this->y2scale->AutoScale($this->img,$min,$max,
  1645. $this->img->plotheight/$this->ytick_factor,
  1646. $this->y2scale->auto_ticks);
  1647. }
  1648. }
  1649. //
  1650. // Autoscale the multiple Y-axis
  1651. //
  1652. $n = count($this->ynaxis);
  1653. for( $i=0; $i < $n; ++$i ) {
  1654. if( $this->ynscale[$i] != null) {
  1655. if( !$this->ynscale[$i]->IsSpecified() && count($this->ynplots[$i])>0 ) {
  1656. list($min,$max) = $this->GetPlotsYMinMax($this->ynplots[$i]);
  1657. $this->ynscale[$i]->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor);
  1658. }
  1659. elseif( $this->ynscale[$i]->IsSpecified() &&
  1660. ( $this->ynscale[$i]->auto_ticks || !$this->ynscale[$i]->ticks->IsSpecified()) ) {
  1661. // The tick calculation will use the user suplied min/max values to determine
  1662. // the ticks. If auto_ticks is false the exact user specifed min and max
  1663. // values will be used for the scale.
  1664. // If auto_ticks is true then the scale might be slightly adjusted
  1665. // so that the min and max values falls on an even major step.
  1666. $min = $this->ynscale[$i]->scale[0];
  1667. $max = $this->ynscale[$i]->scale[1];
  1668. $this->ynscale[$i]->AutoScale($this->img,$min,$max,
  1669. $this->img->plotheight/$this->ytick_factor,
  1670. $this->ynscale[$i]->auto_ticks);
  1671. }
  1672. }
  1673. }
  1674. //Check if we should autoscale x-axis
  1675. if( !$this->xscale->IsSpecified() ) {
  1676. if( substr($this->axtype,0,4) == "text" ) {
  1677. $max=0;
  1678. $n = count($this->plots);
  1679. for($i=0; $i < $n; ++$i ) {
  1680. $p = $this->plots[$i];
  1681. // We need some unfortunate sub class knowledge here in order
  1682. // to increase number of data points in case it is a line plot
  1683. // which has the barcenter set. If not it could mean that the
  1684. // last point of the data is outside the scale since the barcenter
  1685. // settings means that we will shift the entire plot half a tick step
  1686. // to the right in oder to align with the center of the bars.
  1687. if( is_a($p,'BarPlot') || empty($p->barcenter)) {
  1688. $max=max($max,$p->numpoints-1);
  1689. }
  1690. else {
  1691. $max=max($max,$p->numpoints);
  1692. }
  1693. }
  1694. $min=0;
  1695. if( $this->y2axis != null ) {
  1696. foreach( $this->y2plots as $p ) {
  1697. $max=max($max,$p->numpoints-1);
  1698. }
  1699. }
  1700. $n = count($this->ynaxis);
  1701. for( $i=0; $i < $n; ++$i ) {
  1702. if( $this->ynaxis[$i] != null) {
  1703. foreach( $this->ynplots[$i] as $p ) {
  1704. $max=max($max,$p->numpoints-1);
  1705. }
  1706. }
  1707. }
  1708. $this->xscale->Update($this->img,$min,$max);
  1709. $this->xscale->ticks->Set($this->xaxis->tick_step,1);
  1710. $this->xscale->ticks->SupressMinorTickMarks();
  1711. }
  1712. else {
  1713. list($min,$max) = $this->GetXMinMax();
  1714. $lres = $this->GetLinesXMinMax($this->lines);
  1715. if( $lres ) {
  1716. list($linmin,$linmax) = $lres ;
  1717. $min = min($min,$linmin);
  1718. $max = max($max,$linmax);
  1719. }
  1720. $lres = $this->GetLinesXMinMax($this->y2lines);
  1721. if( $lres ) {
  1722. list($linmin,$linmax) = $lres ;
  1723. $min = min($min,$linmin);
  1724. $max = max($max,$linmax);
  1725. }
  1726. $tres = $this->GetTextsXMinMax();
  1727. if( $tres ) {
  1728. list($tmin,$tmax) = $tres ;
  1729. $min = min($min,$tmin);
  1730. $max = max($max,$tmax);
  1731. }
  1732. $tres = $this->GetTextsXMinMax(true);
  1733. if( $tres ) {
  1734. list($tmin,$tmax) = $tres ;
  1735. $min = min($min,$tmin);
  1736. $max = max($max,$tmax);
  1737. }
  1738. $this->xscale->AutoScale($this->img,$min,$max,round($this->img->plotwidth/$this->xtick_factor));
  1739. }
  1740. //Adjust position of y-axis and y2-axis to minimum/maximum of x-scale
  1741. if( !is_numeric($this->yaxis->pos) && !is_string($this->yaxis->pos) )
  1742. $this->yaxis->SetPos($this->xscale->GetMinVal());
  1743. if( $this->y2axis != null ) {
  1744. if( !is_numeric($this->y2axis->pos) && !is_string($this->y2axis->pos) )
  1745. $this->y2axis->SetPos($this->xscale->GetMaxVal());
  1746. $this->y2axis->SetTitleSide(SIDE_RIGHT);
  1747. }
  1748. $n = count($this->ynaxis);
  1749. $nY2adj = $this->y2axis != null ? $this->iYAxisDeltaPos : 0;
  1750. for( $i=0; $i < $n; ++$i ) {
  1751. if( $this->ynaxis[$i] != null ) {
  1752. if( !is_numeric($this->ynaxis[$i]->pos) && !is_string($this->ynaxis[$i]->pos) ) {
  1753. $this->ynaxis[$i]->SetPos($this->xscale->GetMaxVal());
  1754. $this->ynaxis[$i]->SetPosAbsDelta($i*$this->iYAxisDeltaPos + $nY2adj);
  1755. }
  1756. $this->ynaxis[$i]->SetTitleSide(SIDE_RIGHT);
  1757. }
  1758. }
  1759. }
  1760. elseif( $this->xscale->IsSpecified() &&
  1761. ( $this->xscale->auto_ticks || !$this->xscale->ticks->IsSpecified()) ) {
  1762. // The tick calculation will use the user suplied min/max values to determine
  1763. // the ticks. If auto_ticks is false the exact user specifed min and max
  1764. // values will be used for the scale.
  1765. // If auto_ticks is true then the scale might be slightly adjusted
  1766. // so that the min and max values falls on an even major step.
  1767. $min = $this->xscale->scale[0];
  1768. $max = $this->xscale->scale[1];
  1769. $this->xscale->AutoScale($this->img,$min,$max,
  1770. $this->img->plotwidth/$this->xtick_factor,
  1771. false);
  1772. if( $this->y2axis != null ) {
  1773. if( !is_numeric($this->y2axis->pos) && !is_string($this->y2axis->pos) )
  1774. $this->y2axis->SetPos($this->xscale->GetMaxVal());
  1775. $this->y2axis->SetTitleSide(SIDE_RIGHT);
  1776. }
  1777. }
  1778. // If we have a negative values and x-axis position is at 0
  1779. // we need to supress the first and possible the last tick since
  1780. // they will be drawn on top of the y-axis (and possible y2 axis)
  1781. // The test below might seem strange the reasone being that if
  1782. // the user hasn't specified a value for position this will not
  1783. // be set until we do the stroke for the axis so as of now it
  1784. // is undefined.
  1785. // For X-text scale we ignore all this since the tick are usually
  1786. // much further in and not close to the Y-axis. Hence the test
  1787. // for 'text'
  1788. if( ($this->yaxis->pos==$this->xscale->GetMinVal() ||
  1789. (is_string($this->yaxis->pos) && $this->yaxis->pos=='min')) &&
  1790. !is_numeric($this->xaxis->pos) && $this->yscale->GetMinVal() < 0 &&
  1791. substr($this->axtype,0,4) != 'text' && $this->xaxis->pos!="min" ) {
  1792. //$this->yscale->ticks->SupressZeroLabel(false);
  1793. $this->xscale->ticks->SupressFirst();
  1794. if( $this->y2axis != null ) {
  1795. $this->xscale->ticks->SupressLast();
  1796. }
  1797. }
  1798. elseif( !is_numeric($this->yaxis->pos) && $this->yaxis->pos=='max' ) {
  1799. $this->xscale->ticks->SupressLast();
  1800. }
  1801. if( !$_csim ) {
  1802. $this->StrokePlotArea();
  1803. if( $this->iIconDepth == DEPTH_BACK ) {
  1804. $this->StrokeIcons();
  1805. }
  1806. }
  1807. $this->StrokeAxis();
  1808. // Stroke bands
  1809. if( $this->bands != null && !$_csim)
  1810. for($i=0; $i < count($this->bands); ++$i) {
  1811. // Stroke all bands that asks to be in the background
  1812. if( $this->bands[$i]->depth == DEPTH_BACK )
  1813. $this->bands[$i]->Stroke($this->img,$this->xscale,$this->yscale);
  1814. }
  1815. if( $this->y2bands != null && $this->y2scale != null && !$_csim )
  1816. for($i=0; $i < count($this->y2bands); ++$i) {
  1817. // Stroke all bands that asks to be in the foreground
  1818. if( $this->y2bands[$i]->depth == DEPTH_BACK )
  1819. $this->y2bands[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
  1820. }
  1821. if( $this->grid_depth == DEPTH_BACK && !$_csim) {
  1822. $this->ygrid->Stroke();
  1823. $this->xgrid->Stroke();
  1824. }
  1825. // Stroke Y2-axis
  1826. if( $this->y2axis != null && !$_csim) {
  1827. $this->y2axis->Stroke($this->xscale);
  1828. $this->y2grid->Stroke();
  1829. }
  1830. // Stroke yn-axis
  1831. $n = count($this->ynaxis);
  1832. for( $i=0; $i < $n; ++$i ) {
  1833. $this->ynaxis[$i]->Stroke($this->xscale);
  1834. }
  1835. $oldoff=$this->xscale->off;
  1836. if(substr($this->axtype,0,4)=="text") {
  1837. if( $this->text_scale_abscenteroff > -1 ) {
  1838. // For a text scale the scale factor is the number of pixel per step.
  1839. // Hence we can use the scale factor as a substitute for number of pixels
  1840. // per major scale step and use that in order to adjust the offset so that
  1841. // an object of width "abscenteroff" becomes centered.
  1842. $this->xscale->off += round($this->xscale->scale_factor/2)-round($this->text_scale_abscenteroff/2);
  1843. }
  1844. else {
  1845. $this->xscale->off +=
  1846. ceil($this->xscale->scale_factor*$this->text_scale_off*$this->xscale->ticks->minor_step);
  1847. }
  1848. }
  1849. if( $this->iDoClipping ) {
  1850. $oldimage = $this->img->CloneCanvasH();
  1851. }
  1852. if( ! $this->y2orderback ) {
  1853. // Stroke all plots for Y axis
  1854. for($i=0; $i < count($this->plots); ++$i) {
  1855. $this->plots[$i]->Stroke($this->img,$this->xscale,$this->yscale);
  1856. $this->plots[$i]->StrokeMargin($this->img);
  1857. }
  1858. }
  1859. // Stroke all plots for Y2 axis
  1860. if( $this->y2scale != null )
  1861. for($i=0; $i< count($this->y2plots); ++$i ) {
  1862. $this->y2plots[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
  1863. }
  1864. if( $this->y2orderback ) {
  1865. // Stroke all plots for Y1 axis
  1866. for($i=0; $i < count($this->plots); ++$i) {
  1867. $this->plots[$i]->Stroke($this->img,$this->xscale,$this->yscale);
  1868. $this->plots[$i]->StrokeMargin($this->img);
  1869. }
  1870. }
  1871. $n = count($this->ynaxis);
  1872. for( $i=0; $i < $n; ++$i ) {
  1873. $m = count($this->ynplots[$i]);
  1874. for( $j=0; $j < $m; ++$j ) {
  1875. $this->ynplots[$i][$j]->Stroke($this->img,$this->xscale,$this->ynscale[$i]);
  1876. $this->ynplots[$i][$j]->StrokeMargin($this->img);
  1877. }
  1878. }
  1879. if( $this->iIconDepth == DEPTH_FRONT) {
  1880. $this->StrokeIcons();
  1881. }
  1882. if( $this->iDoClipping ) {
  1883. // Clipping only supports graphs at 0 and 90 degrees
  1884. if( $this->img->a == 0 ) {
  1885. $this->img->CopyCanvasH($oldimage,$this->img->img,
  1886. $this->img->left_margin,$this->img->top_margin,
  1887. $this->img->left_margin,$this->img->top_margin,
  1888. $this->img->plotwidth+1,$this->img->plotheight);
  1889. }
  1890. elseif( $this->img->a == 90 ) {
  1891. $adj = ($this->img->height - $this->img->width)/2;
  1892. $this->img->CopyCanvasH($oldimage,$this->img->img,
  1893. $this->img->bottom_margin-$adj,$this->img->left_margin+$adj,
  1894. $this->img->bottom_margin-$adj,$this->img->left_margin+$adj,
  1895. $this->img->plotheight+1,$this->img->plotwidth);
  1896. }
  1897. else {
  1898. JpGraphError::RaiseL(25035,$this->img->a);//('You have enabled clipping. Cliping is only supported for graphs at 0 or 90 degrees rotation. Please adjust you current angle (='.$this->img->a.' degrees) or disable clipping.');
  1899. }
  1900. $this->img->Destroy();
  1901. $this->img->SetCanvasH($oldimage);
  1902. }
  1903. $this->xscale->off=$oldoff;
  1904. if( $this->grid_depth == DEPTH_FRONT && !$_csim ) {
  1905. $this->ygrid->Stroke();
  1906. $this->xgrid->Stroke();
  1907. }
  1908. // Stroke bands
  1909. if( $this->bands!= null )
  1910. for($i=0; $i < count($this->bands); ++$i) {
  1911. // Stroke all bands that asks to be in the foreground
  1912. if( $this->bands[$i]->depth == DEPTH_FRONT )
  1913. $this->bands[$i]->Stroke($this->img,$this->xscale,$this->yscale);
  1914. }
  1915. if( $this->y2bands!= null && $this->y2scale != null )
  1916. for($i=0; $i < count($this->y2bands); ++$i) {
  1917. // Stroke all bands that asks to be in the foreground
  1918. if( $this->y2bands[$i]->depth == DEPTH_FRONT )
  1919. $this->y2bands[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
  1920. }
  1921. // Stroke any lines added
  1922. if( $this->lines != null ) {
  1923. for($i=0; $i < count($this->lines); ++$i) {
  1924. $this->lines[$i]->Stroke($this->img,$this->xscale,$this->yscale);
  1925. }
  1926. }
  1927. if( $this->y2lines != null && $this->y2scale != null ) {
  1928. for($i=0; $i < count($this->y2lines); ++$i) {
  1929. $this->y2lines[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
  1930. }
  1931. }
  1932. // Finally draw the axis again since some plots may have nagged
  1933. // the axis in the edges.However we do no stroke the labels again
  1934. // since any user defined callback would be called twice. It also
  1935. // enhances performance.
  1936. if( !$_csim )
  1937. $this->StrokeAxis(false);
  1938. if( $this->y2scale != null && !$_csim )
  1939. $this->y2axis->Stroke($this->xscale,false);
  1940. if( !$_csim ) {
  1941. $this->StrokePlotBox();
  1942. }
  1943. // The titles and legends never gets rotated so make sure
  1944. // that the angle is 0 before stroking them
  1945. $aa = $this->img->SetAngle(0);
  1946. $this->StrokeTitles();
  1947. $this->footer->Stroke($this->img);
  1948. $this->legend->Stroke($this->img);
  1949. $this->img->SetAngle($aa);
  1950. $this->StrokeTexts();
  1951. $this->StrokeTables();
  1952. if( !$_csim ) {
  1953. $this->img->SetAngle($aa);
  1954. // Draw an outline around the image map
  1955. if(_JPG_DEBUG) {
  1956. $this->DisplayClientSideaImageMapAreas();
  1957. }
  1958. // Adjust the appearance of the image
  1959. $this->AdjustSaturationBrightnessContrast();
  1960. // Should we do any final image transformation
  1961. if( $this->iImgTrans ) {
  1962. if( !class_exists('ImgTrans') ) {
  1963. require_once('jpgraph_imgtrans.php');
  1964. //JpGraphError::Raise('In order to use image transformation you must include the file jpgraph_imgtrans.php in your script.');
  1965. }
  1966. $tform = new ImgTrans($this->img->img);
  1967. $this->img->img = $tform->Skew3D($this->iImgTransHorizon,$this->iImgTransSkewDist,
  1968. $this->iImgTransDirection,$this->iImgTransHighQ,
  1969. $this->iImgTransMinSize,$this->iImgTransFillColor,
  1970. $this->iImgTransBorder);
  1971. }
  1972. // If the filename is given as the special "__handle"
  1973. // then the image handler is returned and the image is NOT
  1974. // streamed back
  1975. if( $aStrokeFileName == _IMG_HANDLER ) {
  1976. return $this->img->img;
  1977. }
  1978. else {
  1979. // Finally stream the generated picture
  1980. $this->cache->PutAndStream($this->img,$this->cache_name,$this->inline,$aStrokeFileName);
  1981. }
  1982. }
  1983. }
  1984. function SetAxisLabelBackground($aType,$aXFColor='lightgray',$aXColor='black',$aYFColor='lightgray',$aYColor='black') {
  1985. $this->iAxisLblBgType = $aType;
  1986. $this->iXAxisLblBgFillColor = $aXFColor;
  1987. $this->iXAxisLblBgColor = $aXColor;
  1988. $this->iYAxisLblBgFillColor = $aYFColor;
  1989. $this->iYAxisLblBgColor = $aYColor;
  1990. }
  1991. //---------------
  1992. // PRIVATE METHODS
  1993. function StrokeAxisLabelBackground() {
  1994. // Types
  1995. // 0 = No background
  1996. // 1 = Only X-labels, length of axis
  1997. // 2 = Only Y-labels, length of axis
  1998. // 3 = As 1 but extends to width of graph
  1999. // 4 = As 2 but extends to height of graph
  2000. // 5 = Combination of 3 & 4
  2001. // 6 = Combination of 1 & 2
  2002. $t = $this->iAxisLblBgType ;
  2003. if( $t < 1 ) return;
  2004. // Stroke optional X-axis label background color
  2005. if( $t == 1 || $t == 3 || $t == 5 || $t == 6 ) {
  2006. $this->img->PushColor($this->iXAxisLblBgFillColor);
  2007. if( $t == 1 || $t == 6 ) {
  2008. $xl = $this->img->left_margin;
  2009. $yu = $this->img->height - $this->img->bottom_margin + 1;
  2010. $xr = $this->img->width - $this->img->right_margin ;
  2011. $yl = $this->img->height-1-$this->frame_weight;
  2012. }
  2013. else { // t==3 || t==5
  2014. $xl = $this->frame_weight;
  2015. $yu = $this->img->height - $this->img->bottom_margin + 1;
  2016. $xr = $this->img->width - 1 - $this->frame_weight;
  2017. $yl = $this->img->height-1-$this->frame_weight;
  2018. }
  2019. $this->img->FilledRectangle($xl,$yu,$xr,$yl);
  2020. $this->img->PopColor();
  2021. // Check if we should add the vertical lines at left and right edge
  2022. if( $this->iXAxisLblBgColor !== '' ) {
  2023. $this->img->PushColor($this->iXAxisLblBgColor);
  2024. if( $t == 1 || $t == 6 ) {
  2025. $this->img->Line($xl,$yu,$xl,$yl);
  2026. $this->img->Line($xr,$yu,$xr,$yl);
  2027. }
  2028. else {
  2029. $xl = $this->img->width - $this->img->right_margin ;
  2030. $this->img->Line($xl,$yu-1,$xr,$yu-1);
  2031. }
  2032. $this->img->PopColor();
  2033. }
  2034. }
  2035. if( $t == 2 || $t == 4 || $t == 5 || $t == 6 ) {
  2036. $this->img->PushColor($this->iYAxisLblBgFillColor);
  2037. if( $t == 2 || $t == 6 ) {
  2038. $xl = $this->frame_weight;
  2039. $yu = $this->frame_weight+$this->img->top_margin;
  2040. $xr = $this->img->left_margin - 1;
  2041. $yl = $this->img->height - $this->img->bottom_margin + 1;
  2042. }
  2043. else {
  2044. $xl = $this->frame_weight;
  2045. $yu = $this->frame_weight;
  2046. $xr = $this->img->left_margin - 1;
  2047. $yl = $this->img->height-1-$this->frame_weight;
  2048. }
  2049. $this->img->FilledRectangle($xl,$yu,$xr,$yl);
  2050. $this->img->PopColor();
  2051. // Check if we should add the vertical lines at left and right edge
  2052. if( $this->iXAxisLblBgColor !== '' ) {
  2053. $this->img->PushColor($this->iXAxisLblBgColor);
  2054. if( $t == 2 || $t == 6 ) {
  2055. $this->img->Line($xl,$yu-1,$xr,$yu-1);
  2056. $this->img->Line($xl,$yl-1,$xr,$yl-1);
  2057. }
  2058. else {
  2059. $this->img->Line($xr+1,$yu,$xr+1,$this->img->top_margin);
  2060. }
  2061. $this->img->PopColor();
  2062. }
  2063. }
  2064. }
  2065. function StrokeAxis($aStrokeLabels=true) {
  2066. if( $aStrokeLabels ) {
  2067. $this->StrokeAxisLabelBackground();
  2068. }
  2069. // Stroke axis
  2070. if( $this->iAxisStyle != AXSTYLE_SIMPLE ) {
  2071. switch( $this->iAxisStyle ) {
  2072. case AXSTYLE_BOXIN :
  2073. $toppos = SIDE_DOWN;
  2074. $bottompos = SIDE_UP;
  2075. $leftpos = SIDE_RIGHT;
  2076. $rightpos = SIDE_LEFT;
  2077. break;
  2078. case AXSTYLE_BOXOUT :
  2079. $toppos = SIDE_UP;
  2080. $bottompos = SIDE_DOWN;
  2081. $leftpos = SIDE_LEFT;
  2082. $rightpos = SIDE_RIGHT;
  2083. break;
  2084. case AXSTYLE_YBOXIN:
  2085. $toppos = -100;
  2086. $bottompos = SIDE_UP;
  2087. $leftpos = SIDE_RIGHT;
  2088. $rightpos = SIDE_LEFT;
  2089. break;
  2090. case AXSTYLE_YBOXOUT:
  2091. $toppos = -100;
  2092. $bottompos = SIDE_DOWN;
  2093. $leftpos = SIDE_LEFT;
  2094. $rightpos = SIDE_RIGHT;
  2095. break;
  2096. default:
  2097. JpGRaphError::RaiseL(25036,$this->iAxisStyle); //('Unknown AxisStyle() : '.$this->iAxisStyle);
  2098. break;
  2099. }
  2100. $this->xaxis->SetPos('min');
  2101. // By default we hide the first label so it doesn't cross the
  2102. // Y-axis in case the positon hasn't been set by the user.
  2103. // However, if we use a box we always want the first value
  2104. // displayed so we make sure it will be displayed.
  2105. $this->xscale->ticks->SupressFirst(false);
  2106. $this->xaxis->SetLabelSide(SIDE_DOWN);
  2107. $this->xaxis->scale->ticks->SetSide($bottompos);
  2108. $this->xaxis->Stroke($this->yscale);
  2109. if( $toppos != -100 ) {
  2110. // To avoid side effects we work on a new copy
  2111. $maxis = $this->xaxis;
  2112. $maxis->SetPos('max');
  2113. $maxis->SetLabelSide(SIDE_UP);
  2114. $maxis->SetLabelMargin(7);
  2115. $this->xaxis->scale->ticks->SetSide($toppos);
  2116. $maxis->Stroke($this->yscale);
  2117. }
  2118. $this->yaxis->SetPos('min');
  2119. $this->yaxis->SetLabelMargin(10);
  2120. $this->yaxis->SetLabelSide(SIDE_LEFT);
  2121. $this->yaxis->scale->ticks->SetSide($leftpos);
  2122. $this->yaxis->Stroke($this->xscale);
  2123. $myaxis = $this->yaxis;
  2124. $myaxis->SetPos('max');
  2125. $myaxis->SetLabelMargin(10);
  2126. $myaxis->SetLabelSide(SIDE_RIGHT);
  2127. $myaxis->title->Set('');
  2128. $myaxis->scale->ticks->SetSide($rightpos);
  2129. $myaxis->Stroke($this->xscale);
  2130. }
  2131. else {
  2132. $this->xaxis->Stroke($this->yscale,$aStrokeLabels);
  2133. $this->yaxis->Stroke($this->xscale,$aStrokeLabels);
  2134. }
  2135. }
  2136. // Private helper function for backgound image
  2137. function LoadBkgImage($aImgFormat='',$aFile='',$aImgStr='') {
  2138. if( $aImgStr != '' ) {
  2139. return Image::CreateFromString($aImgStr);
  2140. }
  2141. if( $aFile == '' )
  2142. $aFile = $this->background_image;
  2143. // Remove case sensitivity and setup appropriate function to create image
  2144. // Get file extension. This should be the LAST '.' separated part of the filename
  2145. $e = explode('.',$aFile);
  2146. $ext = strtolower($e[count($e)-1]);
  2147. if ($ext == "jpeg") {
  2148. $ext = "jpg";
  2149. }
  2150. if( trim($ext) == '' )
  2151. $ext = 'png'; // Assume PNG if no extension specified
  2152. if( $aImgFormat == '' )
  2153. $imgtag = $ext;
  2154. else
  2155. $imgtag = $aImgFormat;
  2156. $supported = imagetypes();
  2157. if( ( $ext == 'jpg' && !($supported & IMG_JPG) ) ||
  2158. ( $ext == 'gif' && !($supported & IMG_GIF) ) ||
  2159. ( $ext == 'png' && !($supported & IMG_PNG) ) ) {
  2160. JpGraphError::RaiseL(25037,$aFile);//('The image format of your background image ('.$aFile.') is not supported in your system configuration. ');
  2161. }
  2162. if( $imgtag == "jpg" || $imgtag == "jpeg")
  2163. {
  2164. $f = "imagecreatefromjpeg";
  2165. $imgtag = "jpg";
  2166. }
  2167. else
  2168. {
  2169. $f = "imagecreatefrom".$imgtag;
  2170. }
  2171. // Compare specified image type and file extension
  2172. if( $imgtag != $ext ) {
  2173. //$t = "Background image seems to be of different type (has different file extension) than specified imagetype. Specified: '".$aImgFormat."'File: '".$aFile."'";
  2174. JpGraphError::RaiseL(25038, $aImgFormat, $aFile);
  2175. }
  2176. $img = @$f($aFile);
  2177. if( !$img ) {
  2178. JpGraphError::RaiseL(25039,$aFile);//(" Can't read background image: '".$aFile."'");
  2179. }
  2180. return $img;
  2181. }
  2182. function StrokeBackgroundGrad() {
  2183. if( $this->bkg_gradtype < 0 )
  2184. return;
  2185. $grad = new Gradient($this->img);
  2186. if( $this->bkg_gradstyle == BGRAD_PLOT ) {
  2187. $xl = $this->img->left_margin;
  2188. $yt = $this->img->top_margin;
  2189. $xr = $xl + $this->img->plotwidth+1 ;
  2190. $yb = $yt + $this->img->plotheight ;
  2191. $grad->FilledRectangle($xl,$yt,$xr,$yb,$this->bkg_gradfrom,$this->bkg_gradto,$this->bkg_gradtype);
  2192. }
  2193. else {
  2194. $xl = 0;
  2195. $yt = 0;
  2196. $xr = $xl + $this->img->width - 1;
  2197. $yb = $yt + $this->img->height;
  2198. if( $this->doshadow ) {
  2199. $xr -= $this->shadow_width;
  2200. $yb -= $this->shadow_width;
  2201. }
  2202. if( $this->doframe ) {
  2203. $yt += $this->frame_weight;
  2204. $yb -= $this->frame_weight;
  2205. $xl += $this->frame_weight;
  2206. $xr -= $this->frame_weight;
  2207. }
  2208. $aa = $this->img->SetAngle(0);
  2209. $grad->FilledRectangle($xl,$yt,$xr,$yb,$this->bkg_gradfrom,$this->bkg_gradto,$this->bkg_gradtype);
  2210. $aa = $this->img->SetAngle($aa);
  2211. }
  2212. }
  2213. function StrokeFrameBackground() {
  2214. if( $this->background_image != "" && $this->background_cflag != "" ) {
  2215. JpGraphError::RaiseL(25040);//('It is not possible to specify both a background image and a background country flag.');
  2216. }
  2217. if( $this->background_image != "" ) {
  2218. $bkgimg = $this->LoadBkgImage($this->background_image_format);
  2219. $this->img->_AdjBrightContrast($bkgimg,$this->background_image_bright,
  2220. $this->background_image_contr);
  2221. $this->img->_AdjSat($bkgimg,$this->background_image_sat);
  2222. }
  2223. elseif( $this->background_cflag != "" ) {
  2224. if( ! class_exists('FlagImages') ) {
  2225. JpGraphError::RaiseL(25041);//('In order to use Country flags as backgrounds you must include the "jpgraph_flags.php" file.');
  2226. }
  2227. $fobj = new FlagImages(FLAGSIZE4);
  2228. $dummy='';
  2229. $bkgimg = $fobj->GetImgByName($this->background_cflag,$dummy);
  2230. $this->background_image_mix = $this->background_cflag_mix;
  2231. $this->background_image_type = $this->background_cflag_type;
  2232. }
  2233. else {
  2234. return ;
  2235. }
  2236. $bw = ImageSX($bkgimg);
  2237. $bh = ImageSY($bkgimg);
  2238. // No matter what the angle is we always stroke the image and frame
  2239. // assuming it is 0 degree
  2240. $aa = $this->img->SetAngle(0);
  2241. switch( $this->background_image_type ) {
  2242. case BGIMG_FILLPLOT: // Resize to just fill the plotarea
  2243. $this->FillMarginArea();
  2244. $this->StrokeFrame();
  2245. $this->FillPlotArea();
  2246. $this->img->CopyMerge($bkgimg,
  2247. $this->img->left_margin,$this->img->top_margin,
  2248. 0,0,$this->img->plotwidth+1,$this->img->plotheight,
  2249. $bw,$bh,$this->background_image_mix);
  2250. break;
  2251. case BGIMG_FILLFRAME: // Fill the whole area from upper left corner, resize to just fit
  2252. $hadj=0; $vadj=0;
  2253. if( $this->doshadow ) {
  2254. $hadj = $this->shadow_width;
  2255. $vadj = $this->shadow_width;
  2256. }
  2257. $this->FillMarginArea();
  2258. $this->FillPlotArea();
  2259. $this->img->CopyMerge($bkgimg,0,0,0,0,$this->img->width-$hadj,$this->img->height-$vadj,
  2260. $bw,$bh,$this->background_image_mix);
  2261. $this->StrokeFrame();
  2262. break;
  2263. case BGIMG_COPY: // Just copy the image from left corner, no resizing
  2264. $this->FillMarginArea();
  2265. $this->FillPlotArea();
  2266. $this->img->CopyMerge($bkgimg,0,0,0,0,$bw,$bh,
  2267. $bw,$bh,$this->background_image_mix);
  2268. $this->StrokeFrame();
  2269. break;
  2270. case BGIMG_CENTER: // Center original image in the plot area
  2271. $this->FillMarginArea();
  2272. $this->FillPlotArea();
  2273. $centerx = round($this->img->plotwidth/2+$this->img->left_margin-$bw/2);
  2274. $centery = round($this->img->plotheight/2+$this->img->top_margin-$bh/2);
  2275. $this->img->CopyMerge($bkgimg,$centerx,$centery,0,0,$bw,$bh,
  2276. $bw,$bh,$this->background_image_mix);
  2277. $this->StrokeFrame();
  2278. break;
  2279. default:
  2280. JpGraphError::RaiseL(25042);//(" Unknown background image layout");
  2281. }
  2282. $this->img->SetAngle($aa);
  2283. }
  2284. // Private
  2285. // Draw a frame around the image
  2286. function StrokeFrame() {
  2287. if( !$this->doframe ) return;
  2288. if( $this->background_image_type <= 1 &&
  2289. ($this->bkg_gradtype < 0 || ($this->bkg_gradtype > 0 && $this->bkg_gradstyle==BGRAD_PLOT)) ) {
  2290. $c = $this->margin_color;
  2291. }
  2292. else {
  2293. $c = false;
  2294. }
  2295. if( $this->doshadow ) {
  2296. $this->img->SetColor($this->frame_color);
  2297. $this->img->ShadowRectangle(0,0,$this->img->width,$this->img->height,
  2298. $c,$this->shadow_width,$this->shadow_color);
  2299. }
  2300. elseif( $this->framebevel ) {
  2301. if( $c ) {
  2302. $this->img->SetColor($this->margin_color);
  2303. $this->img->FilledRectangle(0,0,$this->img->width-1,$this->img->height-1);
  2304. }
  2305. $this->img->Bevel(1,1,$this->img->width-2,$this->img->height-2,
  2306. $this->framebeveldepth,
  2307. $this->framebevelcolor1,$this->framebevelcolor2);
  2308. if( $this->framebevelborder ) {
  2309. $this->img->SetColor($this->framebevelbordercolor);
  2310. $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);
  2311. }
  2312. }
  2313. else {
  2314. $this->img->SetLineWeight($this->frame_weight);
  2315. if( $c ) {
  2316. $this->img->SetColor($this->margin_color);
  2317. $this->img->FilledRectangle(0,0,$this->img->width-1,$this->img->height-1);
  2318. }
  2319. $this->img->SetColor($this->frame_color);
  2320. $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);
  2321. }
  2322. }
  2323. function FillMarginArea() {
  2324. $hadj=0; $vadj=0;
  2325. if( $this->doshadow ) {
  2326. $hadj = $this->shadow_width;
  2327. $vadj = $this->shadow_width;
  2328. }
  2329. $this->img->SetColor($this->margin_color);
  2330. // $this->img->FilledRectangle(0,0,$this->img->width-1-$hadj,$this->img->height-1-$vadj);
  2331. $this->img->FilledRectangle(0,0,$this->img->width-1-$hadj,$this->img->top_margin);
  2332. $this->img->FilledRectangle(0,$this->img->top_margin,$this->img->left_margin,$this->img->height-1-$hadj);
  2333. $this->img->FilledRectangle($this->img->left_margin+1,
  2334. $this->img->height-$this->img->bottom_margin,
  2335. $this->img->width-1-$hadj,
  2336. $this->img->height-1-$hadj);
  2337. $this->img->FilledRectangle($this->img->width-$this->img->right_margin,
  2338. $this->img->top_margin+1,
  2339. $this->img->width-1-$hadj,
  2340. $this->img->height-$this->img->bottom_margin-1);
  2341. }
  2342. function FillPlotArea() {
  2343. $this->img->PushColor($this->plotarea_color);
  2344. $this->img->FilledRectangle($this->img->left_margin,
  2345. $this->img->top_margin,
  2346. $this->img->width-$this->img->right_margin,
  2347. $this->img->height-$this->img->bottom_margin);
  2348. $this->img->PopColor();
  2349. }
  2350. // Stroke the plot area with either a solid color or a background image
  2351. function StrokePlotArea() {
  2352. // Note: To be consistent we really should take a possible shadow
  2353. // into account. However, that causes some problem for the LinearScale class
  2354. // since in the current design it does not have any links to class Graph which
  2355. // means it has no way of compensating for the adjusted plotarea in case of a
  2356. // shadow. So, until I redesign LinearScale we can't compensate for this.
  2357. // So just set the two adjustment parameters to zero for now.
  2358. $boxadj = 0; //$this->doframe ? $this->frame_weight : 0 ;
  2359. $adj = 0; //$this->doshadow ? $this->shadow_width : 0 ;
  2360. if( $this->background_image != "" || $this->background_cflag != "" ) {
  2361. $this->StrokeFrameBackground();
  2362. }
  2363. else {
  2364. $aa = $this->img->SetAngle(0);
  2365. $this->StrokeFrame();
  2366. $aa = $this->img->SetAngle($aa);
  2367. $this->StrokeBackgroundGrad();
  2368. if( $this->bkg_gradtype < 0 ||
  2369. ($this->bkg_gradtype > 0 && $this->bkg_gradstyle==BGRAD_MARGIN) ) {
  2370. $this->FillPlotArea();
  2371. }
  2372. }
  2373. }
  2374. function StrokeIcons() {
  2375. $n = count($this->iIcons);
  2376. for( $i=0; $i < $n; ++$i ) {
  2377. $this->iIcons[$i]->StrokeWithScale($this->img,$this->xscale,$this->yscale);
  2378. }
  2379. }
  2380. function StrokePlotBox() {
  2381. // Should we draw a box around the plot area?
  2382. if( $this->boxed ) {
  2383. $this->img->SetLineWeight(1);
  2384. $this->img->SetLineStyle('solid');
  2385. $this->img->SetColor($this->box_color);
  2386. for($i=0; $i < $this->box_weight; ++$i ) {
  2387. $this->img->Rectangle(
  2388. $this->img->left_margin-$i,$this->img->top_margin-$i,
  2389. $this->img->width-$this->img->right_margin+$i,
  2390. $this->img->height-$this->img->bottom_margin+$i);
  2391. }
  2392. }
  2393. }
  2394. function SetTitleBackgroundFillStyle($aStyle,$aColor1='black',$aColor2='white') {
  2395. $this->titlebkg_fillstyle = $aStyle;
  2396. $this->titlebkg_scolor1 = $aColor1;
  2397. $this->titlebkg_scolor2 = $aColor2;
  2398. }
  2399. function SetTitleBackground($aBackColor='gray', $aStyle=TITLEBKG_STYLE1, $aFrameStyle=TITLEBKG_FRAME_NONE, $aFrameColor='black', $aFrameWeight=1, $aBevelHeight=3, $aEnable=true) {
  2400. $this->titlebackground = $aEnable;
  2401. $this->titlebackground_color = $aBackColor;
  2402. $this->titlebackground_style = $aStyle;
  2403. $this->titlebackground_framecolor = $aFrameColor;
  2404. $this->titlebackground_framestyle = $aFrameStyle;
  2405. $this->titlebackground_frameweight = $aFrameWeight;
  2406. $this->titlebackground_bevelheight = $aBevelHeight ;
  2407. }
  2408. function StrokeTitles() {
  2409. $margin=3;
  2410. if( $this->titlebackground ) {
  2411. // Find out height
  2412. $this->title->margin += 2 ;
  2413. $h = $this->title->GetTextHeight($this->img)+$this->title->margin+$margin;
  2414. if( $this->subtitle->t != "" && !$this->subtitle->hide ) {
  2415. $h += $this->subtitle->GetTextHeight($this->img)+$margin+
  2416. $this->subtitle->margin;
  2417. $h += 2;
  2418. }
  2419. if( $this->subsubtitle->t != "" && !$this->subsubtitle->hide ) {
  2420. $h += $this->subsubtitle->GetTextHeight($this->img)+$margin+
  2421. $this->subsubtitle->margin;
  2422. $h += 2;
  2423. }
  2424. $this->img->PushColor($this->titlebackground_color);
  2425. if( $this->titlebackground_style === TITLEBKG_STYLE1 ) {
  2426. // Inside the frame
  2427. if( $this->framebevel ) {
  2428. $x1 = $y1 = $this->framebeveldepth + 1 ;
  2429. $x2 = $this->img->width - $this->framebeveldepth - 2 ;
  2430. $this->title->margin += $this->framebeveldepth + 1 ;
  2431. $h += $y1 ;
  2432. $h += 2;
  2433. }
  2434. else {
  2435. $x1 = $y1 = $this->frame_weight;
  2436. $x2 = $this->img->width - 2*$x1;
  2437. }
  2438. }
  2439. elseif( $this->titlebackground_style === TITLEBKG_STYLE2 ) {
  2440. // Cover the frame as well
  2441. $x1 = $y1 = 0;
  2442. $x2 = $this->img->width - 1 ;
  2443. }
  2444. elseif( $this->titlebackground_style === TITLEBKG_STYLE3 ) {
  2445. // Cover the frame as well (the difference is that
  2446. // for style==3 a bevel frame border is on top
  2447. // of the title background)
  2448. $x1 = $y1 = 0;
  2449. $x2 = $this->img->width - 1 ;
  2450. $h += $this->framebeveldepth ;
  2451. $this->title->margin += $this->framebeveldepth ;
  2452. }
  2453. else {
  2454. JpGraphError::RaiseL(25043);//('Unknown title background style.');
  2455. }
  2456. if( $this->titlebackground_framestyle === 3 ) {
  2457. $h += $this->titlebackground_bevelheight*2 + 1 ;
  2458. $this->title->margin += $this->titlebackground_bevelheight ;
  2459. }
  2460. if( $this->doshadow ) {
  2461. $x2 -= $this->shadow_width ;
  2462. }
  2463. $indent=0;
  2464. if( $this->titlebackground_framestyle == TITLEBKG_FRAME_BEVEL ) {
  2465. $ind = $this->titlebackground_bevelheight;
  2466. }
  2467. if( $this->titlebkg_fillstyle==TITLEBKG_FILLSTYLE_HSTRIPED ) {
  2468. $this->img->FilledRectangle2($x1+$ind,$y1+$ind,$x2-$ind,$h-$ind,
  2469. $this->titlebkg_scolor1,
  2470. $this->titlebkg_scolor2);
  2471. }
  2472. elseif( $this->titlebkg_fillstyle==TITLEBKG_FILLSTYLE_VSTRIPED ) {
  2473. $this->img->FilledRectangle2($x1+$ind,$y1+$ind,$x2-$ind,$h-$ind,
  2474. $this->titlebkg_scolor1,
  2475. $this->titlebkg_scolor2,2);
  2476. }
  2477. else {
  2478. // Solid fill
  2479. $this->img->FilledRectangle($x1,$y1,$x2,$h);
  2480. }
  2481. $this->img->PopColor();
  2482. $this->img->PushColor($this->titlebackground_framecolor);
  2483. $this->img->SetLineWeight($this->titlebackground_frameweight);
  2484. if( $this->titlebackground_framestyle == TITLEBKG_FRAME_FULL ) {
  2485. // Frame background
  2486. $this->img->Rectangle($x1,$y1,$x2,$h);
  2487. }
  2488. elseif( $this->titlebackground_framestyle == TITLEBKG_FRAME_BOTTOM ) {
  2489. // Bottom line only
  2490. $this->img->Line($x1,$h,$x2,$h);
  2491. }
  2492. elseif( $this->titlebackground_framestyle == TITLEBKG_FRAME_BEVEL ) {
  2493. $this->img->Bevel($x1,$y1,$x2,$h,$this->titlebackground_bevelheight);
  2494. }
  2495. $this->img->PopColor();
  2496. // This is clumsy. But we neeed to stroke the whole graph frame if it is
  2497. // set to bevel to get the bevel shading on top of the text background
  2498. if( $this->framebevel && $this->doframe &&
  2499. $this->titlebackground_style === 3 ) {
  2500. $this->img->Bevel(1,1,$this->img->width-2,$this->img->height-2,
  2501. $this->framebeveldepth,
  2502. $this->framebevelcolor1,$this->framebevelcolor2);
  2503. if( $this->framebevelborder ) {
  2504. $this->img->SetColor($this->framebevelbordercolor);
  2505. $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);
  2506. }
  2507. }
  2508. }
  2509. // Stroke title
  2510. $y = $this->title->margin;
  2511. if( $this->title->halign == 'center' )
  2512. $this->title->Center(0,$this->img->width,$y);
  2513. elseif( $this->title->halign == 'left' ) {
  2514. $this->title->SetPos($this->title->margin+2,$y);
  2515. }
  2516. elseif( $this->title->halign == 'right' ) {
  2517. $indent = 0;
  2518. if( $this->doshadow )
  2519. $indent = $this->shadow_width+2;
  2520. $this->title->SetPos($this->img->width-$this->title->margin-$indent,$y,'right');
  2521. }
  2522. $this->title->Stroke($this->img);
  2523. // ... and subtitle
  2524. $y += $this->title->GetTextHeight($this->img) + $margin + $this->subtitle->margin;
  2525. if( $this->subtitle->halign == 'center' )
  2526. $this->subtitle->Center(0,$this->img->width,$y);
  2527. elseif( $this->subtitle->halign == 'left' ) {
  2528. $this->subtitle->SetPos($this->subtitle->margin+2,$y);
  2529. }
  2530. elseif( $this->subtitle->halign == 'right' ) {
  2531. $indent = 0;
  2532. if( $this->doshadow )
  2533. $indent = $this->shadow_width+2;
  2534. $this->subtitle->SetPos($this->img->width-$this->subtitle->margin-$indent,$y,'right');
  2535. }
  2536. $this->subtitle->Stroke($this->img);
  2537. // ... and subsubtitle
  2538. $y += $this->subtitle->GetTextHeight($this->img) + $margin + $this->subsubtitle->margin;
  2539. if( $this->subsubtitle->halign == 'center' )
  2540. $this->subsubtitle->Center(0,$this->img->width,$y);
  2541. elseif( $this->subsubtitle->halign == 'left' ) {
  2542. $this->subsubtitle->SetPos($this->subsubtitle->margin+2,$y);
  2543. }
  2544. elseif( $this->subsubtitle->halign == 'right' ) {
  2545. $indent = 0;
  2546. if( $this->doshadow )
  2547. $indent = $this->shadow_width+2;
  2548. $this->subsubtitle->SetPos($this->img->width-$this->subsubtitle->margin-$indent,$y,'right');
  2549. }
  2550. $this->subsubtitle->Stroke($this->img);
  2551. // ... and fancy title
  2552. $this->tabtitle->Stroke($this->img);
  2553. }
  2554. function StrokeTexts() {
  2555. // Stroke any user added text objects
  2556. if( $this->texts != null ) {
  2557. for($i=0; $i < count($this->texts); ++$i) {
  2558. $this->texts[$i]->StrokeWithScale($this->img,$this->xscale,$this->yscale);
  2559. }
  2560. }
  2561. if( $this->y2texts != null && $this->y2scale != null ) {
  2562. for($i=0; $i < count($this->y2texts); ++$i) {
  2563. $this->y2texts[$i]->StrokeWithScale($this->img,$this->xscale,$this->y2scale);
  2564. }
  2565. }
  2566. }
  2567. function StrokeTables() {
  2568. if( $this->iTables != null ) {
  2569. $n = count($this->iTables);
  2570. for( $i=0; $i < $n; ++$i ) {
  2571. $this->iTables[$i]->StrokeWithScale($this->img,$this->xscale,$this->yscale);
  2572. }
  2573. }
  2574. }
  2575. function DisplayClientSideaImageMapAreas() {
  2576. // Debug stuff - display the outline of the image map areas
  2577. $csim='';
  2578. foreach ($this->plots as $p) {
  2579. $csim.= $p->GetCSIMareas();
  2580. }
  2581. $csim .= $this->legend->GetCSIMareas();
  2582. if (preg_match_all("/area shape=\"(\w+)\" coords=\"([0-9\, ]+)\"/", $csim, $coords)) {
  2583. $this->img->SetColor($this->csimcolor);
  2584. $n = count($coords[0]);
  2585. for ($i=0; $i < $n; $i++) {
  2586. if ($coords[1][$i]=="poly") {
  2587. preg_match_all('/\s*([0-9]+)\s*,\s*([0-9]+)\s*,*/',$coords[2][$i],$pts);
  2588. $this->img->SetStartPoint($pts[1][count($pts[0])-1],$pts[2][count($pts[0])-1]);
  2589. $m = count($pts[0]);
  2590. for ($j=0; $j < $m; $j++) {
  2591. $this->img->LineTo($pts[1][$j],$pts[2][$j]);
  2592. }
  2593. } else if ($coords[1][$i]=="rect") {
  2594. $pts = preg_split('/,/', $coords[2][$i]);
  2595. $this->img->SetStartPoint($pts[0],$pts[1]);
  2596. $this->img->LineTo($pts[2],$pts[1]);
  2597. $this->img->LineTo($pts[2],$pts[3]);
  2598. $this->img->LineTo($pts[0],$pts[3]);
  2599. $this->img->LineTo($pts[0],$pts[1]);
  2600. }
  2601. }
  2602. }
  2603. }
  2604. function AdjustSaturationBrightnessContrast() {
  2605. // Adjust the brightness and contrast of the image
  2606. if( $this->image_contr || $this->image_bright )
  2607. $this->img->AdjBrightContrast($this->image_bright,$this->image_contr);
  2608. if( $this->image_sat )
  2609. $this->img->AdjSat($this->image_sat);
  2610. }
  2611. // Text scale offset in fractions of a major scale step
  2612. function SetTextScaleOff($aOff) {
  2613. $this->text_scale_off = $aOff;
  2614. $this->xscale->text_scale_off = $aOff;
  2615. }
  2616. // Text width of bar to be centered in absolute pixels
  2617. function SetTextScaleAbsCenterOff($aOff) {
  2618. $this->text_scale_abscenteroff = $aOff;
  2619. }
  2620. // Get Y min and max values for added lines
  2621. function GetLinesYMinMax( $aLines ) {
  2622. $n = count($aLines);
  2623. if( $n == 0 ) return false;
  2624. $min = $aLines[0]->scaleposition ;
  2625. $max = $min ;
  2626. $flg = false;
  2627. for( $i=0; $i < $n; ++$i ) {
  2628. if( $aLines[$i]->direction == HORIZONTAL ) {
  2629. $flg = true ;
  2630. $v = $aLines[$i]->scaleposition ;
  2631. if( $min > $v ) $min = $v ;
  2632. if( $max < $v ) $max = $v ;
  2633. }
  2634. }
  2635. return $flg ? array($min,$max) : false ;
  2636. }
  2637. // Get X min and max values for added lines
  2638. function GetLinesXMinMax( $aLines ) {
  2639. $n = count($aLines);
  2640. if( $n == 0 ) return false ;
  2641. $min = $aLines[0]->scaleposition ;
  2642. $max = $min ;
  2643. $flg = false;
  2644. for( $i=0; $i < $n; ++$i ) {
  2645. if( $aLines[$i]->direction == VERTICAL ) {
  2646. $flg = true ;
  2647. $v = $aLines[$i]->scaleposition ;
  2648. if( $min > $v ) $min = $v ;
  2649. if( $max < $v ) $max = $v ;
  2650. }
  2651. }
  2652. return $flg ? array($min,$max) : false ;
  2653. }
  2654. // Get min and max values for all included plots
  2655. function GetPlotsYMinMax(&$aPlots) {
  2656. $n = count($aPlots);
  2657. $i=0;
  2658. do {
  2659. list($xmax,$max) = $aPlots[$i]->Max();
  2660. } while( ++$i < $n && !is_numeric($max) );
  2661. $i=0;
  2662. do {
  2663. list($xmin,$min) = $aPlots[$i]->Min();
  2664. } while( ++$i < $n && !is_numeric($min) );
  2665. if( !is_numeric($min) || !is_numeric($max) ) {
  2666. JpGraphError::RaiseL(25044);//('Cannot use autoscaling since it is impossible to determine a valid min/max value of the Y-axis (only null values).');
  2667. }
  2668. list($xmax,$max) = $aPlots[0]->Max();
  2669. list($xmin,$min) = $aPlots[0]->Min();
  2670. for($i=0; $i < count($aPlots); ++$i ) {
  2671. list($xmax,$ymax)=$aPlots[$i]->Max();
  2672. list($xmin,$ymin)=$aPlots[$i]->Min();
  2673. if (is_numeric($ymax)) $max=max($max,$ymax);
  2674. if (is_numeric($ymin)) $min=min($min,$ymin);
  2675. }
  2676. if( $min == '' ) $min = 0;
  2677. if( $max == '' ) $max = 0;
  2678. if( $min == 0 && $max == 0 ) {
  2679. // Special case if all values are 0
  2680. $min=0;$max=1;
  2681. }
  2682. return array($min,$max);
  2683. }
  2684. } // Class
  2685. //===================================================
  2686. // CLASS TTF
  2687. // Description: Handle TTF font names
  2688. //===================================================
  2689. class TTF {
  2690. var $font_files,$style_names;
  2691. //---------------
  2692. // CONSTRUCTOR
  2693. function TTF() {
  2694. $this->style_names=array(FS_NORMAL=>'normal',FS_BOLD=>'bold',FS_ITALIC=>'italic',FS_BOLDITALIC=>'bolditalic');
  2695. // File names for available fonts
  2696. $this->font_files=array(
  2697. FF_COURIER => array(FS_NORMAL=>'cour.ttf', FS_BOLD=>'courbd.ttf', FS_ITALIC=>'couri.ttf', FS_BOLDITALIC=>'courbi.ttf' ),
  2698. FF_GEORGIA => array(FS_NORMAL=>'georgia.ttf', FS_BOLD=>'georgiab.ttf', FS_ITALIC=>'georgiai.ttf', FS_BOLDITALIC=>'' ),
  2699. FF_TREBUCHE =>array(FS_NORMAL=>'trebuc.ttf', FS_BOLD=>'trebucbd.ttf', FS_ITALIC=>'trebucit.ttf', FS_BOLDITALIC=>'trebucbi.ttf' ),
  2700. FF_VERDANA => array(FS_NORMAL=>'verdana.ttf', FS_BOLD=>'verdanab.ttf', FS_ITALIC=>'verdanai.ttf', FS_BOLDITALIC=>'' ),
  2701. FF_TIMES => array(FS_NORMAL=>'times.ttf', FS_BOLD=>'timesbd.ttf', FS_ITALIC=>'timesi.ttf', FS_BOLDITALIC=>'timesbi.ttf' ),
  2702. FF_COMIC => array(FS_NORMAL=>'comic.ttf', FS_BOLD=>'comicbd.ttf', FS_ITALIC=>'', FS_BOLDITALIC=>'' ),
  2703. FF_ARIAL => array(FS_NORMAL=>'arial.ttf', FS_BOLD=>'arialbd.ttf', FS_ITALIC=>'ariali.ttf', FS_BOLDITALIC=>'arialbi.ttf' ) ,
  2704. FF_VERA => array(FS_NORMAL=>'Vera.ttf', FS_BOLD=>'VeraBd.ttf', FS_ITALIC=>'VeraIt.ttf', FS_BOLDITALIC=>'VeraBI.ttf' ),
  2705. FF_VERAMONO => array(FS_NORMAL=>'VeraMono.ttf', FS_BOLD=>'VeraMoBd.ttf', FS_ITALIC=>'VeraMoIt.ttf', FS_BOLDITALIC=>'VeraMoBI.ttf' ),
  2706. FF_VERASERIF => array(FS_NORMAL=>'VeraSe.ttf', FS_BOLD=>'VeraSeBd.ttf', FS_ITALIC=>'', FS_BOLDITALIC=>'' ) ,
  2707. FF_SIMSUN => array(FS_NORMAL=>'simsun.ttc', FS_BOLD=>'simhei.ttf', FS_ITALIC=>'', FS_BOLDITALIC=>'' ),
  2708. FF_CHINESE => array(FS_NORMAL=>CHINESE_TTF_FONT, FS_BOLD=>'', FS_ITALIC=>'', FS_BOLDITALIC=>'' ),
  2709. FF_MINCHO => array(FS_NORMAL=>MINCHO_TTF_FONT, FS_BOLD=>'', FS_ITALIC=>'', FS_BOLDITALIC=>'' ),
  2710. FF_PMINCHO => array(FS_NORMAL=>PMINCHO_TTF_FONT, FS_BOLD=>'', FS_ITALIC=>'', FS_BOLDITALIC=>'' ),
  2711. FF_GOTHIC => array(FS_NORMAL=>GOTHIC_TTF_FONT, FS_BOLD=>'', FS_ITALIC=>'', FS_BOLDITALIC=>'' ),
  2712. FF_PGOTHIC => array(FS_NORMAL=>PGOTHIC_TTF_FONT, FS_BOLD=>'', FS_ITALIC=>'', FS_BOLDITALIC=>'' ),
  2713. FF_MINCHO => array(FS_NORMAL=>PMINCHO_TTF_FONT, FS_BOLD=>'', FS_ITALIC=>'', FS_BOLDITALIC=>'' )
  2714. );
  2715. }
  2716. //---------------
  2717. // PUBLIC METHODS
  2718. // Create the TTF file from the font specification
  2719. function File($family,$style=FS_NORMAL) {
  2720. if( $family == FF_HANDWRT || $family==FF_BOOK ) {
  2721. JpGraphError::RaiseL(25045);//('Font families FF_HANDWRT and FF_BOOK are no longer available due to copyright problem with these fonts. Fonts can no longer be distributed with JpGraph. Please download fonts from http://corefonts.sourceforge.net/');
  2722. }
  2723. $fam = @$this->font_files[$family];
  2724. if( !$fam ) {
  2725. JpGraphError::RaiseL(25046,$family);//("Specified TTF font family (id=$family) is unknown or does not exist. Please note that TTF fonts are not distributed with JpGraph for copyright reasons. You can find the MS TTF WEB-fonts (arial, courier etc) for download at http://corefonts.sourceforge.net/");
  2726. }
  2727. $f = @$fam[$style];
  2728. if( $f==='' )
  2729. JpGraphError::RaiseL(25047,$this->style_names[$style],$this->font_files[$family][FS_NORMAL]);//('Style "'.$this->style_names[$style].'" is not available for font family '.$this->font_files[$family][FS_NORMAL].'.');
  2730. if( !$f ) {
  2731. JpGraphError::RaiseL(25048,$fam);//("Unknown font style specification [$fam].");
  2732. }
  2733. if ($family >= FF_MINCHO && $family <= FF_PGOTHIC) {
  2734. $f = MBTTF_DIR.$f;
  2735. } else {
  2736. $f = TTF_DIR.$f;
  2737. }
  2738. if( file_exists($f) === false || is_readable($f) === false ) {
  2739. JpGraphError::RaiseL(25049,$f);//("Font file \"$f\" is not readable or does not exist.");
  2740. }
  2741. return $f;
  2742. }
  2743. } // Class
  2744. //===================================================
  2745. // CLASS LineProperty
  2746. // Description: Holds properties for a line
  2747. //===================================================
  2748. class LineProperty {
  2749. var $iWeight=1, $iColor="black",$iStyle="solid";
  2750. var $iShow=true;
  2751. //---------------
  2752. // PUBLIC METHODS
  2753. function SetColor($aColor) {
  2754. $this->iColor = $aColor;
  2755. }
  2756. function SetWeight($aWeight) {
  2757. $this->iWeight = $aWeight;
  2758. }
  2759. function SetStyle($aStyle) {
  2760. $this->iStyle = $aStyle;
  2761. }
  2762. function Show($aShow=true) {
  2763. $this->iShow=$aShow;
  2764. }
  2765. function Stroke(&$aImg,$aX1,$aY1,$aX2,$aY2) {
  2766. if( $this->iShow ) {
  2767. $aImg->PushColor($this->iColor);
  2768. $oldls = $aImg->line_style;
  2769. $oldlw = $aImg->line_weight;
  2770. $aImg->SetLineWeight($this->iWeight);
  2771. $aImg->SetLineStyle($this->iStyle);
  2772. $aImg->StyleLine($aX1,$aY1,$aX2,$aY2);
  2773. $aImg->PopColor($this->iColor);
  2774. $aImg->line_style = $oldls;
  2775. $aImg->line_weight = $oldlw;
  2776. }
  2777. }
  2778. }
  2779. //===================================================
  2780. // CLASS Text
  2781. // Description: Arbitrary text object that can be added to the graph
  2782. //===================================================
  2783. class Text {
  2784. var $t,$x=0,$y=0,$halign="left",$valign="top",$color=array(0,0,0);
  2785. var $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12;
  2786. var $hide=false, $dir=0;
  2787. var $boxed=false; // Should the text be boxed
  2788. var $paragraph_align="left";
  2789. var $margin=0;
  2790. var $icornerradius=0,$ishadowwidth=3;
  2791. var $iScalePosY=null,$iScalePosX=null;
  2792. var $iWordwrap=0;
  2793. var $fcolor='white',$bcolor='black',$shadow=false;
  2794. var $iCSIMarea='',$iCSIMalt='',$iCSIMtarget='';
  2795. //---------------
  2796. // CONSTRUCTOR
  2797. // Create new text at absolute pixel coordinates
  2798. function Text($aTxt="",$aXAbsPos=0,$aYAbsPos=0) {
  2799. if( ! is_string($aTxt) ) {
  2800. JpGraphError::RaiseL(25050);//('First argument to Text::Text() must be s atring.');
  2801. }
  2802. $this->t = $aTxt;
  2803. $this->x = round($aXAbsPos);
  2804. $this->y = round($aYAbsPos);
  2805. $this->margin = 0;
  2806. }
  2807. //---------------
  2808. // PUBLIC METHODS
  2809. // Set the string in the text object
  2810. function Set($aTxt) {
  2811. $this->t = $aTxt;
  2812. }
  2813. // Alias for Pos()
  2814. function SetPos($aXAbsPos=0,$aYAbsPos=0,$aHAlign="left",$aVAlign="top") {
  2815. $this->Pos($aXAbsPos,$aYAbsPos,$aHAlign,$aVAlign);
  2816. }
  2817. // Specify the position and alignment for the text object
  2818. function Pos($aXAbsPos=0,$aYAbsPos=0,$aHAlign="left",$aVAlign="top") {
  2819. $this->x = $aXAbsPos;
  2820. $this->y = $aYAbsPos;
  2821. $this->halign = $aHAlign;
  2822. $this->valign = $aVAlign;
  2823. }
  2824. function SetScalePos($aX,$aY) {
  2825. $this->iScalePosX = $aX;
  2826. $this->iScalePosY = $aY;
  2827. }
  2828. // Specify alignment for the text
  2829. function Align($aHAlign,$aVAlign="top",$aParagraphAlign="") {
  2830. $this->halign = $aHAlign;
  2831. $this->valign = $aVAlign;
  2832. if( $aParagraphAlign != "" )
  2833. $this->paragraph_align = $aParagraphAlign;
  2834. }
  2835. // Alias
  2836. function SetAlign($aHAlign,$aVAlign="top",$aParagraphAlign="") {
  2837. $this->Align($aHAlign,$aVAlign,$aParagraphAlign);
  2838. }
  2839. // Specifies the alignment for a multi line text
  2840. function ParagraphAlign($aAlign) {
  2841. $this->paragraph_align = $aAlign;
  2842. }
  2843. // Specifies the alignment for a multi line text
  2844. function SetParagraphAlign($aAlign) {
  2845. $this->paragraph_align = $aAlign;
  2846. }
  2847. function SetShadow($aShadowColor='gray',$aShadowWidth=3) {
  2848. $this->ishadowwidth=$aShadowWidth;
  2849. $this->shadow=$aShadowColor;
  2850. $this->boxed=true;
  2851. }
  2852. function SetWordWrap($aCol) {
  2853. $this->iWordwrap = $aCol ;
  2854. }
  2855. // Specify that the text should be boxed. fcolor=frame color, bcolor=border color,
  2856. // $shadow=drop shadow should be added around the text.
  2857. function SetBox($aFrameColor=array(255,255,255),$aBorderColor=array(0,0,0),$aShadowColor=false,$aCornerRadius=4,$aShadowWidth=3) {
  2858. if( $aFrameColor==false )
  2859. $this->boxed=false;
  2860. else
  2861. $this->boxed=true;
  2862. $this->fcolor=$aFrameColor;
  2863. $this->bcolor=$aBorderColor;
  2864. // For backwards compatibility when shadow was just true or false
  2865. if( $aShadowColor === true )
  2866. $aShadowColor = 'gray';
  2867. $this->shadow=$aShadowColor;
  2868. $this->icornerradius=$aCornerRadius;
  2869. $this->ishadowwidth=$aShadowWidth;
  2870. }
  2871. // Hide the text
  2872. function Hide($aHide=true) {
  2873. $this->hide=$aHide;
  2874. }
  2875. // This looks ugly since it's not a very orthogonal design
  2876. // but I added this "inverse" of Hide() to harmonize
  2877. // with some classes which I designed more recently (especially)
  2878. // jpgraph_gantt
  2879. function Show($aShow=true) {
  2880. $this->hide=!$aShow;
  2881. }
  2882. // Specify font
  2883. function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
  2884. $this->font_family=$aFamily;
  2885. $this->font_style=$aStyle;
  2886. $this->font_size=$aSize;
  2887. }
  2888. // Center the text between $left and $right coordinates
  2889. function Center($aLeft,$aRight,$aYAbsPos=false) {
  2890. $this->x = $aLeft + ($aRight-$aLeft )/2;
  2891. $this->halign = "center";
  2892. if( is_numeric($aYAbsPos) )
  2893. $this->y = $aYAbsPos;
  2894. }
  2895. // Set text color
  2896. function SetColor($aColor) {
  2897. $this->color = $aColor;
  2898. }
  2899. function SetAngle($aAngle) {
  2900. $this->SetOrientation($aAngle);
  2901. }
  2902. // Orientation of text. Note only TTF fonts can have an arbitrary angle
  2903. function SetOrientation($aDirection=0) {
  2904. if( is_numeric($aDirection) )
  2905. $this->dir=$aDirection;
  2906. elseif( $aDirection=="h" )
  2907. $this->dir = 0;
  2908. elseif( $aDirection=="v" )
  2909. $this->dir = 90;
  2910. else JpGraphError::RaiseL(25051);//(" Invalid direction specified for text.");
  2911. }
  2912. // Total width of text
  2913. function GetWidth(&$aImg) {
  2914. $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  2915. $w = $aImg->GetTextWidth($this->t,$this->dir);
  2916. return $w;
  2917. }
  2918. // Hight of font
  2919. function GetFontHeight(&$aImg) {
  2920. $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  2921. $h = $aImg->GetFontHeight();
  2922. return $h;
  2923. }
  2924. function GetTextHeight(&$aImg) {
  2925. $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  2926. $h = $aImg->GetTextHeight($this->t,$this->dir);
  2927. return $h;
  2928. }
  2929. function GetHeight(&$aImg) {
  2930. // Synonym for GetTextHeight()
  2931. $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  2932. $h = $aImg->GetTextHeight($this->t,$this->dir);
  2933. return $h;
  2934. }
  2935. // Set the margin which will be interpretated differently depending
  2936. // on the context.
  2937. function SetMargin($aMarg) {
  2938. $this->margin = $aMarg;
  2939. }
  2940. function StrokeWithScale(&$aImg,$axscale,$ayscale) {
  2941. if( $this->iScalePosX === null ||
  2942. $this->iScalePosY === null ) {
  2943. $this->Stroke($aImg);
  2944. }
  2945. else {
  2946. $this->Stroke($aImg,
  2947. round($axscale->Translate($this->iScalePosX)),
  2948. round($ayscale->Translate($this->iScalePosY)));
  2949. }
  2950. }
  2951. function SetCSIMTarget($aTarget,$aAlt=null) {
  2952. $this->iCSIMtarget = $aTarget;
  2953. $this->iCSIMalt = $aAlt;
  2954. }
  2955. function GetCSIMareas() {
  2956. if( $this->iCSIMtarget !== '' )
  2957. return $this->iCSIMarea;
  2958. else
  2959. return '';
  2960. }
  2961. // Display text in image
  2962. function Stroke(&$aImg,$x=null,$y=null) {
  2963. if( !empty($x) ) $this->x = round($x);
  2964. if( !empty($y) ) $this->y = round($y);
  2965. // Insert newlines
  2966. if( $this->iWordwrap > 0 ) {
  2967. $this->t = wordwrap($this->t,$this->iWordwrap,"\n");
  2968. }
  2969. // If position been given as a fraction of the image size
  2970. // calculate the absolute position
  2971. if( $this->x < 1 && $this->x > 0 ) $this->x *= $aImg->width;
  2972. if( $this->y < 1 && $this->y > 0 ) $this->y *= $aImg->height;
  2973. $aImg->PushColor($this->color);
  2974. $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  2975. $aImg->SetTextAlign($this->halign,$this->valign);
  2976. if( $this->boxed ) {
  2977. if( $this->fcolor=="nofill" )
  2978. $this->fcolor=false;
  2979. $aImg->SetLineWeight(1);
  2980. $bbox = $aImg->StrokeBoxedText($this->x,$this->y,$this->t,
  2981. $this->dir,$this->fcolor,$this->bcolor,$this->shadow,
  2982. $this->paragraph_align,5,5,$this->icornerradius,
  2983. $this->ishadowwidth);
  2984. }
  2985. else {
  2986. $bbox = $aImg->StrokeText($this->x,$this->y,$this->t,$this->dir,$this->paragraph_align);
  2987. }
  2988. // Create CSIM targets
  2989. $coords = $bbox[0].','.$bbox[1].','.$bbox[2].','.$bbox[3].','.$bbox[4].','.$bbox[5].','.$bbox[6].','.$bbox[7];
  2990. $this->iCSIMarea = "<area shape=\"poly\" coords=\"$coords\" href=\"".$this->iCSIMtarget."\"";
  2991. $this->iCSIMarea .= " alt=\"".$this->iCSIMalt."\" title=\"".$this->iCSIMalt."\" />\n";
  2992. $aImg->PopColor($this->color);
  2993. }
  2994. } // Class
  2995. class GraphTabTitle extends Text{
  2996. var $corner = 6 , $posx = 7, $posy = 4;
  2997. var $color='darkred',$fillcolor='lightyellow',$bordercolor='black';
  2998. var $align = 'left', $width=TABTITLE_WIDTHFIT;
  2999. function GraphTabTitle() {
  3000. $this->t = '';
  3001. $this->font_style = FS_BOLD;
  3002. $this->hide = true;
  3003. }
  3004. function SetColor($aTxtColor,$aFillColor='lightyellow',$aBorderColor='black') {
  3005. $this->color = $aTxtColor;
  3006. $this->fillcolor = $aFillColor;
  3007. $this->bordercolor = $aBorderColor;
  3008. }
  3009. function SetFillColor($aFillColor) {
  3010. $this->fillcolor = $aFillColor;
  3011. }
  3012. function SetTabAlign($aAlign) {
  3013. // Synonym for SetPos
  3014. $this->align = $aAlign;
  3015. }
  3016. function SetPos($aAlign) {
  3017. $this->align = $aAlign;
  3018. }
  3019. function SetWidth($aWidth) {
  3020. $this->width = $aWidth ;
  3021. }
  3022. function Set($t) {
  3023. $this->t = $t;
  3024. $this->hide = false;
  3025. }
  3026. function SetCorner($aD) {
  3027. $this->corner = $aD ;
  3028. }
  3029. function Stroke(&$aImg) {
  3030. if( $this->hide )
  3031. return;
  3032. $this->boxed = false;
  3033. $w = $this->GetWidth($aImg) + 2*$this->posx;
  3034. $h = $this->GetTextHeight($aImg) + 2*$this->posy;
  3035. $x = $aImg->left_margin;
  3036. $y = $aImg->top_margin;
  3037. if( $this->width === TABTITLE_WIDTHFIT ) {
  3038. if( $this->align == 'left' ) {
  3039. $p = array($x, $y,
  3040. $x, $y-$h+$this->corner,
  3041. $x + $this->corner,$y-$h,
  3042. $x + $w - $this->corner, $y-$h,
  3043. $x + $w, $y-$h+$this->corner,
  3044. $x + $w, $y);
  3045. }
  3046. elseif( $this->align == 'center' ) {
  3047. $x += round($aImg->plotwidth/2) - round($w/2);
  3048. $p = array($x, $y,
  3049. $x, $y-$h+$this->corner,
  3050. $x + $this->corner, $y-$h,
  3051. $x + $w - $this->corner, $y-$h,
  3052. $x + $w, $y-$h+$this->corner,
  3053. $x + $w, $y);
  3054. }
  3055. else {
  3056. $x += $aImg->plotwidth -$w;
  3057. $p = array($x, $y,
  3058. $x, $y-$h+$this->corner,
  3059. $x + $this->corner,$y-$h,
  3060. $x + $w - $this->corner, $y-$h,
  3061. $x + $w, $y-$h+$this->corner,
  3062. $x + $w, $y);
  3063. }
  3064. }
  3065. else {
  3066. if( $this->width === TABTITLE_WIDTHFULL )
  3067. $w = $aImg->plotwidth ;
  3068. else
  3069. $w = $this->width ;
  3070. // Make the tab fit the width of the plot area
  3071. $p = array($x, $y,
  3072. $x, $y-$h+$this->corner,
  3073. $x + $this->corner,$y-$h,
  3074. $x + $w - $this->corner, $y-$h,
  3075. $x + $w, $y-$h+$this->corner,
  3076. $x + $w, $y);
  3077. }
  3078. if( $this->halign == 'left' ) {
  3079. $aImg->SetTextAlign('left','bottom');
  3080. $x += $this->posx;
  3081. $y -= $this->posy;
  3082. }
  3083. elseif( $this->halign == 'center' ) {
  3084. $aImg->SetTextAlign('center','bottom');
  3085. $x += $w/2;
  3086. $y -= $this->posy;
  3087. }
  3088. else {
  3089. $aImg->SetTextAlign('right','bottom');
  3090. $x += $w - $this->posx;
  3091. $y -= $this->posy;
  3092. }
  3093. $aImg->SetColor($this->fillcolor);
  3094. $aImg->FilledPolygon($p);
  3095. $aImg->SetColor($this->bordercolor);
  3096. $aImg->Polygon($p,true);
  3097. $aImg->SetColor($this->color);
  3098. $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  3099. $aImg->StrokeText($x,$y,$this->t,0,'center');
  3100. }
  3101. }
  3102. //===================================================
  3103. // CLASS SuperScriptText
  3104. // Description: Format a superscript text
  3105. //===================================================
  3106. class SuperScriptText extends Text {
  3107. var $iSuper="";
  3108. var $sfont_family="",$sfont_style="",$sfont_size=8;
  3109. var $iSuperMargin=2,$iVertOverlap=4,$iSuperScale=0.65;
  3110. var $iSDir=0;
  3111. var $iSimple=false;
  3112. function SuperScriptText($aTxt="",$aSuper="",$aXAbsPos=0,$aYAbsPos=0) {
  3113. parent::Text($aTxt,$aXAbsPos,$aYAbsPos);
  3114. $this->iSuper = $aSuper;
  3115. }
  3116. function FromReal($aVal,$aPrecision=2) {
  3117. // Convert a floating point number to scientific notation
  3118. $neg=1.0;
  3119. if( $aVal < 0 ) {
  3120. $neg = -1.0;
  3121. $aVal = -$aVal;
  3122. }
  3123. $l = floor(log10($aVal));
  3124. $a = sprintf("%0.".$aPrecision."f",round($aVal / pow(10,$l),$aPrecision));
  3125. $a *= $neg;
  3126. if( $this->iSimple && ($a == 1 || $a==-1) ) $a = '';
  3127. if( $a != '' )
  3128. $this->t = $a.' * 10';
  3129. else {
  3130. if( $neg == 1 )
  3131. $this->t = '10';
  3132. else
  3133. $this->t = '-10';
  3134. }
  3135. $this->iSuper = $l;
  3136. }
  3137. function Set($aTxt,$aSuper="") {
  3138. $this->t = $aTxt;
  3139. $this->iSuper = $aSuper;
  3140. }
  3141. function SetSuperFont($aFontFam,$aFontStyle=FS_NORMAL,$aFontSize=8) {
  3142. $this->sfont_family = $aFontFam;
  3143. $this->sfont_style = $aFontStyle;
  3144. $this->sfont_size = $aFontSize;
  3145. }
  3146. // Total width of text
  3147. function GetWidth(&$aImg) {
  3148. $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  3149. $w = $aImg->GetTextWidth($this->t);
  3150. $aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size);
  3151. $w += $aImg->GetTextWidth($this->iSuper);
  3152. $w += $this->iSuperMargin;
  3153. return $w;
  3154. }
  3155. // Hight of font (approximate the height of the text)
  3156. function GetFontHeight(&$aImg) {
  3157. $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  3158. $h = $aImg->GetFontHeight();
  3159. $aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size);
  3160. $h += $aImg->GetFontHeight();
  3161. return $h;
  3162. }
  3163. // Hight of text
  3164. function GetTextHeight(&$aImg) {
  3165. $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  3166. $h = $aImg->GetTextHeight($this->t);
  3167. $aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size);
  3168. $h += $aImg->GetTextHeight($this->iSuper);
  3169. return $h;
  3170. }
  3171. function Stroke(&$aImg,$ax=-1,$ay=-1) {
  3172. // To position the super script correctly we need different
  3173. // cases to handle the alignmewnt specified since that will
  3174. // determine how we can interpret the x,y coordinates
  3175. $w = parent::GetWidth($aImg);
  3176. $h = parent::GetTextHeight($aImg);
  3177. switch( $this->valign ) {
  3178. case 'top':
  3179. $sy = $this->y;
  3180. break;
  3181. case 'center':
  3182. $sy = $this->y - $h/2;
  3183. break;
  3184. case 'bottom':
  3185. $sy = $this->y - $h;
  3186. break;
  3187. default:
  3188. JpGraphError::RaiseL(25052);//('PANIC: Internal error in SuperScript::Stroke(). Unknown vertical alignment for text');
  3189. exit();
  3190. }
  3191. switch( $this->halign ) {
  3192. case 'left':
  3193. $sx = $this->x + $w;
  3194. break;
  3195. case 'center':
  3196. $sx = $this->x + $w/2;
  3197. break;
  3198. case 'right':
  3199. $sx = $this->x;
  3200. break;
  3201. default:
  3202. JpGraphError::RaiseL(25053);//('PANIC: Internal error in SuperScript::Stroke(). Unknown horizontal alignment for text');
  3203. exit();
  3204. }
  3205. $sx += $this->iSuperMargin;
  3206. $sy += $this->iVertOverlap;
  3207. // Should we automatically determine the font or
  3208. // has the user specified it explicetly?
  3209. if( $this->sfont_family == "" ) {
  3210. if( $this->font_family <= FF_FONT2 ) {
  3211. if( $this->font_family == FF_FONT0 ) {
  3212. $sff = FF_FONT0;
  3213. }
  3214. elseif( $this->font_family == FF_FONT1 ) {
  3215. if( $this->font_style == FS_NORMAL )
  3216. $sff = FF_FONT0;
  3217. else
  3218. $sff = FF_FONT1;
  3219. }
  3220. else {
  3221. $sff = FF_FONT1;
  3222. }
  3223. $sfs = $this->font_style;
  3224. $sfz = $this->font_size;
  3225. }
  3226. else {
  3227. // TTF fonts
  3228. $sff = $this->font_family;
  3229. $sfs = $this->font_style;
  3230. $sfz = floor($this->font_size*$this->iSuperScale);
  3231. if( $sfz < 8 ) $sfz = 8;
  3232. }
  3233. $this->sfont_family = $sff;
  3234. $this->sfont_style = $sfs;
  3235. $this->sfont_size = $sfz;
  3236. }
  3237. else {
  3238. $sff = $this->sfont_family;
  3239. $sfs = $this->sfont_style;
  3240. $sfz = $this->sfont_size;
  3241. }
  3242. parent::Stroke($aImg,$ax,$ay);
  3243. // For the builtin fonts we need to reduce the margins
  3244. // since the bounding bx reported for the builtin fonts
  3245. // are much larger than for the TTF fonts.
  3246. if( $sff <= FF_FONT2 ) {
  3247. $sx -= 2;
  3248. $sy += 3;
  3249. }
  3250. $aImg->SetTextAlign('left','bottom');
  3251. $aImg->SetFont($sff,$sfs,$sfz);
  3252. $aImg->PushColor($this->color);
  3253. $aImg->StrokeText($sx,$sy,$this->iSuper,$this->iSDir,'left');
  3254. $aImg->PopColor();
  3255. }
  3256. }
  3257. //===================================================
  3258. // CLASS Grid
  3259. // Description: responsible for drawing grid lines in graph
  3260. //===================================================
  3261. class Grid {
  3262. var $img;
  3263. var $scale;
  3264. var $grid_color='#DDDDDD',$grid_mincolor='#DDDDDD';
  3265. var $type="solid";
  3266. var $show=false, $showMinor=false,$weight=1;
  3267. var $fill=false,$fillcolor=array('#EFEFEF','#BBCCFF');
  3268. //---------------
  3269. // CONSTRUCTOR
  3270. function Grid(&$aAxis) {
  3271. $this->scale = &$aAxis->scale;
  3272. $this->img = &$aAxis->img;
  3273. }
  3274. //---------------
  3275. // PUBLIC METHODS
  3276. function SetColor($aMajColor,$aMinColor=false) {
  3277. $this->grid_color=$aMajColor;
  3278. if( $aMinColor === false )
  3279. $aMinColor = $aMajColor ;
  3280. $this->grid_mincolor = $aMinColor;
  3281. }
  3282. function SetWeight($aWeight) {
  3283. $this->weight=$aWeight;
  3284. }
  3285. // Specify if grid should be dashed, dotted or solid
  3286. function SetLineStyle($aType) {
  3287. $this->type = $aType;
  3288. }
  3289. // Decide if both major and minor grid should be displayed
  3290. function Show($aShowMajor=true,$aShowMinor=false) {
  3291. $this->show=$aShowMajor;
  3292. $this->showMinor=$aShowMinor;
  3293. }
  3294. function SetFill($aFlg=true,$aColor1='lightgray',$aColor2='lightblue') {
  3295. $this->fill = $aFlg;
  3296. $this->fillcolor = array( $aColor1, $aColor2 );
  3297. }
  3298. // Display the grid
  3299. function Stroke() {
  3300. if( $this->showMinor ) {
  3301. $tmp = $this->grid_color;
  3302. $this->grid_color = $this->grid_mincolor;
  3303. $this->DoStroke($this->scale->ticks->ticks_pos);
  3304. $this->grid_color = $tmp;
  3305. $this->DoStroke($this->scale->ticks->maj_ticks_pos);
  3306. }
  3307. else {
  3308. $this->DoStroke($this->scale->ticks->maj_ticks_pos);
  3309. }
  3310. }
  3311. //--------------
  3312. // Private methods
  3313. // Draw the grid
  3314. function DoStroke(&$aTicksPos) {
  3315. if( !$this->show )
  3316. return;
  3317. $nbrgrids = count($aTicksPos);
  3318. if( $this->scale->type=="y" ) {
  3319. $xl=$this->img->left_margin;
  3320. $xr=$this->img->width-$this->img->right_margin;
  3321. if( $this->fill ) {
  3322. // Draw filled areas
  3323. $y2 = $aTicksPos[0];
  3324. $i=1;
  3325. while( $i < $nbrgrids ) {
  3326. $y1 = $y2;
  3327. $y2 = $aTicksPos[$i++];
  3328. $this->img->SetColor($this->fillcolor[$i & 1]);
  3329. $this->img->FilledRectangle($xl,$y1,$xr,$y2);
  3330. }
  3331. }
  3332. $this->img->SetColor($this->grid_color);
  3333. $this->img->SetLineWeight($this->weight);
  3334. // Draw grid lines
  3335. for($i=0; $i<$nbrgrids; ++$i) {
  3336. $y=$aTicksPos[$i];
  3337. if( $this->type == "solid" )
  3338. $this->img->Line($xl,$y,$xr,$y);
  3339. elseif( $this->type == "dotted" )
  3340. $this->img->DashedLine($xl,$y,$xr,$y,1,6);
  3341. elseif( $this->type == "dashed" )
  3342. $this->img->DashedLine($xl,$y,$xr,$y,2,4);
  3343. elseif( $this->type == "longdashed" )
  3344. $this->img->DashedLine($xl,$y,$xr,$y,8,6);
  3345. }
  3346. }
  3347. elseif( $this->scale->type=="x" ) {
  3348. $yu=$this->img->top_margin;
  3349. $yl=$this->img->height-$this->img->bottom_margin;
  3350. $limit=$this->img->width-$this->img->right_margin;
  3351. if( $this->fill ) {
  3352. // Draw filled areas
  3353. $x2 = $aTicksPos[0];
  3354. $i=1;
  3355. while( $i < $nbrgrids ) {
  3356. $x1 = $x2;
  3357. $x2 = min($aTicksPos[$i++],$limit) ;
  3358. $this->img->SetColor($this->fillcolor[$i & 1]);
  3359. $this->img->FilledRectangle($x1,$yu,$x2,$yl);
  3360. }
  3361. }
  3362. $this->img->SetColor($this->grid_color);
  3363. $this->img->SetLineWeight($this->weight);
  3364. // We must also test for limit since we might have
  3365. // an offset and the number of ticks is calculated with
  3366. // assumption offset==0 so we might end up drawing one
  3367. // to many gridlines
  3368. $i=0;
  3369. $x=$aTicksPos[$i];
  3370. while( $i<count($aTicksPos) && ($x=$aTicksPos[$i]) <= $limit ) {
  3371. if( $this->type == "solid" )
  3372. $this->img->Line($x,$yl,$x,$yu);
  3373. elseif( $this->type == "dotted" )
  3374. $this->img->DashedLine($x,$yl,$x,$yu,1,6);
  3375. elseif( $this->type == "dashed" )
  3376. $this->img->DashedLine($x,$yl,$x,$yu,2,4);
  3377. elseif( $this->type == "longdashed" )
  3378. $this->img->DashedLine($x,$yl,$x,$yu,8,6);
  3379. ++$i;
  3380. }
  3381. }
  3382. else {
  3383. JpGraphError::RaiseL(25054,$this->scale->type);//('Internal error: Unknown grid axis ['.$this->scale->type.']');
  3384. }
  3385. return true;
  3386. }
  3387. } // Class
  3388. //===================================================
  3389. // CLASS Axis
  3390. // Description: Defines X and Y axis. Notes that at the
  3391. // moment the code is not really good since the axis on
  3392. // several occasion must know wheter it's an X or Y axis.
  3393. // This was a design decision to make the code easier to
  3394. // follow.
  3395. //===================================================
  3396. class Axis {
  3397. var $pos = false;
  3398. var $weight=1;
  3399. var $color=array(0,0,0),$label_color=array(0,0,0);
  3400. var $img=null,$scale=null;
  3401. var $hide=false;
  3402. var $ticks_label=false, $ticks_label_colors=null;
  3403. var $show_first_label=true,$show_last_label=true;
  3404. var $label_step=1; // Used by a text axis to specify what multiple of major steps
  3405. // should be labeled.
  3406. var $tick_step=1;
  3407. var $labelPos=0; // Which side of the axis should the labels be?
  3408. var $title=null,$title_adjust,$title_margin,$title_side=SIDE_LEFT;
  3409. var $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12,$label_angle=0;
  3410. var $tick_label_margin=5;
  3411. var $label_halign = '',$label_valign = '', $label_para_align='left';
  3412. var $hide_line=false,$hide_labels=false;
  3413. var $iDeltaAbsPos=0;
  3414. //var $hide_zero_label=false;
  3415. //---------------
  3416. // CONSTRUCTOR
  3417. function Axis(&$img,&$aScale,$color=array(0,0,0)) {
  3418. $this->img = &$img;
  3419. $this->scale = &$aScale;
  3420. $this->color = $color;
  3421. $this->title=new Text("");
  3422. if( $aScale->type=="y" ) {
  3423. $this->title_margin = 25;
  3424. $this->title_adjust="middle";
  3425. $this->title->SetOrientation(90);
  3426. $this->tick_label_margin=7;
  3427. $this->labelPos=SIDE_LEFT;
  3428. //$this->SetLabelFormat('%.1f');
  3429. }
  3430. else {
  3431. $this->title_margin = 5;
  3432. $this->title_adjust="high";
  3433. $this->title->SetOrientation(0);
  3434. $this->tick_label_margin=5;
  3435. $this->labelPos=SIDE_DOWN;
  3436. $this->title_side=SIDE_DOWN;
  3437. //$this->SetLabelFormat('%.0f');
  3438. }
  3439. }
  3440. //---------------
  3441. // PUBLIC METHODS
  3442. function SetLabelFormat($aFormStr) {
  3443. $this->scale->ticks->SetLabelFormat($aFormStr);
  3444. }
  3445. function SetLabelFormatString($aFormStr,$aDate=false) {
  3446. $this->scale->ticks->SetLabelFormat($aFormStr,$aDate);
  3447. }
  3448. function SetLabelFormatCallback($aFuncName) {
  3449. $this->scale->ticks->SetFormatCallback($aFuncName);
  3450. }
  3451. function SetLabelAlign($aHAlign,$aVAlign="top",$aParagraphAlign='left') {
  3452. $this->label_halign = $aHAlign;
  3453. $this->label_valign = $aVAlign;
  3454. $this->label_para_align = $aParagraphAlign;
  3455. }
  3456. // Don't display the first label
  3457. function HideFirstTickLabel($aShow=false) {
  3458. $this->show_first_label=$aShow;
  3459. }
  3460. function HideLastTickLabel($aShow=false) {
  3461. $this->show_last_label=$aShow;
  3462. }
  3463. // Manually specify the major and (optional) minor tick position and labels
  3464. function SetTickPositions($aMajPos,$aMinPos=NULL,$aLabels=NULL) {
  3465. $this->scale->ticks->SetTickPositions($aMajPos,$aMinPos,$aLabels);
  3466. }
  3467. // Manually specify major tick positions and optional labels
  3468. function SetMajTickPositions($aMajPos,$aLabels=NULL) {
  3469. $this->scale->ticks->SetTickPositions($aMajPos,NULL,$aLabels);
  3470. }
  3471. // Hide minor or major tick marks
  3472. function HideTicks($aHideMinor=true,$aHideMajor=true) {
  3473. $this->scale->ticks->SupressMinorTickMarks($aHideMinor);
  3474. $this->scale->ticks->SupressTickMarks($aHideMajor);
  3475. }
  3476. // Hide zero label
  3477. function HideZeroLabel($aFlag=true) {
  3478. $this->scale->ticks->SupressZeroLabel();
  3479. //$this->hide_zero_label = $aFlag;
  3480. }
  3481. function HideFirstLastLabel() {
  3482. // The two first calls to ticks method will supress
  3483. // automatically generated scale values. However, that
  3484. // will not affect manually specified value, e.g text-scales.
  3485. // therefor we also make a kludge here to supress manually
  3486. // specified scale labels.
  3487. $this->scale->ticks->SupressLast();
  3488. $this->scale->ticks->SupressFirst();
  3489. $this->show_first_label = false;
  3490. $this->show_last_label = false;
  3491. }
  3492. // Hide the axis
  3493. function Hide($aHide=true) {
  3494. $this->hide=$aHide;
  3495. }
  3496. // Hide the actual axis-line, but still print the labels
  3497. function HideLine($aHide=true) {
  3498. $this->hide_line = $aHide;
  3499. }
  3500. function HideLabels($aHide=true) {
  3501. $this->hide_labels = $aHide;
  3502. }
  3503. // Weight of axis
  3504. function SetWeight($aWeight) {
  3505. $this->weight = $aWeight;
  3506. }
  3507. // Axis color
  3508. function SetColor($aColor,$aLabelColor=false) {
  3509. $this->color = $aColor;
  3510. if( !$aLabelColor ) $this->label_color = $aColor;
  3511. else $this->label_color = $aLabelColor;
  3512. }
  3513. // Title on axis
  3514. function SetTitle($aTitle,$aAdjustAlign="high") {
  3515. $this->title->Set($aTitle);
  3516. $this->title_adjust=$aAdjustAlign;
  3517. }
  3518. // Specify distance from the axis
  3519. function SetTitleMargin($aMargin) {
  3520. $this->title_margin=$aMargin;
  3521. }
  3522. // Which side of the axis should the axis title be?
  3523. function SetTitleSide($aSideOfAxis) {
  3524. $this->title_side = $aSideOfAxis;
  3525. }
  3526. // Utility function to set the direction for tick marks
  3527. function SetTickDirection($aDir) {
  3528. // Will be deprecated from 1.7
  3529. if( ERR_DEPRECATED )
  3530. JpGraphError::RaiseL(25055);//('Axis::SetTickDirection() is deprecated. Use Axis::SetTickSide() instead');
  3531. $this->scale->ticks->SetSide($aDir);
  3532. }
  3533. function SetTickSide($aDir) {
  3534. $this->scale->ticks->SetSide($aDir);
  3535. }
  3536. // Specify text labels for the ticks. One label for each data point
  3537. function SetTickLabels($aLabelArray,$aLabelColorArray=null) {
  3538. $this->ticks_label = $aLabelArray;
  3539. $this->ticks_label_colors = $aLabelColorArray;
  3540. }
  3541. // How far from the axis should the labels be drawn
  3542. function SetTickLabelMargin($aMargin) {
  3543. if( ERR_DEPRECATED )
  3544. JpGraphError::RaiseL(25056);//('SetTickLabelMargin() is deprecated. Use Axis::SetLabelMargin() instead.');
  3545. $this->tick_label_margin=$aMargin;
  3546. }
  3547. function SetLabelMargin($aMargin) {
  3548. $this->tick_label_margin=$aMargin;
  3549. }
  3550. // Specify that every $step of the ticks should be displayed starting
  3551. // at $start
  3552. // DEPRECATED FUNCTION: USE SetTextTickInterval() INSTEAD
  3553. function SetTextTicks($step,$start=0) {
  3554. JpGraphError::RaiseL(25057);//(" SetTextTicks() is deprecated. Use SetTextTickInterval() instead.");
  3555. }
  3556. // Specify that every $step of the ticks should be displayed starting
  3557. // at $start
  3558. function SetTextTickInterval($aStep,$aStart=0) {
  3559. $this->scale->ticks->SetTextLabelStart($aStart);
  3560. $this->tick_step=$aStep;
  3561. }
  3562. // Specify that every $step tick mark should have a label
  3563. // should be displayed starting
  3564. function SetTextLabelInterval($aStep,$aStart=0) {
  3565. if( $aStep < 1 )
  3566. JpGraphError::RaiseL(25058);//(" Text label interval must be specified >= 1.");
  3567. $this->scale->ticks->SetTextLabelStart($aStart);
  3568. $this->label_step=$aStep;
  3569. }
  3570. // Which side of the axis should the labels be on?
  3571. function SetLabelPos($aSidePos) {
  3572. // This will be deprecated from 1.7
  3573. if( ERR_DEPRECATED )
  3574. JpGraphError::RaiseL(25059);//('SetLabelPos() is deprecated. Use Axis::SetLabelSide() instead.');
  3575. $this->labelPos=$aSidePos;
  3576. }
  3577. function SetLabelSide($aSidePos) {
  3578. $this->labelPos=$aSidePos;
  3579. }
  3580. // Set the font
  3581. function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
  3582. $this->font_family = $aFamily;
  3583. $this->font_style = $aStyle;
  3584. $this->font_size = $aSize;
  3585. }
  3586. // Position for axis line on the "other" scale
  3587. function SetPos($aPosOnOtherScale) {
  3588. $this->pos=$aPosOnOtherScale;
  3589. }
  3590. // Set the position of the axis to be X-pixels delta to the right
  3591. // of the max X-position (used to position the multiple Y-axis)
  3592. function SetPosAbsDelta($aDelta) {
  3593. $this->iDeltaAbsPos=$aDelta;
  3594. }
  3595. // Specify the angle for the tick labels
  3596. function SetLabelAngle($aAngle) {
  3597. $this->label_angle = $aAngle;
  3598. }
  3599. // Stroke the axis.
  3600. function Stroke($aOtherAxisScale,$aStrokeLabels=true) {
  3601. if( $this->hide ) return;
  3602. if( is_numeric($this->pos) ) {
  3603. $pos=$aOtherAxisScale->Translate($this->pos);
  3604. }
  3605. else { // Default to minimum of other scale if pos not set
  3606. if( ($aOtherAxisScale->GetMinVal() >= 0 && $this->pos==false) || $this->pos=="min" ) {
  3607. $pos = $aOtherAxisScale->scale_abs[0];
  3608. }
  3609. elseif($this->pos == "max") {
  3610. $pos = $aOtherAxisScale->scale_abs[1];
  3611. }
  3612. else { // If negative set x-axis at 0
  3613. $this->pos=0;
  3614. $pos=$aOtherAxisScale->Translate(0);
  3615. }
  3616. }
  3617. $pos += $this->iDeltaAbsPos;
  3618. $this->img->SetLineWeight($this->weight);
  3619. $this->img->SetColor($this->color);
  3620. $this->img->SetFont($this->font_family,$this->font_style,$this->font_size);
  3621. if( $this->scale->type == "x" ) {
  3622. if( !$this->hide_line )
  3623. $this->img->FilledRectangle($this->img->left_margin,$pos,
  3624. $this->img->width-$this->img->right_margin,$pos+$this->weight-1);
  3625. if( $this->title_side == SIDE_DOWN ) {
  3626. $y = $pos + $this->img->GetFontHeight() + $this->title_margin + $this->title->margin;
  3627. $yalign = 'top';
  3628. }
  3629. else {
  3630. $y = $pos - $this->img->GetFontHeight() - $this->title_margin - $this->title->margin;
  3631. $yalign = 'bottom';
  3632. }
  3633. if( $this->title_adjust=="high" )
  3634. $this->title->Pos($this->img->width-$this->img->right_margin,$y,"right",$yalign);
  3635. elseif( $this->title_adjust=="middle" || $this->title_adjust=="center" )
  3636. $this->title->Pos(($this->img->width-$this->img->left_margin-$this->img->right_margin)/2+$this->img->left_margin,$y,"center",$yalign);
  3637. elseif($this->title_adjust=="low")
  3638. $this->title->Pos($this->img->left_margin,$y,"left",$yalign);
  3639. else {
  3640. JpGraphError::RaiseL(25060,$this->title_adjust);//('Unknown alignment specified for X-axis title. ('.$this->title_adjust.')');
  3641. }
  3642. }
  3643. elseif( $this->scale->type == "y" ) {
  3644. // Add line weight to the height of the axis since
  3645. // the x-axis could have a width>1 and we want the axis to fit nicely together.
  3646. if( !$this->hide_line )
  3647. $this->img->FilledRectangle($pos-$this->weight+1,$this->img->top_margin,
  3648. $pos,$this->img->height-$this->img->bottom_margin+$this->weight-1);
  3649. $x=$pos ;
  3650. if( $this->title_side == SIDE_LEFT ) {
  3651. $x -= $this->title_margin;
  3652. $x -= $this->title->margin;
  3653. $halign="right";
  3654. }
  3655. else {
  3656. $x += $this->title_margin;
  3657. $x += $this->title->margin;
  3658. $halign="left";
  3659. }
  3660. // If the user has manually specified an hor. align
  3661. // then we override the automatic settings with this
  3662. // specifed setting. Since default is 'left' we compare
  3663. // with that. (This means a manually set 'left' align
  3664. // will have no effect.)
  3665. if( $this->title->halign != 'left' )
  3666. $halign = $this->title->halign;
  3667. if( $this->title_adjust=="high" )
  3668. $this->title->Pos($x,$this->img->top_margin,$halign,"top");
  3669. elseif($this->title_adjust=="middle" || $this->title_adjust=="center")
  3670. $this->title->Pos($x,($this->img->height-$this->img->top_margin-$this->img->bottom_margin)/2+$this->img->top_margin,$halign,"center");
  3671. elseif($this->title_adjust=="low")
  3672. $this->title->Pos($x,$this->img->height-$this->img->bottom_margin,$halign,"bottom");
  3673. else
  3674. JpGraphError::RaiseL(25061,$this->title_adjust);//('Unknown alignment specified for Y-axis title. ('.$this->title_adjust.')');
  3675. }
  3676. $this->scale->ticks->Stroke($this->img,$this->scale,$pos);
  3677. if( $aStrokeLabels ) {
  3678. if( !$this->hide_labels )
  3679. $this->StrokeLabels($pos);
  3680. $this->title->Stroke($this->img);
  3681. }
  3682. }
  3683. //---------------
  3684. // PRIVATE METHODS
  3685. // Draw all the tick labels on major tick marks
  3686. function StrokeLabels($aPos,$aMinor=false,$aAbsLabel=false) {
  3687. $this->img->SetColor($this->label_color);
  3688. $this->img->SetFont($this->font_family,$this->font_style,$this->font_size);
  3689. $yoff=$this->img->GetFontHeight()/2;
  3690. // Only draw labels at major tick marks
  3691. $nbr = count($this->scale->ticks->maj_ticks_label);
  3692. // We have the option to not-display the very first mark
  3693. // (Usefull when the first label might interfere with another
  3694. // axis.)
  3695. $i = $this->show_first_label ? 0 : 1 ;
  3696. if( !$this->show_last_label ) --$nbr;
  3697. // Now run through all labels making sure we don't overshoot the end
  3698. // of the scale.
  3699. $ncolor=0;
  3700. if( isset($this->ticks_label_colors) )
  3701. $ncolor=count($this->ticks_label_colors);
  3702. while( $i<$nbr ) {
  3703. // $tpos holds the absolute text position for the label
  3704. $tpos=$this->scale->ticks->maj_ticklabels_pos[$i];
  3705. // Note. the $limit is only used for the x axis since we
  3706. // might otherwise overshoot if the scale has been centered
  3707. // This is due to us "loosing" the last tick mark if we center.
  3708. if( $this->scale->type=="x" && $tpos > $this->img->width-$this->img->right_margin+1 ) {
  3709. return;
  3710. }
  3711. // we only draw every $label_step label
  3712. if( ($i % $this->label_step)==0 ) {
  3713. // Set specific label color if specified
  3714. if( $ncolor > 0 )
  3715. $this->img->SetColor($this->ticks_label_colors[$i % $ncolor]);
  3716. // If the label has been specified use that and in other case
  3717. // just label the mark with the actual scale value
  3718. $m=$this->scale->ticks->GetMajor();
  3719. // ticks_label has an entry for each data point and is the array
  3720. // that holds the labels set by the user. If the user hasn't
  3721. // specified any values we use whats in the automatically asigned
  3722. // labels in the maj_ticks_label
  3723. if( isset($this->ticks_label[$i*$m]) )
  3724. $label=$this->ticks_label[$i*$m];
  3725. else {
  3726. if( $aAbsLabel )
  3727. $label=abs($this->scale->ticks->maj_ticks_label[$i]);
  3728. else
  3729. $label=$this->scale->ticks->maj_ticks_label[$i];
  3730. if( $this->scale->textscale && $this->scale->ticks->label_formfunc == '' ) {
  3731. ++$label;
  3732. }
  3733. }
  3734. //if( $this->hide_zero_label && $label==0.0 ) {
  3735. // ++$i;
  3736. // continue;
  3737. //}
  3738. if( $this->scale->type == "x" ) {
  3739. if( $this->labelPos == SIDE_DOWN ) {
  3740. if( $this->label_angle==0 || $this->label_angle==90 ) {
  3741. if( $this->label_halign=='' && $this->label_valign=='')
  3742. $this->img->SetTextAlign('center','top');
  3743. else
  3744. $this->img->SetTextAlign($this->label_halign,$this->label_valign);
  3745. }
  3746. else {
  3747. if( $this->label_halign=='' && $this->label_valign=='')
  3748. $this->img->SetTextAlign("right","top");
  3749. else
  3750. $this->img->SetTextAlign($this->label_halign,$this->label_valign);
  3751. }
  3752. $this->img->StrokeText($tpos,$aPos+$this->tick_label_margin,$label,
  3753. $this->label_angle,$this->label_para_align);
  3754. }
  3755. else {
  3756. if( $this->label_angle==0 || $this->label_angle==90 ) {
  3757. if( $this->label_halign=='' && $this->label_valign=='')
  3758. $this->img->SetTextAlign("center","bottom");
  3759. else
  3760. $this->img->SetTextAlign($this->label_halign,$this->label_valign);
  3761. }
  3762. else {
  3763. if( $this->label_halign=='' && $this->label_valign=='')
  3764. $this->img->SetTextAlign("right","bottom");
  3765. else
  3766. $this->img->SetTextAlign($this->label_halign,$this->label_valign);
  3767. }
  3768. $this->img->StrokeText($tpos,$aPos-$this->tick_label_margin,$label,
  3769. $this->label_angle,$this->label_para_align);
  3770. }
  3771. }
  3772. else {
  3773. // scale->type == "y"
  3774. //if( $this->label_angle!=0 )
  3775. //JpGraphError::Raise(" Labels at an angle are not supported on Y-axis");
  3776. if( $this->labelPos == SIDE_LEFT ) { // To the left of y-axis
  3777. if( $this->label_halign=='' && $this->label_valign=='')
  3778. $this->img->SetTextAlign("right","center");
  3779. else
  3780. $this->img->SetTextAlign($this->label_halign,$this->label_valign);
  3781. $this->img->StrokeText($aPos-$this->tick_label_margin,$tpos,$label,$this->label_angle,$this->label_para_align);
  3782. }
  3783. else { // To the right of the y-axis
  3784. if( $this->label_halign=='' && $this->label_valign=='')
  3785. $this->img->SetTextAlign("left","center");
  3786. else
  3787. $this->img->SetTextAlign($this->label_halign,$this->label_valign);
  3788. $this->img->StrokeText($aPos+$this->tick_label_margin,$tpos,$label,$this->label_angle,$this->label_para_align);
  3789. }
  3790. }
  3791. }
  3792. ++$i;
  3793. }
  3794. }
  3795. } // Class
  3796. //===================================================
  3797. // CLASS Ticks
  3798. // Description: Abstract base class for drawing linear and logarithmic
  3799. // tick marks on axis
  3800. //===================================================
  3801. class Ticks {
  3802. var $minor_abs_size=3, $major_abs_size=5;
  3803. var $direction=1; // Should ticks be in(=1) the plot area or outside (=-1)?
  3804. var $scale;
  3805. var $is_set=false;
  3806. var $precision;
  3807. var $supress_zerolabel=false,$supress_first=false;
  3808. var $supress_last=false,$supress_tickmarks=false,$supress_minor_tickmarks=false;
  3809. var $mincolor="",$majcolor="";
  3810. var $weight=1;
  3811. var $label_formatstr=''; // C-style format string to use for labels
  3812. var $label_formfunc='';
  3813. var $label_dateformatstr='';
  3814. var $label_usedateformat=FALSE;
  3815. //---------------
  3816. // CONSTRUCTOR
  3817. function Ticks(&$aScale) {
  3818. $this->scale=&$aScale;
  3819. $this->precision = -1;
  3820. }
  3821. //---------------
  3822. // PUBLIC METHODS
  3823. // Set format string for automatic labels
  3824. function SetLabelFormat($aFormatString,$aDate=FALSE) {
  3825. $this->label_formatstr=$aFormatString;
  3826. $this->label_usedateformat=$aDate;
  3827. }
  3828. function SetLabelDateFormat($aFormatString) {
  3829. $this->label_dateformatstr=$aFormatString;
  3830. }
  3831. function SetFormatCallback($aCallbackFuncName) {
  3832. $this->label_formfunc = $aCallbackFuncName;
  3833. }
  3834. // Don't display the first zero label
  3835. function SupressZeroLabel($aFlag=true) {
  3836. $this->supress_zerolabel=$aFlag;
  3837. }
  3838. // Don't display minor tick marks
  3839. function SupressMinorTickMarks($aHide=true) {
  3840. $this->supress_minor_tickmarks=$aHide;
  3841. }
  3842. // Don't display major tick marks
  3843. function SupressTickMarks($aHide=true) {
  3844. $this->supress_tickmarks=$aHide;
  3845. }
  3846. // Hide the first tick mark
  3847. function SupressFirst($aHide=true) {
  3848. $this->supress_first=$aHide;
  3849. }
  3850. // Hide the last tick mark
  3851. function SupressLast($aHide=true) {
  3852. $this->supress_last=$aHide;
  3853. }
  3854. // Size (in pixels) of minor tick marks
  3855. function GetMinTickAbsSize() {
  3856. return $this->minor_abs_size;
  3857. }
  3858. // Size (in pixels) of major tick marks
  3859. function GetMajTickAbsSize() {
  3860. return $this->major_abs_size;
  3861. }
  3862. function SetSize($aMajSize,$aMinSize=3) {
  3863. $this->major_abs_size = $aMajSize;
  3864. $this->minor_abs_size = $aMinSize;
  3865. }
  3866. // Have the ticks been specified
  3867. function IsSpecified() {
  3868. return $this->is_set;
  3869. }
  3870. // Set the distance between major and minor tick marks
  3871. function Set($aMaj,$aMin) {
  3872. // "Virtual method"
  3873. // Should be implemented by the concrete subclass
  3874. // if any action is wanted.
  3875. }
  3876. // Specify number of decimals in automatic labels
  3877. // Deprecated from 1.4. Use SetFormatString() instead
  3878. function SetPrecision($aPrecision) {
  3879. if( ERR_DEPRECATED )
  3880. JpGraphError::RaiseL(25063);//('Ticks::SetPrecision() is deprecated. Use Ticks::SetLabelFormat() (or Ticks::SetFormatCallback()) instead');
  3881. $this->precision=$aPrecision;
  3882. }
  3883. function SetSide($aSide) {
  3884. $this->direction=$aSide;
  3885. }
  3886. // Which side of the axis should the ticks be on
  3887. function SetDirection($aSide=SIDE_RIGHT) {
  3888. $this->direction=$aSide;
  3889. }
  3890. // Set colors for major and minor tick marks
  3891. function SetMarkColor($aMajorColor,$aMinorColor="") {
  3892. $this->SetColor($aMajorColor,$aMinorColor);
  3893. }
  3894. function SetColor($aMajorColor,$aMinorColor="") {
  3895. $this->majcolor=$aMajorColor;
  3896. // If not specified use same as major
  3897. if( $aMinorColor=="" )
  3898. $this->mincolor=$aMajorColor;
  3899. else
  3900. $this->mincolor=$aMinorColor;
  3901. }
  3902. function SetWeight($aWeight) {
  3903. $this->weight=$aWeight;
  3904. }
  3905. } // Class
  3906. //===================================================
  3907. // CLASS LinearTicks
  3908. // Description: Draw linear ticks on axis
  3909. //===================================================
  3910. class LinearTicks extends Ticks {
  3911. var $minor_step=1, $major_step=2;
  3912. var $xlabel_offset=0,$xtick_offset=0;
  3913. var $label_offset=0; // What offset should the displayed label have
  3914. // i.e should we display 0,1,2 or 1,2,3,4 or 2,3,4 etc
  3915. var $text_label_start=0;
  3916. var $iManualTickPos = NULL, $iManualMinTickPos = NULL, $iManualTickLabels = NULL;
  3917. var $maj_ticks_pos = array(), $maj_ticklabels_pos = array(),
  3918. $ticks_pos = array(), $maj_ticks_label = array();
  3919. //---------------
  3920. // CONSTRUCTOR
  3921. function LinearTicks() {
  3922. $this->precision = -1;
  3923. }
  3924. //---------------
  3925. // PUBLIC METHODS
  3926. // Return major step size in world coordinates
  3927. function GetMajor() {
  3928. return $this->major_step;
  3929. }
  3930. // Return minor step size in world coordinates
  3931. function GetMinor() {
  3932. return $this->minor_step;
  3933. }
  3934. // Set Minor and Major ticks (in world coordinates)
  3935. function Set($aMajStep,$aMinStep=false) {
  3936. if( $aMinStep==false )
  3937. $aMinStep=$aMajStep;
  3938. if( $aMajStep <= 0 || $aMinStep <= 0 ) {
  3939. JpGraphError::RaiseL(25064);
  3940. //(" Minor or major step size is 0. Check that you haven't got an accidental SetTextTicks(0) in your code. If this is not the case you might have stumbled upon a bug in JpGraph. Please report this and if possible include the data that caused the problem.");
  3941. }
  3942. $this->major_step=$aMajStep;
  3943. $this->minor_step=$aMinStep;
  3944. $this->is_set = true;
  3945. }
  3946. function SetMajTickPositions($aMajPos,$aLabels=NULL) {
  3947. $this->SetTickPositions($aMajPos,NULL,$aLabels);
  3948. }
  3949. function SetTickPositions($aMajPos,$aMinPos=NULL,$aLabels=NULL) {
  3950. if( !is_array($aMajPos) || ($aMinPos!==NULL && !is_array($aMinPos)) ) {
  3951. JpGraphError::RaiseL(25065);//('Tick positions must be specifued as an array()');
  3952. return;
  3953. }
  3954. $n=count($aMajPos);
  3955. if( is_array($aLabels) && (count($aLabels) != $n) ) {
  3956. JpGraphError::RaiseL(25066);//('When manually specifying tick positions and labels the number of labels must be the same as the number of specified ticks.');
  3957. return;
  3958. }
  3959. $this->iManualTickPos = $aMajPos;
  3960. $this->iManualMinTickPos = $aMinPos;
  3961. $this->iManualTickLabels = $aLabels;
  3962. }
  3963. // Specify all the tick positions manually and possible also the exact labels
  3964. function _doManualTickPos($aScale) {
  3965. $n=count($this->iManualTickPos);
  3966. $m=count($this->iManualMinTickPos);
  3967. $doLbl=count($this->iManualTickLabels) > 0;
  3968. $this->use_manualtickpos=true;
  3969. $this->maj_ticks_pos = array();
  3970. $this->maj_ticklabels_pos = array();
  3971. $this->ticks_pos = array();
  3972. // Now loop through the supplied positions and translate them to screen coordinates
  3973. // and store them in the maj_label_positions
  3974. $minScale = $aScale->scale[0];
  3975. $maxScale = $aScale->scale[1];
  3976. $j=0;
  3977. for($i=0; $i < $n ; ++$i ) {
  3978. // First make sure that the first tick is not lower than the lower scale value
  3979. if( !isset($this->iManualTickPos[$i]) ||
  3980. $this->iManualTickPos[$i] < $minScale || $this->iManualTickPos[$i] > $maxScale) {
  3981. continue;
  3982. }
  3983. $this->maj_ticks_pos[$j] = $aScale->Translate($this->iManualTickPos[$i]);
  3984. $this->maj_ticklabels_pos[$j] = $this->maj_ticks_pos[$j];
  3985. // Set the minor tick marks the same as major if not specified
  3986. if( $m <= 0 ) {
  3987. $this->ticks_pos[$j] = $this->maj_ticks_pos[$j];
  3988. }
  3989. if( $doLbl ) {
  3990. $this->maj_ticks_label[$j] = $this->iManualTickLabels[$i];
  3991. }
  3992. else {
  3993. $this->maj_ticks_label[$j]=$this->_doLabelFormat($this->iManualTickPos[$i],$i,$n);
  3994. }
  3995. ++$j;
  3996. }
  3997. // Some sanity check
  3998. if( count($this->maj_ticks_pos) < 2 ) {
  3999. JpGraphError::RaiseL(25067);//('Your manually specified scale and ticks is not correct. The scale seems to be too small to hold any of the specified tickl marks.');
  4000. }
  4001. // Setup the minor tick marks
  4002. $j=0;
  4003. for($i=0; $i < $m; ++$i ) {
  4004. if( empty($this->iManualMinTickPos[$i]) ||
  4005. $this->iManualMinTickPos[$i] < $minScale || $this->iManualMinTickPos[$i] > $maxScale)
  4006. continue;
  4007. $this->ticks_pos[$j] = $aScale->Translate($this->iManualMinTickPos[$i]);
  4008. ++$j;
  4009. }
  4010. }
  4011. function _doAutoTickPos($aScale) {
  4012. $maj_step_abs = $aScale->scale_factor*$this->major_step;
  4013. $min_step_abs = $aScale->scale_factor*$this->minor_step;
  4014. if( $min_step_abs==0 || $maj_step_abs==0 ) {
  4015. JpGraphError::RaiseL(25068);//("A plot has an illegal scale. This could for example be that you are trying to use text autoscaling to draw a line plot with only one point or that the plot area is too small. It could also be that no input data value is numeric (perhaps only '-' or 'x')");
  4016. }
  4017. // We need to make this an int since comparing it below
  4018. // with the result from round() can give wrong result, such that
  4019. // (40 < 40) == TRUE !!!
  4020. $limit = (int)$aScale->scale_abs[1];
  4021. if( $aScale->textscale ) {
  4022. // This can only be true for a X-scale (horizontal)
  4023. // Define ticks for a text scale. This is slightly different from a
  4024. // normal linear type of scale since the position might be adjusted
  4025. // and the labels start at on
  4026. $label = (float)$aScale->GetMinVal()+$this->text_label_start+$this->label_offset;
  4027. $start_abs=$aScale->scale_factor*$this->text_label_start;
  4028. $nbrmajticks=ceil(($aScale->GetMaxVal()-$aScale->GetMinVal()-$this->text_label_start )/$this->major_step)+1;
  4029. $x = $aScale->scale_abs[0]+$start_abs+$this->xlabel_offset*$min_step_abs;
  4030. for( $i=0; $label <= $aScale->GetMaxVal()+$this->label_offset; ++$i ) {
  4031. // Apply format to label
  4032. $this->maj_ticks_label[$i]=$this->_doLabelFormat($label,$i,$nbrmajticks);
  4033. $label+=$this->major_step;
  4034. // The x-position of the tick marks can be different from the labels.
  4035. // Note that we record the tick position (not the label) so that the grid
  4036. // happen upon tick marks and not labels.
  4037. $xtick=$aScale->scale_abs[0]+$start_abs+$this->xtick_offset*$min_step_abs+$i*$maj_step_abs;
  4038. $this->maj_ticks_pos[$i]=$xtick;
  4039. $this->maj_ticklabels_pos[$i] = round($x);
  4040. $x += $maj_step_abs;
  4041. }
  4042. }
  4043. else {
  4044. $label = $aScale->GetMinVal();
  4045. $abs_pos = $aScale->scale_abs[0];
  4046. $j=0; $i=0;
  4047. $step = round($maj_step_abs/$min_step_abs);
  4048. if( $aScale->type == "x" ) {
  4049. // For a normal linear type of scale the major ticks will always be multiples
  4050. // of the minor ticks. In order to avoid any rounding issues the major ticks are
  4051. // defined as every "step" minor ticks and not calculated separately
  4052. $nbrmajticks=ceil(($aScale->GetMaxVal()-$aScale->GetMinVal()-$this->text_label_start )/$this->major_step)+1;
  4053. while( round($abs_pos) <= $limit ) {
  4054. $this->ticks_pos[] = round($abs_pos);
  4055. $this->ticks_label[] = $label;
  4056. if( $i % $step == 0 && $j < $nbrmajticks ) {
  4057. $this->maj_ticks_pos[$j] = round($abs_pos);
  4058. $this->maj_ticklabels_pos[$j] = round($abs_pos);
  4059. $this->maj_ticks_label[$j]=$this->_doLabelFormat($label,$j,$nbrmajticks);
  4060. ++$j;
  4061. }
  4062. ++$i;
  4063. $abs_pos += $min_step_abs;
  4064. $label+=$this->minor_step;
  4065. }
  4066. }
  4067. elseif( $aScale->type == "y" ) {
  4068. $nbrmajticks=floor(($aScale->GetMaxVal()-$aScale->GetMinVal())/$this->major_step)+1;
  4069. while( round($abs_pos) >= $limit ) {
  4070. $this->ticks_pos[$i] = round($abs_pos);
  4071. $this->ticks_label[$i]=$label;
  4072. if( $i % $step == 0 && $j < $nbrmajticks) {
  4073. $this->maj_ticks_pos[$j] = round($abs_pos);
  4074. $this->maj_ticklabels_pos[$j] = round($abs_pos);
  4075. $this->maj_ticks_label[$j]=$this->_doLabelFormat($label,$j,$nbrmajticks);
  4076. ++$j;
  4077. }
  4078. ++$i;
  4079. $abs_pos += $min_step_abs;
  4080. $label += $this->minor_step;
  4081. }
  4082. }
  4083. }
  4084. }
  4085. function _doLabelFormat($aVal,$aIdx,$aNbrTicks) {
  4086. // If precision hasn't been specified set it to a sensible value
  4087. if( $this->precision==-1 ) {
  4088. $t = log10($this->minor_step);
  4089. if( $t > 0 )
  4090. $precision = 0;
  4091. else
  4092. $precision = -floor($t);
  4093. }
  4094. else
  4095. $precision = $this->precision;
  4096. if( $this->label_formfunc != '' ) {
  4097. $f=$this->label_formfunc;
  4098. $l = call_user_func($f,$aVal);
  4099. }
  4100. elseif( $this->label_formatstr != '' || $this->label_dateformatstr != '' ) {
  4101. if( $this->label_usedateformat ) {
  4102. $l = date($this->label_formatstr,$aVal);
  4103. }
  4104. else {
  4105. if( $this->label_dateformatstr !== '' )
  4106. $l = date($this->label_dateformatstr,$aVal);
  4107. else
  4108. $l = sprintf($this->label_formatstr,$aVal);
  4109. }
  4110. }
  4111. else {
  4112. $l = sprintf('%01.'.$precision.'f',round($aVal,$precision));
  4113. }
  4114. if( ($this->supress_zerolabel && $l==0) || ($this->supress_first && $aIdx==0) ||
  4115. ($this->supress_last && $aIdx==$aNbrTicks-1) ) {
  4116. $l='';
  4117. }
  4118. return $l;
  4119. }
  4120. // Stroke ticks on either X or Y axis
  4121. function _StrokeTicks(&$aImg,$aScale,$aPos) {
  4122. $hor = $aScale->type == 'x';
  4123. $aImg->SetLineWeight($this->weight);
  4124. // We need to make this an int since comparing it below
  4125. // with the result from round() can give wrong result, such that
  4126. // (40 < 40) == TRUE !!!
  4127. $limit = (int)$aScale->scale_abs[1];
  4128. // A text scale doesn't have any minor ticks
  4129. if( !$aScale->textscale ) {
  4130. // Stroke minor ticks
  4131. $yu = $aPos - $this->direction*$this->GetMinTickAbsSize();
  4132. $xr = $aPos + $this->direction*$this->GetMinTickAbsSize();
  4133. $n = count($this->ticks_pos);
  4134. for($i=0; $i < $n; ++$i ) {
  4135. if( !$this->supress_tickmarks && !$this->supress_minor_tickmarks) {
  4136. if( $this->mincolor!="" ) $aImg->PushColor($this->mincolor);
  4137. if( $hor ) {
  4138. //if( $this->ticks_pos[$i] <= $limit )
  4139. $aImg->Line($this->ticks_pos[$i],$aPos,$this->ticks_pos[$i],$yu);
  4140. }
  4141. else {
  4142. //if( $this->ticks_pos[$i] >= $limit )
  4143. $aImg->Line($aPos,$this->ticks_pos[$i],$xr,$this->ticks_pos[$i]);
  4144. }
  4145. if( $this->mincolor!="" ) $aImg->PopColor();
  4146. }
  4147. }
  4148. }
  4149. // Stroke major ticks
  4150. $yu = $aPos - $this->direction*$this->GetMajTickAbsSize();
  4151. $xr = $aPos + $this->direction*$this->GetMajTickAbsSize();
  4152. $nbrmajticks=ceil(($aScale->GetMaxVal()-$aScale->GetMinVal()-$this->text_label_start )/$this->major_step)+1;
  4153. $n = count($this->maj_ticks_pos);
  4154. for($i=0; $i < $n ; ++$i ) {
  4155. if(!($this->xtick_offset > 0 && $i==$nbrmajticks-1) && !$this->supress_tickmarks) {
  4156. if( $this->majcolor!="" ) $aImg->PushColor($this->majcolor);
  4157. if( $hor ) {
  4158. //if( $this->maj_ticks_pos[$i] <= $limit )
  4159. $aImg->Line($this->maj_ticks_pos[$i],$aPos,$this->maj_ticks_pos[$i],$yu);
  4160. }
  4161. else {
  4162. //if( $this->maj_ticks_pos[$i] >= $limit )
  4163. $aImg->Line($aPos,$this->maj_ticks_pos[$i],$xr,$this->maj_ticks_pos[$i]);
  4164. }
  4165. if( $this->majcolor!="" ) $aImg->PopColor();
  4166. }
  4167. }
  4168. }
  4169. // Draw linear ticks
  4170. function Stroke(&$aImg,$aScale,$aPos) {
  4171. if( $this->iManualTickPos != NULL )
  4172. $this->_doManualTickPos($aScale);
  4173. else
  4174. $this->_doAutoTickPos($aScale);
  4175. $this->_StrokeTicks($aImg,$aScale,$aPos, $aScale->type == 'x' );
  4176. }
  4177. //---------------
  4178. // PRIVATE METHODS
  4179. // Spoecify the offset of the displayed tick mark with the tick "space"
  4180. // Legal values for $o is [0,1] used to adjust where the tick marks and label
  4181. // should be positioned within the major tick-size
  4182. // $lo specifies the label offset and $to specifies the tick offset
  4183. // this comes in handy for example in bar graphs where we wont no offset for the
  4184. // tick but have the labels displayed halfway under the bars.
  4185. function SetXLabelOffset($aLabelOff,$aTickOff=-1) {
  4186. $this->xlabel_offset=$aLabelOff;
  4187. if( $aTickOff==-1 ) // Same as label offset
  4188. $this->xtick_offset=$aLabelOff;
  4189. else
  4190. $this->xtick_offset=$aTickOff;
  4191. if( $aLabelOff>0 )
  4192. $this->SupressLast(); // The last tick wont fit
  4193. }
  4194. // Which tick label should we start with?
  4195. function SetTextLabelStart($aTextLabelOff) {
  4196. $this->text_label_start=$aTextLabelOff;
  4197. }
  4198. } // Class
  4199. //===================================================
  4200. // CLASS LinearScale
  4201. // Description: Handle linear scaling between screen and world
  4202. //===================================================
  4203. class LinearScale {
  4204. var $scale=array(0,0);
  4205. var $scale_abs=array(0,0);
  4206. var $scale_factor; // Scale factor between world and screen
  4207. var $world_size; // Plot area size in world coordinates
  4208. var $world_abs_size; // Plot area size in pixels
  4209. var $off; // Offset between image edge and plot area
  4210. var $type; // is this x or y scale ?
  4211. var $ticks=null; // Store ticks
  4212. var $text_scale_off = 0;
  4213. var $autoscale_min=false; // Forced minimum value, auto determine max
  4214. var $autoscale_max=false; // Forced maximum value, auto determine min
  4215. var $gracetop=0,$gracebottom=0;
  4216. var $intscale=false; // Restrict autoscale to integers
  4217. var $textscale=false; // Just a flag to let the Plot class find out if
  4218. // we are a textscale or not. This is a cludge since
  4219. // this ionformatyion is availabale in Graph::axtype but
  4220. // we don't have access to the graph object in the Plots
  4221. // stroke method. So we let graph store the status here
  4222. // when the linear scale is created. A real cludge...
  4223. var $auto_ticks=false; // When using manual scale should the ticks be automatically set?
  4224. var $name = 'lin';
  4225. //---------------
  4226. // CONSTRUCTOR
  4227. function LinearScale($aMin=0,$aMax=0,$aType="y") {
  4228. assert($aType=="x" || $aType=="y" );
  4229. assert($aMin<=$aMax);
  4230. $this->type=$aType;
  4231. $this->scale=array($aMin,$aMax);
  4232. $this->world_size=$aMax-$aMin;
  4233. $this->ticks = new LinearTicks();
  4234. }
  4235. //---------------
  4236. // PUBLIC METHODS
  4237. // Check if scale is set or if we should autoscale
  4238. // We should do this is either scale or ticks has not been set
  4239. function IsSpecified() {
  4240. if( $this->GetMinVal()==$this->GetMaxVal() ) { // Scale not set
  4241. return false;
  4242. }
  4243. return true;
  4244. }
  4245. // Set the minimum data value when the autoscaling is used.
  4246. // Usefull if you want a fix minimum (like 0) but have an
  4247. // automatic maximum
  4248. function SetAutoMin($aMin) {
  4249. $this->autoscale_min=$aMin;
  4250. }
  4251. // Set the minimum data value when the autoscaling is used.
  4252. // Usefull if you want a fix minimum (like 0) but have an
  4253. // automatic maximum
  4254. function SetAutoMax($aMax) {
  4255. $this->autoscale_max=$aMax;
  4256. }
  4257. // If the user manually specifies a scale should the ticks
  4258. // still be set automatically?
  4259. function SetAutoTicks($aFlag=true) {
  4260. $this->auto_ticks = $aFlag;
  4261. }
  4262. // Specify scale "grace" value (top and bottom)
  4263. function SetGrace($aGraceTop,$aGraceBottom=0) {
  4264. if( $aGraceTop<0 || $aGraceBottom < 0 )
  4265. JpGraphError::RaiseL(25069);//(" Grace must be larger then 0");
  4266. $this->gracetop=$aGraceTop;
  4267. $this->gracebottom=$aGraceBottom;
  4268. }
  4269. // Get the minimum value in the scale
  4270. function GetMinVal() {
  4271. return $this->scale[0];
  4272. }
  4273. // get maximum value for scale
  4274. function GetMaxVal() {
  4275. return $this->scale[1];
  4276. }
  4277. // Specify a new min/max value for sclae
  4278. function Update(&$aImg,$aMin,$aMax) {
  4279. $this->scale=array($aMin,$aMax);
  4280. $this->world_size=$aMax-$aMin;
  4281. $this->InitConstants($aImg);
  4282. }
  4283. // Translate between world and screen
  4284. function Translate($aCoord) {
  4285. if( !is_numeric($aCoord) ) {
  4286. if( $aCoord != '' && $aCoord != '-' && $aCoord != 'x' )
  4287. JpGraphError::RaiseL(25070);//('Your data contains non-numeric values.');
  4288. return 0;
  4289. }
  4290. else {
  4291. return $this->off + ($aCoord - $this->scale[0])*$this->scale_factor;
  4292. }
  4293. }
  4294. // Relative translate (don't include offset) usefull when we just want
  4295. // to know the relative position (in pixels) on the axis
  4296. function RelTranslate($aCoord) {
  4297. if( !is_numeric($aCoord) ) {
  4298. if( $aCoord != '' && $aCoord != '-' && $aCoord != 'x' )
  4299. JpGraphError::RaiseL(25070);//('Your data contains non-numeric values.');
  4300. return 0;
  4301. }
  4302. else {
  4303. return ($aCoord - $this->scale[0]) * $this->scale_factor;
  4304. }
  4305. }
  4306. // Restrict autoscaling to only use integers
  4307. function SetIntScale($aIntScale=true) {
  4308. $this->intscale=$aIntScale;
  4309. }
  4310. // Calculate an integer autoscale
  4311. function IntAutoScale(&$img,$min,$max,$maxsteps,$majend=true) {
  4312. // Make sure limits are integers
  4313. $min=floor($min);
  4314. $max=ceil($max);
  4315. if( abs($min-$max)==0 ) {
  4316. --$min; ++$max;
  4317. }
  4318. $maxsteps = floor($maxsteps);
  4319. $gracetop=round(($this->gracetop/100.0)*abs($max-$min));
  4320. $gracebottom=round(($this->gracebottom/100.0)*abs($max-$min));
  4321. if( is_numeric($this->autoscale_min) ) {
  4322. $min = ceil($this->autoscale_min);
  4323. if( $min >= $max ) {
  4324. JpGraphError::RaiseL(25071);//('You have specified a min value with SetAutoMin() which is larger than the maximum value used for the scale. This is not possible.');
  4325. }
  4326. }
  4327. if( is_numeric($this->autoscale_max) ) {
  4328. $max = ceil($this->autoscale_max);
  4329. if( $min >= $max ) {
  4330. JpGraphError::RaiseL(25072);//('You have specified a max value with SetAutoMax() which is smaller than the miminum value used for the scale. This is not possible.');
  4331. }
  4332. }
  4333. if( abs($min-$max ) == 0 ) {
  4334. ++$max;
  4335. --$min;
  4336. }
  4337. $min -= $gracebottom;
  4338. $max += $gracetop;
  4339. // First get tickmarks as multiples of 1, 10, ...
  4340. if( $majend ) {
  4341. list($num1steps,$adj1min,$adj1max,$maj1step) =
  4342. $this->IntCalcTicks($maxsteps,$min,$max,1);
  4343. }
  4344. else {
  4345. $adj1min = $min;
  4346. $adj1max = $max;
  4347. list($num1steps,$maj1step) =
  4348. $this->IntCalcTicksFreeze($maxsteps,$min,$max,1);
  4349. }
  4350. if( abs($min-$max) > 2 ) {
  4351. // Then get tick marks as 2:s 2, 20, ...
  4352. if( $majend ) {
  4353. list($num2steps,$adj2min,$adj2max,$maj2step) =
  4354. $this->IntCalcTicks($maxsteps,$min,$max,5);
  4355. }
  4356. else {
  4357. $adj2min = $min;
  4358. $adj2max = $max;
  4359. list($num2steps,$maj2step) =
  4360. $this->IntCalcTicksFreeze($maxsteps,$min,$max,5);
  4361. }
  4362. }
  4363. else {
  4364. $num2steps = 10000; // Dummy high value so we don't choose this
  4365. }
  4366. if( abs($min-$max) > 5 ) {
  4367. // Then get tickmarks as 5:s 5, 50, 500, ...
  4368. if( $majend ) {
  4369. list($num5steps,$adj5min,$adj5max,$maj5step) =
  4370. $this->IntCalcTicks($maxsteps,$min,$max,2);
  4371. }
  4372. else {
  4373. $adj5min = $min;
  4374. $adj5max = $max;
  4375. list($num5steps,$maj5step) =
  4376. $this->IntCalcTicksFreeze($maxsteps,$min,$max,2);
  4377. }
  4378. }
  4379. else {
  4380. $num5steps = 10000; // Dummy high value so we don't choose this
  4381. }
  4382. // Check to see whichof 1:s, 2:s or 5:s fit better with
  4383. // the requested number of major ticks
  4384. $match1=abs($num1steps-$maxsteps);
  4385. $match2=abs($num2steps-$maxsteps);
  4386. if( !empty($maj5step) && $maj5step > 1 )
  4387. $match5=abs($num5steps-$maxsteps);
  4388. else
  4389. $match5=10000; // Dummy high value
  4390. // Compare these three values and see which is the closest match
  4391. // We use a 0.6 weight to gravitate towards multiple of 5:s
  4392. if( $match1 < $match2 ) {
  4393. if( $match1 < $match5 )
  4394. $r=1;
  4395. else
  4396. $r=3;
  4397. }
  4398. else {
  4399. if( $match2 < $match5 )
  4400. $r=2;
  4401. else
  4402. $r=3;
  4403. }
  4404. // Minsteps are always the same as maxsteps for integer scale
  4405. switch( $r ) {
  4406. case 1:
  4407. $this->ticks->Set($maj1step,$maj1step);
  4408. $this->Update($img,$adj1min,$adj1max);
  4409. break;
  4410. case 2:
  4411. $this->ticks->Set($maj2step,$maj2step);
  4412. $this->Update($img,$adj2min,$adj2max);
  4413. break;
  4414. case 3:
  4415. $this->ticks->Set($maj5step,$maj5step);
  4416. $this->Update($img,$adj5min,$adj5max);
  4417. break;
  4418. default:
  4419. JpGraphError::RaiseL(25073,$r);//('Internal error. Integer scale algorithm comparison out of bound (r=$r)');
  4420. }
  4421. }
  4422. // Calculate autoscale. Used if user hasn't given a scale and ticks
  4423. // $maxsteps is the maximum number of major tickmarks allowed.
  4424. function AutoScale(&$img,$min,$max,$maxsteps,$majend=true) {
  4425. if( $this->intscale ) {
  4426. $this->IntAutoScale($img,$min,$max,$maxsteps,$majend);
  4427. return;
  4428. }
  4429. if( abs($min-$max) < 0.00001 ) {
  4430. // We need some difference to be able to autoscale
  4431. // make it 5% above and 5% below value
  4432. if( $min==0 && $max==0 ) { // Special case
  4433. $min=-1; $max=1;
  4434. }
  4435. else {
  4436. $delta = (abs($max)+abs($min))*0.005;
  4437. $min -= $delta;
  4438. $max += $delta;
  4439. }
  4440. }
  4441. $gracetop=($this->gracetop/100.0)*abs($max-$min);
  4442. $gracebottom=($this->gracebottom/100.0)*abs($max-$min);
  4443. if( is_numeric($this->autoscale_min) ) {
  4444. $min = $this->autoscale_min;
  4445. if( $min >= $max ) {
  4446. JpGraphError::RaiseL(25071);//('You have specified a min value with SetAutoMin() which is larger than the maximum value used for the scale. This is not possible.');
  4447. }
  4448. if( abs($min-$max ) < 0.00001 )
  4449. $max *= 1.2;
  4450. }
  4451. if( is_numeric($this->autoscale_max) ) {
  4452. $max = $this->autoscale_max;
  4453. if( $min >= $max ) {
  4454. JpGraphError::RaiseL(25072);//('You have specified a max value with SetAutoMax() which is smaller than the miminum value used for the scale. This is not possible.');
  4455. }
  4456. if( abs($min-$max ) < 0.00001 )
  4457. $min *= 0.8;
  4458. }
  4459. $min -= $gracebottom;
  4460. $max += $gracetop;
  4461. // First get tickmarks as multiples of 0.1, 1, 10, ...
  4462. if( $majend ) {
  4463. list($num1steps,$adj1min,$adj1max,$min1step,$maj1step) =
  4464. $this->CalcTicks($maxsteps,$min,$max,1,2);
  4465. }
  4466. else {
  4467. $adj1min=$min;
  4468. $adj1max=$max;
  4469. list($num1steps,$min1step,$maj1step) =
  4470. $this->CalcTicksFreeze($maxsteps,$min,$max,1,2,false);
  4471. }
  4472. // Then get tick marks as 2:s 0.2, 2, 20, ...
  4473. if( $majend ) {
  4474. list($num2steps,$adj2min,$adj2max,$min2step,$maj2step) =
  4475. $this->CalcTicks($maxsteps,$min,$max,5,2);
  4476. }
  4477. else {
  4478. $adj2min=$min;
  4479. $adj2max=$max;
  4480. list($num2steps,$min2step,$maj2step) =
  4481. $this->CalcTicksFreeze($maxsteps,$min,$max,5,2,false);
  4482. }
  4483. // Then get tickmarks as 5:s 0.05, 0.5, 5, 50, ...
  4484. if( $majend ) {
  4485. list($num5steps,$adj5min,$adj5max,$min5step,$maj5step) =
  4486. $this->CalcTicks($maxsteps,$min,$max,2,5);
  4487. }
  4488. else {
  4489. $adj5min=$min;
  4490. $adj5max=$max;
  4491. list($num5steps,$min5step,$maj5step) =
  4492. $this->CalcTicksFreeze($maxsteps,$min,$max,2,5,false);
  4493. }
  4494. // Check to see whichof 1:s, 2:s or 5:s fit better with
  4495. // the requested number of major ticks
  4496. $match1=abs($num1steps-$maxsteps);
  4497. $match2=abs($num2steps-$maxsteps);
  4498. $match5=abs($num5steps-$maxsteps);
  4499. // Compare these three values and see which is the closest match
  4500. // We use a 0.8 weight to gravitate towards multiple of 5:s
  4501. $r=$this->MatchMin3($match1,$match2,$match5,0.8);
  4502. switch( $r ) {
  4503. case 1:
  4504. $this->Update($img,$adj1min,$adj1max);
  4505. $this->ticks->Set($maj1step,$min1step);
  4506. break;
  4507. case 2:
  4508. $this->Update($img,$adj2min,$adj2max);
  4509. $this->ticks->Set($maj2step,$min2step);
  4510. break;
  4511. case 3:
  4512. $this->Update($img,$adj5min,$adj5max);
  4513. $this->ticks->Set($maj5step,$min5step);
  4514. break;
  4515. }
  4516. }
  4517. //---------------
  4518. // PRIVATE METHODS
  4519. // This method recalculates all constants that are depending on the
  4520. // margins in the image. If the margins in the image are changed
  4521. // this method should be called for every scale that is registred with
  4522. // that image. Should really be installed as an observer of that image.
  4523. function InitConstants(&$img) {
  4524. if( $this->type=="x" ) {
  4525. $this->world_abs_size=$img->width - $img->left_margin - $img->right_margin;
  4526. $this->off=$img->left_margin;
  4527. $this->scale_factor = 0;
  4528. if( $this->world_size > 0 )
  4529. $this->scale_factor=$this->world_abs_size/($this->world_size*1.0);
  4530. }
  4531. else { // y scale
  4532. $this->world_abs_size=$img->height - $img->top_margin - $img->bottom_margin;
  4533. $this->off=$img->top_margin+$this->world_abs_size;
  4534. $this->scale_factor = 0;
  4535. if( $this->world_size > 0 ) {
  4536. $this->scale_factor=-$this->world_abs_size/($this->world_size*1.0);
  4537. }
  4538. }
  4539. $size = $this->world_size * $this->scale_factor;
  4540. $this->scale_abs=array($this->off,$this->off + $size);
  4541. }
  4542. // Initialize the conversion constants for this scale
  4543. // This tries to pre-calculate as much as possible to speed up the
  4544. // actual conversion (with Translate()) later on
  4545. // $start =scale start in absolute pixels (for x-scale this is an y-position
  4546. // and for an y-scale this is an x-position
  4547. // $len =absolute length in pixels of scale
  4548. function SetConstants($aStart,$aLen) {
  4549. $this->world_abs_size=$aLen;
  4550. $this->off=$aStart;
  4551. if( $this->world_size<=0 ) {
  4552. // This should never ever happen !!
  4553. JpGraphError::RaiseL(25074);
  4554. //("You have unfortunately stumbled upon a bug in JpGraph. It seems like the scale range is ".$this->world_size." [for ".$this->type." scale] <br> Please report Bug #01 to jpgraph@aditus.nu and include the script that gave this error. This problem could potentially be caused by trying to use \"illegal\" values in the input data arrays (like trying to send in strings or only NULL values) which causes the autoscaling to fail.");
  4555. }
  4556. // scale_factor = number of pixels per world unit
  4557. $this->scale_factor=$this->world_abs_size/($this->world_size*1.0);
  4558. // scale_abs = start and end points of scale in absolute pixels
  4559. $this->scale_abs=array($this->off,$this->off+$this->world_size*$this->scale_factor);
  4560. }
  4561. // Calculate number of ticks steps with a specific division
  4562. // $a is the divisor of 10**x to generate the first maj tick intervall
  4563. // $a=1, $b=2 give major ticks with multiple of 10, ...,0.1,1,10,...
  4564. // $a=5, $b=2 give major ticks with multiple of 2:s ...,0.2,2,20,...
  4565. // $a=2, $b=5 give major ticks with multiple of 5:s ...,0.5,5,50,...
  4566. // We return a vector of
  4567. // [$numsteps,$adjmin,$adjmax,$minstep,$majstep]
  4568. // If $majend==true then the first and last marks on the axis will be major
  4569. // labeled tick marks otherwise it will be adjusted to the closest min tick mark
  4570. function CalcTicks($maxsteps,$min,$max,$a,$b,$majend=true) {
  4571. $diff=$max-$min;
  4572. if( $diff==0 )
  4573. $ld=0;
  4574. else
  4575. $ld=floor(log10($diff));
  4576. // Gravitate min towards zero if we are close
  4577. if( $min>0 && $min < pow(10,$ld) ) $min=0;
  4578. //$majstep=pow(10,$ld-1)/$a;
  4579. $majstep=pow(10,$ld)/$a;
  4580. $minstep=$majstep/$b;
  4581. $adjmax=ceil($max/$minstep)*$minstep;
  4582. $adjmin=floor($min/$minstep)*$minstep;
  4583. $adjdiff = $adjmax-$adjmin;
  4584. $numsteps=$adjdiff/$majstep;
  4585. while( $numsteps>$maxsteps ) {
  4586. $majstep=pow(10,$ld)/$a;
  4587. $numsteps=$adjdiff/$majstep;
  4588. ++$ld;
  4589. }
  4590. $minstep=$majstep/$b;
  4591. $adjmin=floor($min/$minstep)*$minstep;
  4592. $adjdiff = $adjmax-$adjmin;
  4593. if( $majend ) {
  4594. $adjmin = floor($min/$majstep)*$majstep;
  4595. $adjdiff = $adjmax-$adjmin;
  4596. $adjmax = ceil($adjdiff/$majstep)*$majstep+$adjmin;
  4597. }
  4598. else
  4599. $adjmax=ceil($max/$minstep)*$minstep;
  4600. return array($numsteps,$adjmin,$adjmax,$minstep,$majstep);
  4601. }
  4602. function CalcTicksFreeze($maxsteps,$min,$max,$a,$b) {
  4603. // Same as CalcTicks but don't adjust min/max values
  4604. $diff=$max-$min;
  4605. if( $diff==0 )
  4606. $ld=0;
  4607. else
  4608. $ld=floor(log10($diff));
  4609. //$majstep=pow(10,$ld-1)/$a;
  4610. $majstep=pow(10,$ld)/$a;
  4611. $minstep=$majstep/$b;
  4612. $numsteps=floor($diff/$majstep);
  4613. while( $numsteps > $maxsteps ) {
  4614. $majstep=pow(10,$ld)/$a;
  4615. $numsteps=floor($diff/$majstep);
  4616. ++$ld;
  4617. }
  4618. $minstep=$majstep/$b;
  4619. return array($numsteps,$minstep,$majstep);
  4620. }
  4621. function IntCalcTicks($maxsteps,$min,$max,$a,$majend=true) {
  4622. $diff=$max-$min;
  4623. if( $diff==0 )
  4624. JpGraphError::RaiseL(25075);//('Can\'t automatically determine ticks since min==max.');
  4625. else
  4626. $ld=floor(log10($diff));
  4627. // Gravitate min towards zero if we are close
  4628. if( $min>0 && $min < pow(10,$ld) ) $min=0;
  4629. if( $ld == 0 ) $ld=1;
  4630. if( $a == 1 )
  4631. $majstep = 1;
  4632. else
  4633. $majstep=pow(10,$ld)/$a;
  4634. $adjmax=ceil($max/$majstep)*$majstep;
  4635. $adjmin=floor($min/$majstep)*$majstep;
  4636. $adjdiff = $adjmax-$adjmin;
  4637. $numsteps=$adjdiff/$majstep;
  4638. while( $numsteps>$maxsteps ) {
  4639. $majstep=pow(10,$ld)/$a;
  4640. $numsteps=$adjdiff/$majstep;
  4641. ++$ld;
  4642. }
  4643. $adjmin=floor($min/$majstep)*$majstep;
  4644. $adjdiff = $adjmax-$adjmin;
  4645. if( $majend ) {
  4646. $adjmin = floor($min/$majstep)*$majstep;
  4647. $adjdiff = $adjmax-$adjmin;
  4648. $adjmax = ceil($adjdiff/$majstep)*$majstep+$adjmin;
  4649. }
  4650. else
  4651. $adjmax=ceil($max/$majstep)*$majstep;
  4652. return array($numsteps,$adjmin,$adjmax,$majstep);
  4653. }
  4654. function IntCalcTicksFreeze($maxsteps,$min,$max,$a) {
  4655. // Same as IntCalcTick but don't change min/max values
  4656. $diff=$max-$min;
  4657. if( $diff==0 )
  4658. JpGraphError::RaiseL(25075);//('Can\'t automatically determine ticks since min==max.');
  4659. else
  4660. $ld=floor(log10($diff));
  4661. if( $ld == 0 ) $ld=1;
  4662. if( $a == 1 )
  4663. $majstep = 1;
  4664. else
  4665. $majstep=pow(10,$ld)/$a;
  4666. $numsteps=floor($diff/$majstep);
  4667. while( $numsteps > $maxsteps ) {
  4668. $majstep=pow(10,$ld)/$a;
  4669. $numsteps=floor($diff/$majstep);
  4670. ++$ld;
  4671. }
  4672. return array($numsteps,$majstep);
  4673. }
  4674. // Determine the minimum of three values witha weight for last value
  4675. function MatchMin3($a,$b,$c,$weight) {
  4676. if( $a < $b ) {
  4677. if( $a < ($c*$weight) )
  4678. return 1; // $a smallest
  4679. else
  4680. return 3; // $c smallest
  4681. }
  4682. elseif( $b < ($c*$weight) )
  4683. return 2; // $b smallest
  4684. return 3; // $c smallest
  4685. }
  4686. } // Class
  4687. //===================================================
  4688. // CLASS RGB
  4689. // Description: Color definitions as RGB triples
  4690. //===================================================
  4691. class RGB {
  4692. var $rgb_table;
  4693. var $img;
  4694. function RGB(&$aImg) {
  4695. $this->img = &$aImg;
  4696. // Conversion array between color names and RGB
  4697. $this->rgb_table = array(
  4698. "aqua"=> array(0,255,255),
  4699. "lime"=> array(0,255,0),
  4700. "teal"=> array(0,128,128),
  4701. "whitesmoke"=>array(245,245,245),
  4702. "gainsboro"=>array(220,220,220),
  4703. "oldlace"=>array(253,245,230),
  4704. "linen"=>array(250,240,230),
  4705. "antiquewhite"=>array(250,235,215),
  4706. "papayawhip"=>array(255,239,213),
  4707. "blanchedalmond"=>array(255,235,205),
  4708. "bisque"=>array(255,228,196),
  4709. "peachpuff"=>array(255,218,185),
  4710. "navajowhite"=>array(255,222,173),
  4711. "moccasin"=>array(255,228,181),
  4712. "cornsilk"=>array(255,248,220),
  4713. "ivory"=>array(255,255,240),
  4714. "lemonchiffon"=>array(255,250,205),
  4715. "seashell"=>array(255,245,238),
  4716. "mintcream"=>array(245,255,250),
  4717. "azure"=>array(240,255,255),
  4718. "aliceblue"=>array(240,248,255),
  4719. "lavender"=>array(230,230,250),
  4720. "lavenderblush"=>array(255,240,245),
  4721. "mistyrose"=>array(255,228,225),
  4722. "white"=>array(255,255,255),
  4723. "black"=>array(0,0,0),
  4724. "darkslategray"=>array(47,79,79),
  4725. "dimgray"=>array(105,105,105),
  4726. "slategray"=>array(112,128,144),
  4727. "lightslategray"=>array(119,136,153),
  4728. "gray"=>array(190,190,190),
  4729. "lightgray"=>array(211,211,211),
  4730. "midnightblue"=>array(25,25,112),
  4731. "navy"=>array(0,0,128),
  4732. "cornflowerblue"=>array(100,149,237),
  4733. "darkslateblue"=>array(72,61,139),
  4734. "slateblue"=>array(106,90,205),
  4735. "mediumslateblue"=>array(123,104,238),
  4736. "lightslateblue"=>array(132,112,255),
  4737. "mediumblue"=>array(0,0,205),
  4738. "royalblue"=>array(65,105,225),
  4739. "blue"=>array(0,0,255),
  4740. "dodgerblue"=>array(30,144,255),
  4741. "deepskyblue"=>array(0,191,255),
  4742. "skyblue"=>array(135,206,235),
  4743. "lightskyblue"=>array(135,206,250),
  4744. "steelblue"=>array(70,130,180),
  4745. "lightred"=>array(211,167,168),
  4746. "lightsteelblue"=>array(176,196,222),
  4747. "lightblue"=>array(173,216,230),
  4748. "powderblue"=>array(176,224,230),
  4749. "paleturquoise"=>array(175,238,238),
  4750. "darkturquoise"=>array(0,206,209),
  4751. "mediumturquoise"=>array(72,209,204),
  4752. "turquoise"=>array(64,224,208),
  4753. "cyan"=>array(0,255,255),
  4754. "lightcyan"=>array(224,255,255),
  4755. "cadetblue"=>array(95,158,160),
  4756. "mediumaquamarine"=>array(102,205,170),
  4757. "aquamarine"=>array(127,255,212),
  4758. "darkgreen"=>array(0,100,0),
  4759. "darkolivegreen"=>array(85,107,47),
  4760. "darkseagreen"=>array(143,188,143),
  4761. "seagreen"=>array(46,139,87),
  4762. "mediumseagreen"=>array(60,179,113),
  4763. "lightseagreen"=>array(32,178,170),
  4764. "palegreen"=>array(152,251,152),
  4765. "springgreen"=>array(0,255,127),
  4766. "lawngreen"=>array(124,252,0),
  4767. "green"=>array(0,255,0),
  4768. "chartreuse"=>array(127,255,0),
  4769. "mediumspringgreen"=>array(0,250,154),
  4770. "greenyellow"=>array(173,255,47),
  4771. "limegreen"=>array(50,205,50),
  4772. "yellowgreen"=>array(154,205,50),
  4773. "forestgreen"=>array(34,139,34),
  4774. "olivedrab"=>array(107,142,35),
  4775. "darkkhaki"=>array(189,183,107),
  4776. "khaki"=>array(240,230,140),
  4777. "palegoldenrod"=>array(238,232,170),
  4778. "lightgoldenrodyellow"=>array(250,250,210),
  4779. "lightyellow"=>array(255,255,200),
  4780. "yellow"=>array(255,255,0),
  4781. "gold"=>array(255,215,0),
  4782. "lightgoldenrod"=>array(238,221,130),
  4783. "goldenrod"=>array(218,165,32),
  4784. "darkgoldenrod"=>array(184,134,11),
  4785. "rosybrown"=>array(188,143,143),
  4786. "indianred"=>array(205,92,92),
  4787. "saddlebrown"=>array(139,69,19),
  4788. "sienna"=>array(160,82,45),
  4789. "peru"=>array(205,133,63),
  4790. "burlywood"=>array(222,184,135),
  4791. "beige"=>array(245,245,220),
  4792. "wheat"=>array(245,222,179),
  4793. "sandybrown"=>array(244,164,96),
  4794. "tan"=>array(210,180,140),
  4795. "chocolate"=>array(210,105,30),
  4796. "firebrick"=>array(178,34,34),
  4797. "brown"=>array(165,42,42),
  4798. "darksalmon"=>array(233,150,122),
  4799. "salmon"=>array(250,128,114),
  4800. "lightsalmon"=>array(255,160,122),
  4801. "orange"=>array(255,165,0),
  4802. "darkorange"=>array(255,140,0),
  4803. "coral"=>array(255,127,80),
  4804. "lightcoral"=>array(240,128,128),
  4805. "tomato"=>array(255,99,71),
  4806. "orangered"=>array(255,69,0),
  4807. "red"=>array(255,0,0),
  4808. "hotpink"=>array(255,105,180),
  4809. "deeppink"=>array(255,20,147),
  4810. "pink"=>array(255,192,203),
  4811. "lightpink"=>array(255,182,193),
  4812. "palevioletred"=>array(219,112,147),
  4813. "maroon"=>array(176,48,96),
  4814. "mediumvioletred"=>array(199,21,133),
  4815. "violetred"=>array(208,32,144),
  4816. "magenta"=>array(255,0,255),
  4817. "violet"=>array(238,130,238),
  4818. "plum"=>array(221,160,221),
  4819. "orchid"=>array(218,112,214),
  4820. "mediumorchid"=>array(186,85,211),
  4821. "darkorchid"=>array(153,50,204),
  4822. "darkviolet"=>array(148,0,211),
  4823. "blueviolet"=>array(138,43,226),
  4824. "purple"=>array(160,32,240),
  4825. "mediumpurple"=>array(147,112,219),
  4826. "thistle"=>array(216,191,216),
  4827. "snow1"=>array(255,250,250),
  4828. "snow2"=>array(238,233,233),
  4829. "snow3"=>array(205,201,201),
  4830. "snow4"=>array(139,137,137),
  4831. "seashell1"=>array(255,245,238),
  4832. "seashell2"=>array(238,229,222),
  4833. "seashell3"=>array(205,197,191),
  4834. "seashell4"=>array(139,134,130),
  4835. "AntiqueWhite1"=>array(255,239,219),
  4836. "AntiqueWhite2"=>array(238,223,204),
  4837. "AntiqueWhite3"=>array(205,192,176),
  4838. "AntiqueWhite4"=>array(139,131,120),
  4839. "bisque1"=>array(255,228,196),
  4840. "bisque2"=>array(238,213,183),
  4841. "bisque3"=>array(205,183,158),
  4842. "bisque4"=>array(139,125,107),
  4843. "peachPuff1"=>array(255,218,185),
  4844. "peachpuff2"=>array(238,203,173),
  4845. "peachpuff3"=>array(205,175,149),
  4846. "peachpuff4"=>array(139,119,101),
  4847. "navajowhite1"=>array(255,222,173),
  4848. "navajowhite2"=>array(238,207,161),
  4849. "navajowhite3"=>array(205,179,139),
  4850. "navajowhite4"=>array(139,121,94),
  4851. "lemonchiffon1"=>array(255,250,205),
  4852. "lemonchiffon2"=>array(238,233,191),
  4853. "lemonchiffon3"=>array(205,201,165),
  4854. "lemonchiffon4"=>array(139,137,112),
  4855. "ivory1"=>array(255,255,240),
  4856. "ivory2"=>array(238,238,224),
  4857. "ivory3"=>array(205,205,193),
  4858. "ivory4"=>array(139,139,131),
  4859. "honeydew"=>array(193,205,193),
  4860. "lavenderblush1"=>array(255,240,245),
  4861. "lavenderblush2"=>array(238,224,229),
  4862. "lavenderblush3"=>array(205,193,197),
  4863. "lavenderblush4"=>array(139,131,134),
  4864. "mistyrose1"=>array(255,228,225),
  4865. "mistyrose2"=>array(238,213,210),
  4866. "mistyrose3"=>array(205,183,181),
  4867. "mistyrose4"=>array(139,125,123),
  4868. "azure1"=>array(240,255,255),
  4869. "azure2"=>array(224,238,238),
  4870. "azure3"=>array(193,205,205),
  4871. "azure4"=>array(131,139,139),
  4872. "slateblue1"=>array(131,111,255),
  4873. "slateblue2"=>array(122,103,238),
  4874. "slateblue3"=>array(105,89,205),
  4875. "slateblue4"=>array(71,60,139),
  4876. "royalblue1"=>array(72,118,255),
  4877. "royalblue2"=>array(67,110,238),
  4878. "royalblue3"=>array(58,95,205),
  4879. "royalblue4"=>array(39,64,139),
  4880. "dodgerblue1"=>array(30,144,255),
  4881. "dodgerblue2"=>array(28,134,238),
  4882. "dodgerblue3"=>array(24,116,205),
  4883. "dodgerblue4"=>array(16,78,139),
  4884. "steelblue1"=>array(99,184,255),
  4885. "steelblue2"=>array(92,172,238),
  4886. "steelblue3"=>array(79,148,205),
  4887. "steelblue4"=>array(54,100,139),
  4888. "deepskyblue1"=>array(0,191,255),
  4889. "deepskyblue2"=>array(0,178,238),
  4890. "deepskyblue3"=>array(0,154,205),
  4891. "deepskyblue4"=>array(0,104,139),
  4892. "skyblue1"=>array(135,206,255),
  4893. "skyblue2"=>array(126,192,238),
  4894. "skyblue3"=>array(108,166,205),
  4895. "skyblue4"=>array(74,112,139),
  4896. "lightskyblue1"=>array(176,226,255),
  4897. "lightskyblue2"=>array(164,211,238),
  4898. "lightskyblue3"=>array(141,182,205),
  4899. "lightskyblue4"=>array(96,123,139),
  4900. "slategray1"=>array(198,226,255),
  4901. "slategray2"=>array(185,211,238),
  4902. "slategray3"=>array(159,182,205),
  4903. "slategray4"=>array(108,123,139),
  4904. "lightsteelblue1"=>array(202,225,255),
  4905. "lightsteelblue2"=>array(188,210,238),
  4906. "lightsteelblue3"=>array(162,181,205),
  4907. "lightsteelblue4"=>array(110,123,139),
  4908. "lightblue1"=>array(191,239,255),
  4909. "lightblue2"=>array(178,223,238),
  4910. "lightblue3"=>array(154,192,205),
  4911. "lightblue4"=>array(104,131,139),
  4912. "lightcyan1"=>array(224,255,255),
  4913. "lightcyan2"=>array(209,238,238),
  4914. "lightcyan3"=>array(180,205,205),
  4915. "lightcyan4"=>array(122,139,139),
  4916. "paleturquoise1"=>array(187,255,255),
  4917. "paleturquoise2"=>array(174,238,238),
  4918. "paleturquoise3"=>array(150,205,205),
  4919. "paleturquoise4"=>array(102,139,139),
  4920. "cadetblue1"=>array(152,245,255),
  4921. "cadetblue2"=>array(142,229,238),
  4922. "cadetblue3"=>array(122,197,205),
  4923. "cadetblue4"=>array(83,134,139),
  4924. "turquoise1"=>array(0,245,255),
  4925. "turquoise2"=>array(0,229,238),
  4926. "turquoise3"=>array(0,197,205),
  4927. "turquoise4"=>array(0,134,139),
  4928. "cyan1"=>array(0,255,255),
  4929. "cyan2"=>array(0,238,238),
  4930. "cyan3"=>array(0,205,205),
  4931. "cyan4"=>array(0,139,139),
  4932. "darkslategray1"=>array(151,255,255),
  4933. "darkslategray2"=>array(141,238,238),
  4934. "darkslategray3"=>array(121,205,205),
  4935. "darkslategray4"=>array(82,139,139),
  4936. "aquamarine1"=>array(127,255,212),
  4937. "aquamarine2"=>array(118,238,198),
  4938. "aquamarine3"=>array(102,205,170),
  4939. "aquamarine4"=>array(69,139,116),
  4940. "darkseagreen1"=>array(193,255,193),
  4941. "darkseagreen2"=>array(180,238,180),
  4942. "darkseagreen3"=>array(155,205,155),
  4943. "darkseagreen4"=>array(105,139,105),
  4944. "seagreen1"=>array(84,255,159),
  4945. "seagreen2"=>array(78,238,148),
  4946. "seagreen3"=>array(67,205,128),
  4947. "seagreen4"=>array(46,139,87),
  4948. "palegreen1"=>array(154,255,154),
  4949. "palegreen2"=>array(144,238,144),
  4950. "palegreen3"=>array(124,205,124),
  4951. "palegreen4"=>array(84,139,84),
  4952. "springgreen1"=>array(0,255,127),
  4953. "springgreen2"=>array(0,238,118),
  4954. "springgreen3"=>array(0,205,102),
  4955. "springgreen4"=>array(0,139,69),
  4956. "chartreuse1"=>array(127,255,0),
  4957. "chartreuse2"=>array(118,238,0),
  4958. "chartreuse3"=>array(102,205,0),
  4959. "chartreuse4"=>array(69,139,0),
  4960. "olivedrab1"=>array(192,255,62),
  4961. "olivedrab2"=>array(179,238,58),
  4962. "olivedrab3"=>array(154,205,50),
  4963. "olivedrab4"=>array(105,139,34),
  4964. "darkolivegreen1"=>array(202,255,112),
  4965. "darkolivegreen2"=>array(188,238,104),
  4966. "darkolivegreen3"=>array(162,205,90),
  4967. "darkolivegreen4"=>array(110,139,61),
  4968. "khaki1"=>array(255,246,143),
  4969. "khaki2"=>array(238,230,133),
  4970. "khaki3"=>array(205,198,115),
  4971. "khaki4"=>array(139,134,78),
  4972. "lightgoldenrod1"=>array(255,236,139),
  4973. "lightgoldenrod2"=>array(238,220,130),
  4974. "lightgoldenrod3"=>array(205,190,112),
  4975. "lightgoldenrod4"=>array(139,129,76),
  4976. "yellow1"=>array(255,255,0),
  4977. "yellow2"=>array(238,238,0),
  4978. "yellow3"=>array(205,205,0),
  4979. "yellow4"=>array(139,139,0),
  4980. "gold1"=>array(255,215,0),
  4981. "gold2"=>array(238,201,0),
  4982. "gold3"=>array(205,173,0),
  4983. "gold4"=>array(139,117,0),
  4984. "goldenrod1"=>array(255,193,37),
  4985. "goldenrod2"=>array(238,180,34),
  4986. "goldenrod3"=>array(205,155,29),
  4987. "goldenrod4"=>array(139,105,20),
  4988. "darkgoldenrod1"=>array(255,185,15),
  4989. "darkgoldenrod2"=>array(238,173,14),
  4990. "darkgoldenrod3"=>array(205,149,12),
  4991. "darkgoldenrod4"=>array(139,101,8),
  4992. "rosybrown1"=>array(255,193,193),
  4993. "rosybrown2"=>array(238,180,180),
  4994. "rosybrown3"=>array(205,155,155),
  4995. "rosybrown4"=>array(139,105,105),
  4996. "indianred1"=>array(255,106,106),
  4997. "indianred2"=>array(238,99,99),
  4998. "indianred3"=>array(205,85,85),
  4999. "indianred4"=>array(139,58,58),
  5000. "sienna1"=>array(255,130,71),
  5001. "sienna2"=>array(238,121,66),
  5002. "sienna3"=>array(205,104,57),
  5003. "sienna4"=>array(139,71,38),
  5004. "burlywood1"=>array(255,211,155),
  5005. "burlywood2"=>array(238,197,145),
  5006. "burlywood3"=>array(205,170,125),
  5007. "burlywood4"=>array(139,115,85),
  5008. "wheat1"=>array(255,231,186),
  5009. "wheat2"=>array(238,216,174),
  5010. "wheat3"=>array(205,186,150),
  5011. "wheat4"=>array(139,126,102),
  5012. "tan1"=>array(255,165,79),
  5013. "tan2"=>array(238,154,73),
  5014. "tan3"=>array(205,133,63),
  5015. "tan4"=>array(139,90,43),
  5016. "chocolate1"=>array(255,127,36),
  5017. "chocolate2"=>array(238,118,33),
  5018. "chocolate3"=>array(205,102,29),
  5019. "chocolate4"=>array(139,69,19),
  5020. "firebrick1"=>array(255,48,48),
  5021. "firebrick2"=>array(238,44,44),
  5022. "firebrick3"=>array(205,38,38),
  5023. "firebrick4"=>array(139,26,26),
  5024. "brown1"=>array(255,64,64),
  5025. "brown2"=>array(238,59,59),
  5026. "brown3"=>array(205,51,51),
  5027. "brown4"=>array(139,35,35),
  5028. "salmon1"=>array(255,140,105),
  5029. "salmon2"=>array(238,130,98),
  5030. "salmon3"=>array(205,112,84),
  5031. "salmon4"=>array(139,76,57),
  5032. "lightsalmon1"=>array(255,160,122),
  5033. "lightsalmon2"=>array(238,149,114),
  5034. "lightsalmon3"=>array(205,129,98),
  5035. "lightsalmon4"=>array(139,87,66),
  5036. "orange1"=>array(255,165,0),
  5037. "orange2"=>array(238,154,0),
  5038. "orange3"=>array(205,133,0),
  5039. "orange4"=>array(139,90,0),
  5040. "darkorange1"=>array(255,127,0),
  5041. "darkorange2"=>array(238,118,0),
  5042. "darkorange3"=>array(205,102,0),
  5043. "darkorange4"=>array(139,69,0),
  5044. "coral1"=>array(255,114,86),
  5045. "coral2"=>array(238,106,80),
  5046. "coral3"=>array(205,91,69),
  5047. "coral4"=>array(139,62,47),
  5048. "tomato1"=>array(255,99,71),
  5049. "tomato2"=>array(238,92,66),
  5050. "tomato3"=>array(205,79,57),
  5051. "tomato4"=>array(139,54,38),
  5052. "orangered1"=>array(255,69,0),
  5053. "orangered2"=>array(238,64,0),
  5054. "orangered3"=>array(205,55,0),
  5055. "orangered4"=>array(139,37,0),
  5056. "deeppink1"=>array(255,20,147),
  5057. "deeppink2"=>array(238,18,137),
  5058. "deeppink3"=>array(205,16,118),
  5059. "deeppink4"=>array(139,10,80),
  5060. "hotpink1"=>array(255,110,180),
  5061. "hotpink2"=>array(238,106,167),
  5062. "hotpink3"=>array(205,96,144),
  5063. "hotpink4"=>array(139,58,98),
  5064. "pink1"=>array(255,181,197),
  5065. "pink2"=>array(238,169,184),
  5066. "pink3"=>array(205,145,158),
  5067. "pink4"=>array(139,99,108),
  5068. "lightpink1"=>array(255,174,185),
  5069. "lightpink2"=>array(238,162,173),
  5070. "lightpink3"=>array(205,140,149),
  5071. "lightpink4"=>array(139,95,101),
  5072. "palevioletred1"=>array(255,130,171),
  5073. "palevioletred2"=>array(238,121,159),
  5074. "palevioletred3"=>array(205,104,137),
  5075. "palevioletred4"=>array(139,71,93),
  5076. "maroon1"=>array(255,52,179),
  5077. "maroon2"=>array(238,48,167),
  5078. "maroon3"=>array(205,41,144),
  5079. "maroon4"=>array(139,28,98),
  5080. "violetred1"=>array(255,62,150),
  5081. "violetred2"=>array(238,58,140),
  5082. "violetred3"=>array(205,50,120),
  5083. "violetred4"=>array(139,34,82),
  5084. "magenta1"=>array(255,0,255),
  5085. "magenta2"=>array(238,0,238),
  5086. "magenta3"=>array(205,0,205),
  5087. "magenta4"=>array(139,0,139),
  5088. "mediumred"=>array(140,34,34),
  5089. "orchid1"=>array(255,131,250),
  5090. "orchid2"=>array(238,122,233),
  5091. "orchid3"=>array(205,105,201),
  5092. "orchid4"=>array(139,71,137),
  5093. "plum1"=>array(255,187,255),
  5094. "plum2"=>array(238,174,238),
  5095. "plum3"=>array(205,150,205),
  5096. "plum4"=>array(139,102,139),
  5097. "mediumorchid1"=>array(224,102,255),
  5098. "mediumorchid2"=>array(209,95,238),
  5099. "mediumorchid3"=>array(180,82,205),
  5100. "mediumorchid4"=>array(122,55,139),
  5101. "darkorchid1"=>array(191,62,255),
  5102. "darkorchid2"=>array(178,58,238),
  5103. "darkorchid3"=>array(154,50,205),
  5104. "darkorchid4"=>array(104,34,139),
  5105. "purple1"=>array(155,48,255),
  5106. "purple2"=>array(145,44,238),
  5107. "purple3"=>array(125,38,205),
  5108. "purple4"=>array(85,26,139),
  5109. "mediumpurple1"=>array(171,130,255),
  5110. "mediumpurple2"=>array(159,121,238),
  5111. "mediumpurple3"=>array(137,104,205),
  5112. "mediumpurple4"=>array(93,71,139),
  5113. "thistle1"=>array(255,225,255),
  5114. "thistle2"=>array(238,210,238),
  5115. "thistle3"=>array(205,181,205),
  5116. "thistle4"=>array(139,123,139),
  5117. "gray1"=>array(10,10,10),
  5118. "gray2"=>array(40,40,30),
  5119. "gray3"=>array(70,70,70),
  5120. "gray4"=>array(100,100,100),
  5121. "gray5"=>array(130,130,130),
  5122. "gray6"=>array(160,160,160),
  5123. "gray7"=>array(190,190,190),
  5124. "gray8"=>array(210,210,210),
  5125. "gray9"=>array(240,240,240),
  5126. "darkgray"=>array(100,100,100),
  5127. "darkblue"=>array(0,0,139),
  5128. "darkcyan"=>array(0,139,139),
  5129. "darkmagenta"=>array(139,0,139),
  5130. "darkred"=>array(139,0,0),
  5131. "silver"=>array(192, 192, 192),
  5132. "eggplant"=>array(144,176,168),
  5133. "lightgreen"=>array(144,238,144));
  5134. }
  5135. //----------------
  5136. // PUBLIC METHODS
  5137. // Colors can be specified as either
  5138. // 1. #xxxxxx HTML style
  5139. // 2. "colorname" as a named color
  5140. // 3. array(r,g,b) RGB triple
  5141. // This function translates this to a native RGB format and returns an
  5142. // RGB triple.
  5143. function Color($aColor) {
  5144. if (is_string($aColor)) {
  5145. // Strip of any alpha factor
  5146. $pos = strpos($aColor,'@');
  5147. if( $pos === false ) {
  5148. $alpha = 0;
  5149. }
  5150. else {
  5151. $pos2 = strpos($aColor,':');
  5152. if( $pos2===false )
  5153. $pos2 = $pos-1; // Sentinel
  5154. if( $pos > $pos2 ) {
  5155. $alpha = substr($aColor,$pos+1);
  5156. $aColor = substr($aColor,0,$pos);
  5157. }
  5158. else {
  5159. $alpha = substr($aColor,$pos+1,$pos2-$pos-1);
  5160. $aColor = substr($aColor,0,$pos).substr($aColor,$pos2);
  5161. }
  5162. }
  5163. // Extract potential adjustment figure at end of color
  5164. // specification
  5165. $pos = strpos($aColor,":");
  5166. if( $pos === false ) {
  5167. $adj = 1.0;
  5168. }
  5169. else {
  5170. $adj = 0.0 + substr($aColor,$pos+1);
  5171. $aColor = substr($aColor,0,$pos);
  5172. }
  5173. if( $adj < 0 )
  5174. JpGraphError::RaiseL(25077);//('Adjustment factor for color must be > 0');
  5175. if (substr($aColor, 0, 1) == "#") {
  5176. $r = hexdec(substr($aColor, 1, 2));
  5177. $g = hexdec(substr($aColor, 3, 2));
  5178. $b = hexdec(substr($aColor, 5, 2));
  5179. } else {
  5180. if(!isset($this->rgb_table[$aColor]) )
  5181. JpGraphError::RaiseL(25078,$aColor);//(" Unknown color: $aColor");
  5182. $tmp=$this->rgb_table[$aColor];
  5183. $r = $tmp[0];
  5184. $g = $tmp[1];
  5185. $b = $tmp[2];
  5186. }
  5187. // Scale adj so that an adj=2 always
  5188. // makes the color 100% white (i.e. 255,255,255.
  5189. // and adj=1 neutral and adj=0 black.
  5190. if( $adj > 1 ) {
  5191. $m = ($adj-1.0)*(255-min(255,min($r,min($g,$b))));
  5192. return array(min(255,$r+$m), min(255,$g+$m), min(255,$b+$m),$alpha);
  5193. }
  5194. elseif( $adj < 1 ) {
  5195. $m = ($adj-1.0)*max(255,max($r,max($g,$b)));
  5196. return array(max(0,$r+$m), max(0,$g+$m), max(0,$b+$m),$alpha);
  5197. }
  5198. else {
  5199. return array($r,$g,$b,$alpha);
  5200. }
  5201. } elseif( is_array($aColor) ) {
  5202. if( count($aColor)==3 ) {
  5203. $aColor[3]=0;
  5204. return $aColor;
  5205. }
  5206. else
  5207. return $aColor;
  5208. }
  5209. else
  5210. JpGraphError::RaiseL(25079,$aColor,count($aColor));//(" Unknown color specification: $aColor , size=".count($aColor));
  5211. }
  5212. // Compare two colors
  5213. // return true if equal
  5214. function Equal($aCol1,$aCol2) {
  5215. $c1 = $this->Color($aCol1);
  5216. $c2 = $this->Color($aCol2);
  5217. if( $c1[0]==$c2[0] && $c1[1]==$c2[1] && $c1[2]==$c2[2] )
  5218. return true;
  5219. else
  5220. return false;
  5221. }
  5222. // Allocate a new color in the current image
  5223. // Return new color index, -1 if no more colors could be allocated
  5224. function Allocate($aColor,$aAlpha=0.0) {
  5225. list ($r, $g, $b, $a) = $this->color($aColor);
  5226. // If alpha is specified in the color string then this
  5227. // takes precedence over the second argument
  5228. if( $a > 0 )
  5229. $aAlpha = $a;
  5230. if( $GLOBALS['gd2'] ) {
  5231. if( $aAlpha < 0 || $aAlpha > 1 ) {
  5232. JpGraphError::RaiseL(25080);//('Alpha parameter for color must be between 0.0 and 1.0');
  5233. }
  5234. return imagecolorresolvealpha($this->img, $r, $g, $b, round($aAlpha * 127));
  5235. } else {
  5236. $index = imagecolorexact($this->img, $r, $g, $b);
  5237. if ($index == -1) {
  5238. $index = imagecolorallocate($this->img, $r, $g, $b);
  5239. if( USE_APPROX_COLORS && $index == -1 )
  5240. $index = imagecolorresolve($this->img, $r, $g, $b);
  5241. }
  5242. return $index;
  5243. }
  5244. }
  5245. } // Class
  5246. //===================================================
  5247. // CLASS Image
  5248. // Description: Wrapper class with some goodies to form the
  5249. // Interface to low level image drawing routines.
  5250. //===================================================
  5251. class Image {
  5252. var $img_format;
  5253. var $expired=true;
  5254. var $img=null;
  5255. var $left_margin=30,$right_margin=20,$top_margin=20,$bottom_margin=30;
  5256. var $plotwidth=0,$plotheight=0;
  5257. var $rgb=null;
  5258. var $current_color,$current_color_name;
  5259. var $lastx=0, $lasty=0;
  5260. var $width=0, $height=0;
  5261. var $line_weight=1;
  5262. var $line_style=1; // Default line style is solid
  5263. var $obs_list=array();
  5264. var $font_size=12,$font_family=FF_FONT1, $font_style=FS_NORMAL;
  5265. var $font_file='';
  5266. var $text_halign="left",$text_valign="bottom";
  5267. var $ttf=null;
  5268. var $use_anti_aliasing=false;
  5269. var $quality=null;
  5270. var $colorstack=array(),$colorstackidx=0;
  5271. var $canvascolor = 'white' ;
  5272. var $langconv = null ;
  5273. //---------------
  5274. // CONSTRUCTOR
  5275. function Image($aWidth,$aHeight,$aFormat=DEFAULT_GFORMAT) {
  5276. $this->CreateImgCanvas($aWidth,$aHeight);
  5277. $this->SetAutoMargin();
  5278. if( !$this->SetImgFormat($aFormat) ) {
  5279. JpGraphError::RaiseL(25081,$aFormat);//("JpGraph: Selected graphic format is either not supported or unknown [$aFormat]");
  5280. }
  5281. $this->ttf = new TTF();
  5282. $this->langconv = new LanguageConv();
  5283. }
  5284. // Should we use anti-aliasing. Note: This really slows down graphics!
  5285. function SetAntiAliasing() {
  5286. $this->use_anti_aliasing=true;
  5287. }
  5288. function CreateRawCanvas($aWidth=0,$aHeight=0) {
  5289. if( $aWidth <= 1 || $aHeight <= 1 ) {
  5290. JpGraphError::RaiseL(25082,$aWidth,$aHeight);//("Illegal sizes specified for width or height when creating an image, (width=$aWidth, height=$aHeight)");
  5291. }
  5292. if( @$GLOBALS['gd2']==true && USE_TRUECOLOR ) {
  5293. $this->img = @imagecreatetruecolor($aWidth, $aHeight);
  5294. if( $this->img < 1 ) {
  5295. JpGraphError::RaiseL(25126);
  5296. //die("Can't create truecolor image. Check that you really have GD2 library installed.");
  5297. }
  5298. $this->SetAlphaBlending();
  5299. } else {
  5300. $this->img = @imagecreate($aWidth, $aHeight);
  5301. if( $this->img < 1 ) {
  5302. JpGraphError::RaiseL(25126);
  5303. //die("<b>JpGraph Error:</b> Can't create image. Check that you really have the GD library installed.");
  5304. }
  5305. }
  5306. if( $this->rgb != null )
  5307. $this->rgb->img = $this->img ;
  5308. else
  5309. $this->rgb = new RGB($this->img);
  5310. }
  5311. function CloneCanvasH() {
  5312. $oldimage = $this->img;
  5313. $this->CreateRawCanvas($this->width,$this->height);
  5314. imagecopy($this->img,$oldimage,0,0,0,0,$this->width,$this->height);
  5315. return $oldimage;
  5316. }
  5317. function CreateImgCanvas($aWidth=0,$aHeight=0) {
  5318. $old = array($this->img,$this->width,$this->height);
  5319. $aWidth = round($aWidth);
  5320. $aHeight = round($aHeight);
  5321. $this->width=$aWidth;
  5322. $this->height=$aHeight;
  5323. if( $aWidth==0 || $aHeight==0 ) {
  5324. // We will set the final size later.
  5325. // Note: The size must be specified before any other
  5326. // img routines that stroke anything are called.
  5327. $this->img = null;
  5328. $this->rgb = null;
  5329. return $old;
  5330. }
  5331. $this->CreateRawCanvas($aWidth,$aHeight);
  5332. // Set canvas color (will also be the background color for a
  5333. // a pallett image
  5334. $this->SetColor($this->canvascolor);
  5335. $this->FilledRectangle(0,0,$aWidth,$aHeight);
  5336. return $old ;
  5337. }
  5338. function CopyCanvasH($aToHdl,$aFromHdl,$aToX,$aToY,$aFromX,$aFromY,$aWidth,$aHeight,$aw=-1,$ah=-1) {
  5339. if( $aw === -1 ) {
  5340. $aw = $aWidth;
  5341. $ah = $aHeight;
  5342. $f = 'imagecopyresized';
  5343. }
  5344. else {
  5345. $f = $GLOBALS['copyfunc'] ;
  5346. }
  5347. $f($aToHdl,$aFromHdl,
  5348. $aToX,$aToY,$aFromX,$aFromY, $aWidth,$aHeight,$aw,$ah);
  5349. }
  5350. function Copy($fromImg,$toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$fromWidth=-1,$fromHeight=-1) {
  5351. $this->CopyCanvasH($this->img,$fromImg,$toX,$toY,$fromX,$fromY,
  5352. $toWidth,$toHeight,$fromWidth,$fromHeight);
  5353. }
  5354. function CopyMerge($fromImg,$toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$fromWidth=-1,$fromHeight=-1,$aMix=100) {
  5355. if( $aMix == 100 ) {
  5356. $this->CopyCanvasH($this->img,$fromImg,
  5357. $toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$fromWidth,$fromHeight);
  5358. }
  5359. else {
  5360. if( ($fromWidth != -1 && ($fromWidth != $toWidth)) ||
  5361. ($fromHeight != -1 && ($fromHeight != $fromHeight)) ) {
  5362. // Create a new canvas that will hold the re-scaled original from image
  5363. if( $toWidth <= 1 || $toHeight <= 1 ) {
  5364. JpGraphError::RaiseL(25083);//('Illegal image size when copying image. Size for copied to image is 1 pixel or less.');
  5365. }
  5366. if( @$GLOBALS['gd2']==true && USE_TRUECOLOR ) {
  5367. $tmpimg = @imagecreatetruecolor($toWidth, $toHeight);
  5368. } else {
  5369. $tmpimg = @imagecreate($toWidth, $toHeight);
  5370. }
  5371. if( $tmpimg < 1 ) {
  5372. JpGraphError::RaiseL(25084);//('Failed to create temporary GD canvas. Out of memory ?');
  5373. }
  5374. $this->CopyCanvasH($tmpimg,$fromImg,0,0,0,0,
  5375. $toWidth,$toHeight,$fromWidth,$fromHeight);
  5376. $fromImg = $tmpimg;
  5377. }
  5378. imagecopymerge($this->img,$fromImg,$toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$aMix);
  5379. }
  5380. }
  5381. function GetWidth($aImg=null) {
  5382. if( $aImg === null )
  5383. $aImg = $this->img;
  5384. return imagesx($aImg);
  5385. }
  5386. function GetHeight($aImg=null) {
  5387. if( $aImg === null )
  5388. $aImg = $this->img;
  5389. return imagesy($aImg);
  5390. }
  5391. function CreateFromString($aStr) {
  5392. $img = imagecreatefromstring($aStr);
  5393. if( $img === false ) {
  5394. JpGraphError::RaiseL(25085);//('An image can not be created from the supplied string. It is either in a format not supported or the string is representing an corrupt image.');
  5395. }
  5396. return $img;
  5397. }
  5398. function SetCanvasH($aHdl) {
  5399. $this->img = $aHdl;
  5400. $this->rgb->img = $aHdl;
  5401. }
  5402. function SetCanvasColor($aColor) {
  5403. $this->canvascolor = $aColor ;
  5404. }
  5405. function SetAlphaBlending($aFlg=true) {
  5406. if( $GLOBALS['gd2'] )
  5407. ImageAlphaBlending($this->img,$aFlg);
  5408. else
  5409. JpGraphError::RaiseL(25086);//('You only seem to have GD 1.x installed. To enable Alphablending requires GD 2.x or higher. Please install GD or make sure the constant USE_GD2 is specified correctly to reflect your installation. By default it tries to autodetect what version of GD you have installed. On some very rare occasions it may falsely detect GD2 where only GD1 is installed. You must then set USE_GD2 to false.');
  5410. }
  5411. function SetAutoMargin() {
  5412. GLOBAL $gJpgBrandTiming;
  5413. $min_bm=10;
  5414. /*
  5415. if( $gJpgBrandTiming )
  5416. $min_bm=15;
  5417. */
  5418. $lm = min(40,$this->width/7);
  5419. $rm = min(20,$this->width/10);
  5420. $tm = max(20,$this->height/7);
  5421. $bm = max($min_bm,$this->height/7);
  5422. $this->SetMargin($lm,$rm,$tm,$bm);
  5423. }
  5424. //---------------
  5425. // PUBLIC METHODS
  5426. function SetFont($family,$style=FS_NORMAL,$size=10) {
  5427. $this->font_family=$family;
  5428. $this->font_style=$style;
  5429. $this->font_size=$size;
  5430. $this->font_file='';
  5431. if( ($this->font_family==FF_FONT1 || $this->font_family==FF_FONT2) && $this->font_style==FS_BOLD ){
  5432. ++$this->font_family;
  5433. }
  5434. if( $this->font_family > FF_FONT2+1 ) { // A TTF font so get the font file
  5435. // Check that this PHP has support for TTF fonts
  5436. if( !function_exists('imagettfbbox') ) {
  5437. JpGraphError::RaiseL(25087);//('This PHP build has not been configured with TTF support. You need to recompile your PHP installation with FreeType support.');
  5438. exit();
  5439. }
  5440. $this->font_file = $this->ttf->File($this->font_family,$this->font_style);
  5441. }
  5442. }
  5443. // Get the specific height for a text string
  5444. function GetTextHeight($txt="",$angle=0) {
  5445. $tmp = split("\n",$txt);
  5446. $n = count($tmp);
  5447. $m=0;
  5448. for($i=0; $i< $n; ++$i)
  5449. $m = max($m,strlen($tmp[$i]));
  5450. if( $this->font_family <= FF_FONT2+1 ) {
  5451. if( $angle==0 ) {
  5452. $h = imagefontheight($this->font_family);
  5453. if( $h === false ) {
  5454. JpGraphError::RaiseL(25088);//('You have a misconfigured GD font support. The call to imagefontwidth() fails.');
  5455. }
  5456. return $n*$h;
  5457. }
  5458. else {
  5459. $w = @imagefontwidth($this->font_family);
  5460. if( $w === false ) {
  5461. JpGraphError::RaiseL(25088);//('You have a misconfigured GD font support. The call to imagefontwidth() fails.');
  5462. }
  5463. return $m*$w;
  5464. }
  5465. }
  5466. else {
  5467. $bbox = $this->GetTTFBBox($txt,$angle);
  5468. return $bbox[1]-$bbox[5];
  5469. }
  5470. }
  5471. // Estimate font height
  5472. function GetFontHeight($angle=0) {
  5473. $txt = "XOMg";
  5474. return $this->GetTextHeight($txt,$angle);
  5475. }
  5476. // Approximate font width with width of letter "O"
  5477. function GetFontWidth($angle=0) {
  5478. $txt = 'O';
  5479. return $this->GetTextWidth($txt,$angle);
  5480. }
  5481. // Get actual width of text in absolute pixels
  5482. function GetTextWidth($txt,$angle=0) {
  5483. $tmp = split("\n",$txt);
  5484. $n = count($tmp);
  5485. if( $this->font_family <= FF_FONT2+1 ) {
  5486. $m=0;
  5487. for($i=0; $i < $n; ++$i) {
  5488. $l=strlen($tmp[$i]);
  5489. if( $l > $m ) {
  5490. $m = $l;
  5491. }
  5492. }
  5493. if( $angle==0 ) {
  5494. $w = @imagefontwidth($this->font_family);
  5495. if( $w === false ) {
  5496. JpGraphError::RaiseL(25088);//('You have a misconfigured GD font support. The call to imagefontwidth() fails.');
  5497. }
  5498. return $m*$w;
  5499. }
  5500. else {
  5501. // 90 degrees internal so height becomes width
  5502. $h = @imagefontheight($this->font_family);
  5503. if( $h === false ) {
  5504. JpGraphError::RaiseL(25089);//('You have a misconfigured GD font support. The call to imagefontheight() fails.');
  5505. }
  5506. return $n*$h;
  5507. }
  5508. }
  5509. else {
  5510. // For TTF fonts we must walk through a lines and find the
  5511. // widest one which we use as the width of the multi-line
  5512. // paragraph
  5513. $m=0;
  5514. for( $i=0; $i < $n; ++$i ) {
  5515. $bbox = $this->GetTTFBBox($tmp[$i],$angle);
  5516. $mm = $bbox[2] - $bbox[0];
  5517. if( $mm > $m )
  5518. $m = $mm;
  5519. }
  5520. return $m;
  5521. }
  5522. }
  5523. // Draw text with a box around it
  5524. function StrokeBoxedText($x,$y,$txt,$dir=0,$fcolor="white",$bcolor="black",
  5525. $shadowcolor=false,$paragraph_align="left",
  5526. $xmarg=6,$ymarg=4,$cornerradius=0,$dropwidth=3) {
  5527. if( !is_numeric($dir) ) {
  5528. if( $dir=="h" ) $dir=0;
  5529. elseif( $dir=="v" ) $dir=90;
  5530. else JpGraphError::RaiseL(25090,$dir);//(" Unknown direction specified in call to StrokeBoxedText() [$dir]");
  5531. }
  5532. if( $this->font_family >= FF_FONT0 && $this->font_family <= FF_FONT2+1) {
  5533. $width=$this->GetTextWidth($txt,$dir) ;
  5534. $height=$this->GetTextHeight($txt,$dir) ;
  5535. }
  5536. else {
  5537. $width=$this->GetBBoxWidth($txt,$dir) ;
  5538. $height=$this->GetBBoxHeight($txt,$dir) ;
  5539. }
  5540. $height += 2*$ymarg;
  5541. $width += 2*$xmarg;
  5542. if( $this->text_halign=="right" ) $x -= $width;
  5543. elseif( $this->text_halign=="center" ) $x -= $width/2;
  5544. if( $this->text_valign=="bottom" ) $y -= $height;
  5545. elseif( $this->text_valign=="center" ) $y -= $height/2;
  5546. if( $shadowcolor ) {
  5547. $this->PushColor($shadowcolor);
  5548. $this->FilledRoundedRectangle($x-$xmarg+$dropwidth,$y-$ymarg+$dropwidth,
  5549. $x+$width+$dropwidth,$y+$height-$ymarg+$dropwidth,
  5550. $cornerradius);
  5551. $this->PopColor();
  5552. $this->PushColor($fcolor);
  5553. $this->FilledRoundedRectangle($x-$xmarg,$y-$ymarg,
  5554. $x+$width,$y+$height-$ymarg,
  5555. $cornerradius);
  5556. $this->PopColor();
  5557. $this->PushColor($bcolor);
  5558. $this->RoundedRectangle($x-$xmarg,$y-$ymarg,
  5559. $x+$width,$y+$height-$ymarg,$cornerradius);
  5560. $this->PopColor();
  5561. }
  5562. else {
  5563. if( $fcolor ) {
  5564. $oc=$this->current_color;
  5565. $this->SetColor($fcolor);
  5566. $this->FilledRoundedRectangle($x-$xmarg,$y-$ymarg,$x+$width,$y+$height-$ymarg,$cornerradius);
  5567. $this->current_color=$oc;
  5568. }
  5569. if( $bcolor ) {
  5570. $oc=$this->current_color;
  5571. $this->SetColor($bcolor);
  5572. $this->RoundedRectangle($x-$xmarg,$y-$ymarg,$x+$width,$y+$height-$ymarg,$cornerradius);
  5573. $this->current_color=$oc;
  5574. }
  5575. }
  5576. $h=$this->text_halign;
  5577. $v=$this->text_valign;
  5578. $this->SetTextAlign("left","top");
  5579. $this->StrokeText($x, $y, $txt, $dir, $paragraph_align);
  5580. $bb = array($x-$xmarg,$y+$height-$ymarg,$x+$width,$y+$height-$ymarg,
  5581. $x+$width,$y-$ymarg,$x-$xmarg,$y-$ymarg);
  5582. $this->SetTextAlign($h,$v);
  5583. return $bb;
  5584. }
  5585. // Set text alignment
  5586. function SetTextAlign($halign,$valign="bottom") {
  5587. $this->text_halign=$halign;
  5588. $this->text_valign=$valign;
  5589. }
  5590. function _StrokeBuiltinFont($x,$y,$txt,$dir=0,$paragraph_align="left",&$aBoundingBox,$aDebug=false) {
  5591. if( is_numeric($dir) && $dir!=90 && $dir!=0)
  5592. JpGraphError::RaiseL(25091);//(" Internal font does not support drawing text at arbitrary angle. Use TTF fonts instead.");
  5593. $h=$this->GetTextHeight($txt);
  5594. $fh=$this->GetFontHeight();
  5595. $w=$this->GetTextWidth($txt);
  5596. if( $this->text_halign=="right")
  5597. $x -= $dir==0 ? $w : $h;
  5598. elseif( $this->text_halign=="center" ) {
  5599. // For center we subtract 1 pixel since this makes the middle
  5600. // be prefectly in the middle
  5601. $x -= $dir==0 ? $w/2-1 : $h/2;
  5602. }
  5603. if( $this->text_valign=="top" )
  5604. $y += $dir==0 ? $h : $w;
  5605. elseif( $this->text_valign=="center" )
  5606. $y += $dir==0 ? $h/2 : $w/2;
  5607. if( $dir==90 ) {
  5608. imagestringup($this->img,$this->font_family,$x,$y,$txt,$this->current_color);
  5609. $aBoundingBox = array(round($x),round($y),round($x),round($y-$w),round($x+$h),round($y-$w),round($x+$h),round($y));
  5610. if( $aDebug ) {
  5611. // Draw bounding box
  5612. $this->PushColor('green');
  5613. $this->Polygon($aBoundingBox,true);
  5614. $this->PopColor();
  5615. }
  5616. }
  5617. else {
  5618. if( ereg("\n",$txt) ) {
  5619. $tmp = split("\n",$txt);
  5620. for($i=0; $i < count($tmp); ++$i) {
  5621. $w1 = $this->GetTextWidth($tmp[$i]);
  5622. if( $paragraph_align=="left" ) {
  5623. imagestring($this->img,$this->font_family,$x,$y-$h+1+$i*$fh,$tmp[$i],$this->current_color);
  5624. }
  5625. elseif( $paragraph_align=="right" ) {
  5626. imagestring($this->img,$this->font_family,$x+($w-$w1),
  5627. $y-$h+1+$i*$fh,$tmp[$i],$this->current_color);
  5628. }
  5629. else {
  5630. imagestring($this->img,$this->font_family,$x+$w/2-$w1/2,
  5631. $y-$h+1+$i*$fh,$tmp[$i],$this->current_color);
  5632. }
  5633. }
  5634. }
  5635. else {
  5636. //Put the text
  5637. imagestring($this->img,$this->font_family,$x,$y-$h+1,$txt,$this->current_color);
  5638. }
  5639. if( $aDebug ) {
  5640. // Draw the bounding rectangle and the bounding box
  5641. $p1 = array(round($x),round($y),round($x),round($y-$h),round($x+$w),round($y-$h),round($x+$w),round($y));
  5642. // Draw bounding box
  5643. $this->PushColor('green');
  5644. $this->Polygon($p1,true);
  5645. $this->PopColor();
  5646. }
  5647. $aBoundingBox=array(round($x),round($y),round($x),round($y-$h),round($x+$w),round($y-$h),round($x+$w),round($y));
  5648. }
  5649. }
  5650. function AddTxtCR($aTxt) {
  5651. // If the user has just specified a '\n'
  5652. // instead of '\n\t' we have to add '\r' since
  5653. // the width will be too muchy otherwise since when
  5654. // we print we stroke the individually lines by hand.
  5655. $e = explode("\n",$aTxt);
  5656. $n = count($e);
  5657. for($i=0; $i<$n; ++$i) {
  5658. $e[$i]=str_replace("\r","",$e[$i]);
  5659. }
  5660. return implode("\n\r",$e);
  5661. }
  5662. function GetTTFBBox($aTxt,$aAngle=0) {
  5663. $bbox = @ImageTTFBBox($this->font_size,$aAngle,$this->font_file,$aTxt);
  5664. if( $bbox === false ) {
  5665. JpGraphError::RaiseL(25092,$this->font_file);
  5666. //("There is either a configuration problem with TrueType or a problem reading font file (".$this->font_file."). Make sure file exists and is in a readable place for the HTTP process. (If 'basedir' restriction is enabled in PHP then the font file must be located in the document root.). It might also be a wrongly installed FreeType library. Try uppgrading to at least FreeType 2.1.13 and recompile GD with the correct setup so it can find the new FT library.");
  5667. }
  5668. return $bbox;
  5669. }
  5670. function GetBBoxTTF($aTxt,$aAngle=0) {
  5671. // Normalize the bounding box to become a minimum
  5672. // enscribing rectangle
  5673. $aTxt = $this->AddTxtCR($aTxt);
  5674. if( !is_readable($this->font_file) ) {
  5675. JpGraphError::RaiseL(25093,$this->font_file);
  5676. //('Can not read font file ('.$this->font_file.') in call to Image::GetBBoxTTF. Please make sure that you have set a font before calling this method and that the font is installed in the TTF directory.');
  5677. }
  5678. $bbox = $this->GetTTFBBox($aTxt,$aAngle);
  5679. if( $aAngle==0 )
  5680. return $bbox;
  5681. if( $aAngle >= 0 ) {
  5682. if( $aAngle <= 90 ) { //<=0
  5683. $bbox = array($bbox[6],$bbox[1],$bbox[2],$bbox[1],
  5684. $bbox[2],$bbox[5],$bbox[6],$bbox[5]);
  5685. }
  5686. elseif( $aAngle <= 180 ) { //<= 2
  5687. $bbox = array($bbox[4],$bbox[7],$bbox[0],$bbox[7],
  5688. $bbox[0],$bbox[3],$bbox[4],$bbox[3]);
  5689. }
  5690. elseif( $aAngle <= 270 ) { //<= 3
  5691. $bbox = array($bbox[2],$bbox[5],$bbox[6],$bbox[5],
  5692. $bbox[6],$bbox[1],$bbox[2],$bbox[1]);
  5693. }
  5694. else {
  5695. $bbox = array($bbox[0],$bbox[3],$bbox[4],$bbox[3],
  5696. $bbox[4],$bbox[7],$bbox[0],$bbox[7]);
  5697. }
  5698. }
  5699. elseif( $aAngle < 0 ) {
  5700. if( $aAngle <= -270 ) { // <= -3
  5701. $bbox = array($bbox[6],$bbox[1],$bbox[2],$bbox[1],
  5702. $bbox[2],$bbox[5],$bbox[6],$bbox[5]);
  5703. }
  5704. elseif( $aAngle <= -180 ) { // <= -2
  5705. $bbox = array($bbox[0],$bbox[3],$bbox[4],$bbox[3],
  5706. $bbox[4],$bbox[7],$bbox[0],$bbox[7]);
  5707. }
  5708. elseif( $aAngle <= -90 ) { // <= -1
  5709. $bbox = array($bbox[2],$bbox[5],$bbox[6],$bbox[5],
  5710. $bbox[6],$bbox[1],$bbox[2],$bbox[1]);
  5711. }
  5712. else {
  5713. $bbox = array($bbox[0],$bbox[3],$bbox[4],$bbox[3],
  5714. $bbox[4],$bbox[7],$bbox[0],$bbox[7]);
  5715. }
  5716. }
  5717. return $bbox;
  5718. }
  5719. function GetBBoxHeight($aTxt,$aAngle=0) {
  5720. $box = $this->GetBBoxTTF($aTxt,$aAngle);
  5721. return $box[1]-$box[7]+1;
  5722. }
  5723. function GetBBoxWidth($aTxt,$aAngle=0) {
  5724. $box = $this->GetBBoxTTF($aTxt,$aAngle);
  5725. return $box[2]-$box[0]+1;
  5726. }
  5727. function _StrokeTTF($x,$y,$txt,$dir=0,$paragraph_align="left",&$aBoundingBox,$debug=false) {
  5728. // Setupo default inter line margin for paragraphs to
  5729. // 25% of the font height.
  5730. $ConstLineSpacing = 0.25 ;
  5731. // Remember the anchor point before adjustment
  5732. if( $debug ) {
  5733. $ox=$x;
  5734. $oy=$y;
  5735. }
  5736. if( !ereg("\n",$txt) || ($dir>0 && ereg("\n",$txt)) ) {
  5737. // Format a single line
  5738. $txt = $this->AddTxtCR($txt);
  5739. $bbox=$this->GetBBoxTTF($txt,$dir);
  5740. // Align x,y ot lower left corner of bbox
  5741. $x -= $bbox[0];
  5742. $y -= $bbox[1];
  5743. // Note to self: "topanchor" is deprecated after we changed the
  5744. // bopunding box stuff.
  5745. if( $this->text_halign=="right" || $this->text_halign=="topanchor" )
  5746. $x -= $bbox[2]-$bbox[0];
  5747. elseif( $this->text_halign=="center" ) $x -= ($bbox[2]-$bbox[0])/2;
  5748. if( $this->text_valign=="top" ) $y += abs($bbox[5])+$bbox[1];
  5749. elseif( $this->text_valign=="center" ) $y -= ($bbox[5]-$bbox[1])/2;
  5750. ImageTTFText ($this->img, $this->font_size, $dir, $x, $y,
  5751. $this->current_color,$this->font_file,$txt);
  5752. // Calculate and return the co-ordinates for the bounding box
  5753. $box=@ImageTTFBBox($this->font_size,$dir,$this->font_file,$txt);
  5754. $p1 = array();
  5755. for($i=0; $i < 4; ++$i) {
  5756. $p1[] = round($box[$i*2]+$x);
  5757. $p1[] = round($box[$i*2+1]+$y);
  5758. }
  5759. $aBoundingBox = $p1;
  5760. // Debugging code to highlight the bonding box and bounding rectangle
  5761. // For text at 0 degrees the bounding box and bounding rectangle are the
  5762. // same
  5763. if( $debug ) {
  5764. // Draw the bounding rectangle and the bounding box
  5765. $box=@ImageTTFBBox($this->font_size,$dir,$this->font_file,$txt);
  5766. $p = array();
  5767. $p1 = array();
  5768. for($i=0; $i < 4; ++$i) {
  5769. $p[] = $bbox[$i*2]+$x;
  5770. $p[] = $bbox[$i*2+1]+$y;
  5771. $p1[] = $box[$i*2]+$x;
  5772. $p1[] = $box[$i*2+1]+$y;
  5773. }
  5774. // Draw bounding box
  5775. $this->PushColor('green');
  5776. $this->Polygon($p1,true);
  5777. $this->PopColor();
  5778. // Draw bounding rectangle
  5779. $this->PushColor('darkgreen');
  5780. $this->Polygon($p,true);
  5781. $this->PopColor();
  5782. // Draw a cross at the anchor point
  5783. $this->PushColor('red');
  5784. $this->Line($ox-15,$oy,$ox+15,$oy);
  5785. $this->Line($ox,$oy-15,$ox,$oy+15);
  5786. $this->PopColor();
  5787. }
  5788. }
  5789. else {
  5790. // Format a text paragraph
  5791. $fh=$this->GetFontHeight();
  5792. // Line margin is 25% of font height
  5793. $linemargin=round($fh*$ConstLineSpacing);
  5794. $fh += $linemargin;
  5795. $w=$this->GetTextWidth($txt);
  5796. $y -= $linemargin/2;
  5797. $tmp = split("\n",$txt);
  5798. $nl = count($tmp);
  5799. $h = $nl * $fh;
  5800. if( $this->text_halign=="right")
  5801. $x -= $dir==0 ? $w : $h;
  5802. elseif( $this->text_halign=="center" ) {
  5803. $x -= $dir==0 ? $w/2 : $h/2;
  5804. }
  5805. if( $this->text_valign=="top" )
  5806. $y += $dir==0 ? $h : $w;
  5807. elseif( $this->text_valign=="center" )
  5808. $y += $dir==0 ? $h/2 : $w/2;
  5809. // Here comes a tricky bit.
  5810. // Since we have to give the position for the string at the
  5811. // baseline this means thaht text will move slightly up
  5812. // and down depending on any of it's character descend below
  5813. // the baseline, for example a 'g'. To adjust the Y-position
  5814. // we therefore adjust the text with the baseline Y-offset
  5815. // as used for the current font and size. This will keep the
  5816. // baseline at a fixed positoned disregarding the actual
  5817. // characters in the string.
  5818. $standardbox = $this->GetTTFBBox('Gg',$dir);
  5819. $yadj = $standardbox[1];
  5820. $xadj = $standardbox[0];
  5821. $aBoundingBox = array();
  5822. for($i=0; $i < $nl; ++$i) {
  5823. $wl = $this->GetTextWidth($tmp[$i]);
  5824. $bbox = $this->GetTTFBBox($tmp[$i],$dir);
  5825. if( $paragraph_align=="left" ) {
  5826. $xl = $x;
  5827. }
  5828. elseif( $paragraph_align=="right" ) {
  5829. $xl = $x + ($w-$wl);
  5830. }
  5831. else {
  5832. // Center
  5833. $xl = $x + $w/2 - $wl/2 ;
  5834. }
  5835. $xl -= $bbox[0];
  5836. $yl = $y - $yadj;
  5837. $xl = $xl - $xadj;
  5838. ImageTTFText ($this->img, $this->font_size, $dir,
  5839. $xl, $yl-($h-$fh)+$fh*$i,
  5840. $this->current_color,$this->font_file,$tmp[$i]);
  5841. if( $debug ) {
  5842. // Draw the bounding rectangle around each line
  5843. $box=@ImageTTFBBox($this->font_size,$dir,$this->font_file,$tmp[$i]);
  5844. $p = array();
  5845. for($j=0; $j < 4; ++$j) {
  5846. $p[] = $bbox[$j*2]+$xl;
  5847. $p[] = $bbox[$j*2+1]+$yl-($h-$fh)+$fh*$i;
  5848. }
  5849. // Draw bounding rectangle
  5850. $this->PushColor('darkgreen');
  5851. $this->Polygon($p,true);
  5852. $this->PopColor();
  5853. }
  5854. }
  5855. // Get the bounding box
  5856. $bbox = $this->GetBBoxTTF($txt,$dir);
  5857. for($j=0; $j < 4; ++$j) {
  5858. $bbox[$j*2]+= round($x);
  5859. $bbox[$j*2+1]+= round($y - ($h-$fh) - $yadj);
  5860. }
  5861. $aBoundingBox = $bbox;
  5862. if( $debug ) {
  5863. // Draw a cross at the anchor point
  5864. $this->PushColor('red');
  5865. $this->Line($ox-25,$oy,$ox+25,$oy);
  5866. $this->Line($ox,$oy-25,$ox,$oy+25);
  5867. $this->PopColor();
  5868. }
  5869. }
  5870. }
  5871. function StrokeText($x,$y,$txt,$dir=0,$paragraph_align="left",$debug=false) {
  5872. $x = round($x);
  5873. $y = round($y);
  5874. // Do special language encoding
  5875. $txt = $this->langconv->Convert($txt,$this->font_family);
  5876. if( !is_numeric($dir) )
  5877. JpGraphError::RaiseL(25094);//(" Direction for text most be given as an angle between 0 and 90.");
  5878. if( $this->font_family >= FF_FONT0 && $this->font_family <= FF_FONT2+1) {
  5879. $this->_StrokeBuiltinFont($x,$y,$txt,$dir,$paragraph_align,$boundingbox,$debug);
  5880. }
  5881. elseif($this->font_family >= _FF_FIRST && $this->font_family <= _FF_LAST) {
  5882. $this->_StrokeTTF($x,$y,$txt,$dir,$paragraph_align,$boundingbox,$debug);
  5883. }
  5884. else
  5885. JpGraphError::RaiseL(25095);//(" Unknown font font family specification. ");
  5886. return $boundingbox;
  5887. }
  5888. function SetMargin($lm,$rm,$tm,$bm) {
  5889. $this->left_margin=$lm;
  5890. $this->right_margin=$rm;
  5891. $this->top_margin=$tm;
  5892. $this->bottom_margin=$bm;
  5893. $this->plotwidth=$this->width - $this->left_margin-$this->right_margin ;
  5894. $this->plotheight=$this->height - $this->top_margin-$this->bottom_margin ;
  5895. if( $this->width > 0 && $this->height > 0 ) {
  5896. if( $this->plotwidth < 0 || $this->plotheight < 0 )
  5897. JpGraphError::raise("Too small plot area. ($lm,$rm,$tm,$bm : $this->plotwidth x $this->plotheight). With the given image size and margins there is to little space left for the plot. Increase the plot size or reduce the margins.");
  5898. }
  5899. }
  5900. function SetTransparent($color) {
  5901. imagecolortransparent ($this->img,$this->rgb->allocate($color));
  5902. }
  5903. function SetColor($color,$aAlpha=0) {
  5904. $this->current_color_name = $color;
  5905. $this->current_color=$this->rgb->allocate($color,$aAlpha);
  5906. if( $this->current_color == -1 ) {
  5907. $tc=imagecolorstotal($this->img);
  5908. JpGraphError::RaiseL(25096);
  5909. //("Can't allocate any more colors. Image has already allocated maximum of <b>$tc colors</b>. This might happen if you have anti-aliasing turned on together with a background image or perhaps gradient fill since this requires many, many colors. Try to turn off anti-aliasing. If there is still a problem try downgrading the quality of the background image to use a smaller pallete to leave some entries for your graphs. You should try to limit the number of colors in your background image to 64. If there is still problem set the constant DEFINE(\"USE_APPROX_COLORS\",true); in jpgraph.php This will use approximative colors when the palette is full. Unfortunately there is not much JpGraph can do about this since the palette size is a limitation of current graphic format and what the underlying GD library suppports.");
  5910. }
  5911. return $this->current_color;
  5912. }
  5913. function PushColor($color) {
  5914. if( $color != "" ) {
  5915. $this->colorstack[$this->colorstackidx]=$this->current_color_name;
  5916. $this->colorstack[$this->colorstackidx+1]=$this->current_color;
  5917. $this->colorstackidx+=2;
  5918. $this->SetColor($color);
  5919. }
  5920. else {
  5921. JpGraphError::RaiseL(25097);//("Color specified as empty string in PushColor().");
  5922. }
  5923. }
  5924. function PopColor() {
  5925. if($this->colorstackidx<1)
  5926. JpGraphError::RaiseL(25098);//(" Negative Color stack index. Unmatched call to PopColor()");
  5927. $this->current_color=$this->colorstack[--$this->colorstackidx];
  5928. $this->current_color_name=$this->colorstack[--$this->colorstackidx];
  5929. }
  5930. // Why this duplication? Because this way we can call this method
  5931. // for any image and not only the current objsct
  5932. function AdjSat($sat) {
  5933. if( $GLOBALS['gd2'] && USE_TRUECOLOR )
  5934. return;
  5935. $this->_AdjSat($this->img,$sat);
  5936. }
  5937. function _AdjSat($img,$sat) {
  5938. $nbr = imagecolorstotal ($img);
  5939. for( $i=0; $i<$nbr; ++$i ) {
  5940. $colarr = imagecolorsforindex ($img,$i);
  5941. $rgb[0]=$colarr["red"];
  5942. $rgb[1]=$colarr["green"];
  5943. $rgb[2]=$colarr["blue"];
  5944. $rgb = $this->AdjRGBSat($rgb,$sat);
  5945. imagecolorset ($img, $i, $rgb[0], $rgb[1], $rgb[2]);
  5946. }
  5947. }
  5948. function AdjBrightContrast($bright,$contr=0) {
  5949. if( $GLOBALS['gd2'] && USE_TRUECOLOR )
  5950. return;
  5951. $this->_AdjBrightContrast($this->img,$bright,$contr);
  5952. }
  5953. function _AdjBrightContrast($img,$bright,$contr=0) {
  5954. if( $bright < -1 || $bright > 1 || $contr < -1 || $contr > 1 )
  5955. JpGraphError::RaiseL(25099);//(" Parameters for brightness and Contrast out of range [-1,1]");
  5956. $nbr = imagecolorstotal ($img);
  5957. for( $i=0; $i<$nbr; ++$i ) {
  5958. $colarr = imagecolorsforindex ($img,$i);
  5959. $r = $this->AdjRGBBrightContrast($colarr["red"],$bright,$contr);
  5960. $g = $this->AdjRGBBrightContrast($colarr["green"],$bright,$contr);
  5961. $b = $this->AdjRGBBrightContrast($colarr["blue"],$bright,$contr);
  5962. imagecolorset ($img, $i, $r, $g, $b);
  5963. }
  5964. }
  5965. // Private helper function for adj sat
  5966. // Adjust saturation for RGB array $u. $sat is a value between -1 and 1
  5967. // Note: Due to GD inability to handle true color the RGB values are only between
  5968. // 8 bit. This makes saturation quite sensitive for small increases in parameter sat.
  5969. //
  5970. // Tip: To get a grayscale picture set sat=-100, values <-100 changes the colors
  5971. // to it's complement.
  5972. //
  5973. // Implementation note: The saturation is implemented directly in the RGB space
  5974. // by adjusting the perpendicular distance between the RGB point and the "grey"
  5975. // line (1,1,1). Setting $sat>0 moves the point away from the line along the perp.
  5976. // distance and a negative value moves the point closer to the line.
  5977. // The values are truncated when the color point hits the bounding box along the
  5978. // RGB axis.
  5979. // DISCLAIMER: I'm not 100% sure this is he correct way to implement a color
  5980. // saturation function in RGB space. However, it looks ok and has the expected effect.
  5981. function AdjRGBSat($rgb,$sat) {
  5982. // TODO: Should be moved to the RGB class
  5983. // Grey vector
  5984. $v=array(1,1,1);
  5985. // Dot product
  5986. $dot = $rgb[0]*$v[0]+$rgb[1]*$v[1]+$rgb[2]*$v[2];
  5987. // Normalize dot product
  5988. $normdot = $dot/3; // dot/|v|^2
  5989. // Direction vector between $u and its projection onto $v
  5990. for($i=0; $i<3; ++$i)
  5991. $r[$i] = $rgb[$i] - $normdot*$v[$i];
  5992. // Adjustment factor so that sat==1 sets the highest RGB value to 255
  5993. if( $sat > 0 ) {
  5994. $m=0;
  5995. for( $i=0; $i<3; ++$i) {
  5996. if( sign($r[$i]) == 1 && $r[$i]>0)
  5997. $m=max($m,(255-$rgb[$i])/$r[$i]);
  5998. }
  5999. $tadj=$m;
  6000. }
  6001. else
  6002. $tadj=1;
  6003. $tadj = $tadj*$sat;
  6004. for($i=0; $i<3; ++$i) {
  6005. $un[$i] = round($rgb[$i] + $tadj*$r[$i]);
  6006. if( $un[$i]<0 ) $un[$i]=0; // Truncate color when they reach 0
  6007. if( $un[$i]>255 ) $un[$i]=255;// Avoid potential rounding error
  6008. }
  6009. return $un;
  6010. }
  6011. // Private helper function for AdjBrightContrast
  6012. function AdjRGBBrightContrast($rgb,$bright,$contr) {
  6013. // TODO: Should be moved to the RGB class
  6014. // First handle contrast, i.e change the dynamic range around grey
  6015. if( $contr <= 0 ) {
  6016. // Decrease contrast
  6017. $adj = abs($rgb-128) * (-$contr);
  6018. if( $rgb < 128 ) $rgb += $adj;
  6019. else $rgb -= $adj;
  6020. }
  6021. else { // $contr > 0
  6022. // Increase contrast
  6023. if( $rgb < 128 ) $rgb = $rgb - ($rgb * $contr);
  6024. else $rgb = $rgb + ((255-$rgb) * $contr);
  6025. }
  6026. // Add (or remove) various amount of white
  6027. $rgb += $bright*255;
  6028. $rgb=min($rgb,255);
  6029. $rgb=max($rgb,0);
  6030. return $rgb;
  6031. }
  6032. function SetLineWeight($weight) {
  6033. $this->line_weight = $weight;
  6034. }
  6035. function SetStartPoint($x,$y) {
  6036. $this->lastx=round($x);
  6037. $this->lasty=round($y);
  6038. }
  6039. function Arc($cx,$cy,$w,$h,$s,$e) {
  6040. // GD Arc doesn't like negative angles
  6041. while( $s < 0) $s += 360;
  6042. while( $e < 0) $e += 360;
  6043. imagearc($this->img,round($cx),round($cy),round($w),round($h),
  6044. $s,$e,$this->current_color);
  6045. }
  6046. function FilledArc($xc,$yc,$w,$h,$s,$e,$style="") {
  6047. if( $GLOBALS['gd2'] ) {
  6048. while( $s < 0 ) $s += 360;
  6049. while( $e < 0 ) $e += 360;
  6050. if( $style=="" )
  6051. $style=IMG_ARC_PIE;
  6052. imagefilledarc($this->img,round($xc),round($yc),round($w),round($h),
  6053. round($s),round($e),$this->current_color,$style);
  6054. return;
  6055. }
  6056. // In GD 1.x we have to do it ourself interesting enough there is surprisingly
  6057. // little difference in time between doing it PHP and using the optimised GD
  6058. // library (roughly ~20%) I had expected it to be at least 100% slower doing it
  6059. // manually with a polygon approximation in PHP.....
  6060. $fillcolor = $this->current_color_name;
  6061. $w /= 2; // We use radius in our calculations instead
  6062. $h /= 2;
  6063. // Setup the angles so we have the same conventions as the builtin
  6064. // FilledArc() which is a little bit strange if you ask me....
  6065. $s = 360-$s;
  6066. $e = 360-$e;
  6067. if( $e > $s ) {
  6068. $e = $e - 360;
  6069. $da = $s - $e;
  6070. }
  6071. $da = $s-$e;
  6072. // We use radians
  6073. $s *= M_PI/180;
  6074. $e *= M_PI/180;
  6075. $da *= M_PI/180;
  6076. // Calculate a polygon approximation
  6077. $p[0] = $xc;
  6078. $p[1] = $yc;
  6079. // Heuristic on how many polygons we need to make the
  6080. // arc look good
  6081. $numsteps = round(8 * abs($da) * ($w+$h)*($w+$h)/1500);
  6082. if( $numsteps == 0 ) return;
  6083. if( $numsteps < 7 ) $numsteps=7;
  6084. $delta = abs($da)/$numsteps;
  6085. $pa=array();
  6086. $a = $s;
  6087. for($i=1; $i<=$numsteps; ++$i ) {
  6088. $p[2*$i] = round($xc + $w*cos($a));
  6089. $p[2*$i+1] = round($yc - $h*sin($a));
  6090. //$a = $s + $i*$delta;
  6091. $a -= $delta;
  6092. $pa[2*($i-1)] = $p[2*$i];
  6093. $pa[2*($i-1)+1] = $p[2*$i+1];
  6094. }
  6095. // Get the last point at the exact ending angle to avoid
  6096. // any rounding errors.
  6097. $p[2*$i] = round($xc + $w*cos($e));
  6098. $p[2*$i+1] = round($yc - $h*sin($e));
  6099. $pa[2*($i-1)] = $p[2*$i];
  6100. $pa[2*($i-1)+1] = $p[2*$i+1];
  6101. $i++;
  6102. $p[2*$i] = $xc;
  6103. $p[2*$i+1] = $yc;
  6104. if( $fillcolor != "" ) {
  6105. $this->PushColor($fillcolor);
  6106. imagefilledpolygon($this->img,$p,count($p)/2,$this->current_color);
  6107. $this->PopColor();
  6108. }
  6109. }
  6110. function FilledCakeSlice($cx,$cy,$w,$h,$s,$e) {
  6111. $this->CakeSlice($cx,$cy,$w,$h,$s,$e,$this->current_color_name);
  6112. }
  6113. function CakeSlice($xc,$yc,$w,$h,$s,$e,$fillcolor="",$arccolor="") {
  6114. $s = round($s); $e = round($e);
  6115. $w = round($w); $h = round($h);
  6116. $xc = round($xc); $yc = round($yc);
  6117. $this->PushColor($fillcolor);
  6118. $this->FilledArc($xc,$yc,2*$w,2*$h,$s,$e);
  6119. $this->PopColor();
  6120. if( $arccolor != "" ) {
  6121. $this->PushColor($arccolor);
  6122. // We add 2 pixels to make the Arc() better aligned with the filled arc.
  6123. if( $GLOBALS['gd2'] ) {
  6124. imagefilledarc($this->img,$xc,$yc,2*$w,2*$h,$s,$e,$this->current_color,IMG_ARC_NOFILL | IMG_ARC_EDGED ) ;
  6125. }
  6126. else {
  6127. $this->Arc($xc,$yc,2*$w+2,2*$h+2,$s,$e);
  6128. $xx = $w * cos(2*M_PI - $s*M_PI/180) + $xc;
  6129. $yy = $yc - $h * sin(2*M_PI - $s*M_PI/180);
  6130. $this->Line($xc,$yc,$xx,$yy);
  6131. $xx = $w * cos(2*M_PI - $e*M_PI/180) + $xc;
  6132. $yy = $yc - $h * sin(2*M_PI - $e*M_PI/180);
  6133. $this->Line($xc,$yc,$xx,$yy);
  6134. }
  6135. $this->PopColor();
  6136. }
  6137. }
  6138. function Ellipse($xc,$yc,$w,$h) {
  6139. $this->Arc($xc,$yc,$w,$h,0,360);
  6140. }
  6141. // Breseham circle gives visually better result then using GD
  6142. // built in arc(). It takes some more time but gives better
  6143. // accuracy.
  6144. function BresenhamCircle($xc,$yc,$r) {
  6145. $d = 3-2*$r;
  6146. $x = 0;
  6147. $y = $r;
  6148. while($x<=$y) {
  6149. $this->Point($xc+$x,$yc+$y);
  6150. $this->Point($xc+$x,$yc-$y);
  6151. $this->Point($xc-$x,$yc+$y);
  6152. $this->Point($xc-$x,$yc-$y);
  6153. $this->Point($xc+$y,$yc+$x);
  6154. $this->Point($xc+$y,$yc-$x);
  6155. $this->Point($xc-$y,$yc+$x);
  6156. $this->Point($xc-$y,$yc-$x);
  6157. if( $d<0 ) $d += 4*$x+6;
  6158. else {
  6159. $d += 4*($x-$y)+10;
  6160. --$y;
  6161. }
  6162. ++$x;
  6163. }
  6164. }
  6165. function Circle($xc,$yc,$r) {
  6166. if( USE_BRESENHAM )
  6167. $this->BresenhamCircle($xc,$yc,$r);
  6168. else {
  6169. /*
  6170. // Some experimental code snippet to see if we can get a decent
  6171. // result doing a trig-circle
  6172. // Create an approximated circle with 0.05 rad resolution
  6173. $end = 2*M_PI;
  6174. $l = $r/10;
  6175. if( $l < 3 ) $l=3;
  6176. $step_size = 2*M_PI/(2*$r*M_PI/$l);
  6177. $pts = array();
  6178. $pts[] = $r + $xc;
  6179. $pts[] = $yc;
  6180. for( $a=$step_size; $a <= $end; $a += $step_size ) {
  6181. $pts[] = round($xc + $r*cos($a));
  6182. $pts[] = round($yc - $r*sin($a));
  6183. }
  6184. imagepolygon($this->img,$pts,count($pts)/2,$this->current_color);
  6185. */
  6186. $this->Arc($xc,$yc,$r*2,$r*2,0,360);
  6187. // For some reason imageellipse() isn't in GD 2.0.1, PHP 4.1.1
  6188. //imageellipse($this->img,$xc,$yc,$r,$r,$this->current_color);
  6189. }
  6190. }
  6191. function FilledCircle($xc,$yc,$r) {
  6192. if( $GLOBALS['gd2'] ) {
  6193. imagefilledellipse($this->img,round($xc),round($yc),
  6194. 2*$r,2*$r,$this->current_color);
  6195. }
  6196. else {
  6197. for( $i=1; $i < 2*$r; $i += 2 ) {
  6198. // To avoid moire patterns we have to draw some
  6199. // 1 extra "skewed" filled circles
  6200. $this->Arc($xc,$yc,$i,$i,0,360);
  6201. $this->Arc($xc,$yc,$i+1,$i,0,360);
  6202. $this->Arc($xc,$yc,$i+1,$i+1,0,360);
  6203. }
  6204. }
  6205. }
  6206. // Linear Color InterPolation
  6207. function lip($f,$t,$p) {
  6208. $p = round($p,1);
  6209. $r = $f[0] + ($t[0]-$f[0])*$p;
  6210. $g = $f[1] + ($t[1]-$f[1])*$p;
  6211. $b = $f[2] + ($t[2]-$f[2])*$p;
  6212. return array($r,$g,$b);
  6213. }
  6214. // Anti-aliased line.
  6215. // Note that this is roughly 8 times slower then a normal line!
  6216. function WuLine($x1,$y1,$x2,$y2) {
  6217. // Get foreground line color
  6218. $lc = imagecolorsforindex($this->img,$this->current_color);
  6219. $lc = array($lc["red"],$lc["green"],$lc["blue"]);
  6220. $dx = $x2-$x1;
  6221. $dy = $y2-$y1;
  6222. if( abs($dx) > abs($dy) ) {
  6223. if( $dx<0 ) {
  6224. $dx = -$dx;$dy = -$dy;
  6225. $tmp=$x2;$x2=$x1;$x1=$tmp;
  6226. $tmp=$y2;$y2=$y1;$y1=$tmp;
  6227. }
  6228. $x=$x1<<16; $y=$y1<<16;
  6229. $yinc = ($dy*65535)/$dx;
  6230. while( ($x >> 16) < $x2 ) {
  6231. $bc = @imagecolorsforindex($this->img,imagecolorat($this->img,$x>>16,$y>>16));
  6232. if( $bc <= 0 ) {
  6233. JpGraphError::RaiseL(25100);//('Problem with color palette and your GD setup. Please disable anti-aliasing or use GD2 with true-color. If you have GD2 library installed please make sure that you have set the USE_GD2 constant to true and that truecolor is enabled.');
  6234. }
  6235. $bc=array($bc["red"],$bc["green"],$bc["blue"]);
  6236. $this->SetColor($this->lip($lc,$bc,($y & 0xFFFF)/65535));
  6237. imagesetpixel($this->img,$x>>16,$y>>16,$this->current_color);
  6238. $this->SetColor($this->lip($lc,$bc,(~$y & 0xFFFF)/65535));
  6239. imagesetpixel($this->img,$x>>16,($y>>16)+1,$this->current_color);
  6240. $x += 65536; $y += $yinc;
  6241. }
  6242. }
  6243. else {
  6244. if( $dy<0 ) {
  6245. $dx = -$dx;$dy = -$dy;
  6246. $tmp=$x2;$x2=$x1;$x1=$tmp;
  6247. $tmp=$y2;$y2=$y1;$y1=$tmp;
  6248. }
  6249. $x=$x1<<16; $y=$y1<<16;
  6250. $xinc = ($dx*65535)/$dy;
  6251. while( ($y >> 16) < $y2 ) {
  6252. $bc = @imagecolorsforindex($this->img,imagecolorat($this->img,$x>>16,$y>>16));
  6253. if( $bc <= 0 ) {
  6254. JpGraphError::RaiseL(25100);//('Problem with color palette and your GD setup. Please disable anti-aliasing or use GD2 with true-color. If you have GD2 library installed please make sure that you have set the USE_GD2 constant to true and truecolor is enabled.');
  6255. }
  6256. $bc=array($bc["red"],$bc["green"],$bc["blue"]);
  6257. $this->SetColor($this->lip($lc,$bc,($x & 0xFFFF)/65535));
  6258. imagesetpixel($this->img,$x>>16,$y>>16,$this->current_color);
  6259. $this->SetColor($this->lip($lc,$bc,(~$x & 0xFFFF)/65535));
  6260. imagesetpixel($this->img,($x>>16)+1,$y>>16,$this->current_color);
  6261. $y += 65536; $x += $xinc;
  6262. }
  6263. }
  6264. $this->SetColor($lc);
  6265. imagesetpixel($this->img,$x2,$y2,$this->current_color);
  6266. imagesetpixel($this->img,$x1,$y1,$this->current_color);
  6267. }
  6268. // Set line style dashed, dotted etc
  6269. function SetLineStyle($s) {
  6270. if( is_numeric($s) ) {
  6271. if( $s<1 || $s>4 )
  6272. JpGraphError::RaiseL(25101,$s);//(" Illegal numeric argument to SetLineStyle(): ($s)");
  6273. }
  6274. elseif( is_string($s) ) {
  6275. if( $s == "solid" ) $s=1;
  6276. elseif( $s == "dotted" ) $s=2;
  6277. elseif( $s == "dashed" ) $s=3;
  6278. elseif( $s == "longdashed" ) $s=4;
  6279. else JpGraphError::RaiseL(25102,$s);//(" Illegal string argument to SetLineStyle(): $s");
  6280. }
  6281. else JpGraphError::RaiseL(25103,$s);//(" Illegal argument to SetLineStyle $s");
  6282. $this->line_style=$s;
  6283. }
  6284. // Same as Line but take the line_style into account
  6285. function StyleLine($x1,$y1,$x2,$y2) {
  6286. switch( $this->line_style ) {
  6287. case 1:// Solid
  6288. $this->Line($x1,$y1,$x2,$y2);
  6289. break;
  6290. case 2: // Dotted
  6291. $this->DashedLine($x1,$y1,$x2,$y2,1,6);
  6292. break;
  6293. case 3: // Dashed
  6294. $this->DashedLine($x1,$y1,$x2,$y2,2,4);
  6295. break;
  6296. case 4: // Longdashes
  6297. $this->DashedLine($x1,$y1,$x2,$y2,8,6);
  6298. break;
  6299. default:
  6300. JpGraphError::RaiseL(25104,$this->line_style);//(" Unknown line style: $this->line_style ");
  6301. break;
  6302. }
  6303. }
  6304. function Line($x1,$y1,$x2,$y2) {
  6305. $x1 = round($x1);
  6306. $x2 = round($x2);
  6307. $y1 = round($y1);
  6308. $y2 = round($y2);
  6309. if( $this->line_weight==0 ) return;
  6310. if( $this->use_anti_aliasing ) {
  6311. $dx = $x2-$x1;
  6312. $dy = $y2-$y1;
  6313. // Vertical, Horizontal or 45 lines don't need anti-aliasing
  6314. if( $dx!=0 && $dy!=0 && $dx!=$dy ) {
  6315. $this->WuLine($x1,$y1,$x2,$y2);
  6316. return;
  6317. }
  6318. }
  6319. if( $this->line_weight==1 ) {
  6320. imageline($this->img,$x1,$y1,$x2,$y2,$this->current_color);
  6321. }
  6322. elseif( $x1==$x2 ) { // Special case for vertical lines
  6323. imageline($this->img,$x1,$y1,$x2,$y2,$this->current_color);
  6324. $w1=floor($this->line_weight/2);
  6325. $w2=floor(($this->line_weight-1)/2);
  6326. for($i=1; $i<=$w1; ++$i)
  6327. imageline($this->img,$x1+$i,$y1,$x2+$i,$y2,$this->current_color);
  6328. for($i=1; $i<=$w2; ++$i)
  6329. imageline($this->img,$x1-$i,$y1,$x2-$i,$y2,$this->current_color);
  6330. }
  6331. elseif( $y1==$y2 ) { // Special case for horizontal lines
  6332. imageline($this->img,$x1,$y1,$x2,$y2,$this->current_color);
  6333. $w1=floor($this->line_weight/2);
  6334. $w2=floor(($this->line_weight-1)/2);
  6335. for($i=1; $i<=$w1; ++$i)
  6336. imageline($this->img,$x1,$y1+$i,$x2,$y2+$i,$this->current_color);
  6337. for($i=1; $i<=$w2; ++$i)
  6338. imageline($this->img,$x1,$y1-$i,$x2,$y2-$i,$this->current_color);
  6339. }
  6340. else { // General case with a line at an angle
  6341. $a = atan2($y1-$y2,$x2-$x1);
  6342. // Now establish some offsets from the center. This gets a little
  6343. // bit involved since we are dealing with integer functions and we
  6344. // want the apperance to be as smooth as possible and never be thicker
  6345. // then the specified width.
  6346. // We do the trig stuff to make sure that the endpoints of the line
  6347. // are perpendicular to the line itself.
  6348. $dx=(sin($a)*$this->line_weight/2);
  6349. $dy=(cos($a)*$this->line_weight/2);
  6350. $pnts = array($x2+$dx,$y2+$dy,$x2-$dx,$y2-$dy,$x1-$dx,$y1-$dy,$x1+$dx,$y1+$dy);
  6351. imagefilledpolygon($this->img,$pnts,count($pnts)/2,$this->current_color);
  6352. }
  6353. $this->lastx=$x2; $this->lasty=$y2;
  6354. }
  6355. function Polygon($p,$closed=FALSE,$fast=FALSE) {
  6356. if( $this->line_weight==0 ) return;
  6357. $n=count($p);
  6358. $oldx = $p[0];
  6359. $oldy = $p[1];
  6360. if( $fast ) {
  6361. for( $i=2; $i < $n; $i+=2 ) {
  6362. imageline($this->img,$oldx,$oldy,$p[$i],$p[$i+1],$this->current_color);
  6363. $oldx = $p[$i];
  6364. $oldy = $p[$i+1];
  6365. }
  6366. if( $closed ) {
  6367. imageline($this->img,$p[$n*2-2],$p[$n*2-1],$p[0],$p[1],$this->current_color);
  6368. }
  6369. }
  6370. else {
  6371. for( $i=2; $i < $n; $i+=2 ) {
  6372. $this->StyleLine($oldx,$oldy,$p[$i],$p[$i+1]);
  6373. $oldx = $p[$i];
  6374. $oldy = $p[$i+1];
  6375. }
  6376. }
  6377. if( $closed )
  6378. $this->Line($oldx,$oldy,$p[0],$p[1]);
  6379. }
  6380. function FilledPolygon($pts) {
  6381. $n=count($pts);
  6382. if( $n == 0 ) {
  6383. JpGraphError::RaiseL(25105);//('NULL data specified for a filled polygon. Check that your data is not NULL.');
  6384. }
  6385. for($i=0; $i < $n; ++$i)
  6386. $pts[$i] = round($pts[$i]);
  6387. imagefilledpolygon($this->img,$pts,count($pts)/2,$this->current_color);
  6388. }
  6389. function Rectangle($xl,$yu,$xr,$yl) {
  6390. $this->Polygon(array($xl,$yu,$xr,$yu,$xr,$yl,$xl,$yl,$xl,$yu));
  6391. }
  6392. function FilledRectangle($xl,$yu,$xr,$yl) {
  6393. $this->FilledPolygon(array($xl,$yu,$xr,$yu,$xr,$yl,$xl,$yl));
  6394. }
  6395. function FilledRectangle2($xl,$yu,$xr,$yl,$color1,$color2,$style=1) {
  6396. // Fill a rectangle with lines of two colors
  6397. if( $style===1 ) {
  6398. // Horizontal stripe
  6399. if( $yl < $yu ) {
  6400. $t = $yl; $yl=$yu; $yu=$t;
  6401. }
  6402. for( $y=$yu; $y <= $yl; ++$y) {
  6403. $this->SetColor($color1);
  6404. $this->Line($xl,$y,$xr,$y);
  6405. ++$y;
  6406. $this->SetColor($color2);
  6407. $this->Line($xl,$y,$xr,$y);
  6408. }
  6409. }
  6410. else {
  6411. if( $xl < $xl ) {
  6412. $t = $xl; $xl=$xr; $xr=$t;
  6413. }
  6414. for( $x=$xl; $x <= $xr; ++$x) {
  6415. $this->SetColor($color1);
  6416. $this->Line($x,$yu,$x,$yl);
  6417. ++$x;
  6418. $this->SetColor($color2);
  6419. $this->Line($x,$yu,$x,$yl);
  6420. }
  6421. }
  6422. }
  6423. function ShadowRectangle($xl,$yu,$xr,$yl,$fcolor=false,$shadow_width=3,$shadow_color=array(102,102,102)) {
  6424. // This is complicated by the fact that we must also handle the case where
  6425. // the reactangle has no fill color
  6426. $this->PushColor($shadow_color);
  6427. $this->FilledRectangle($xr-$shadow_width,$yu+$shadow_width,$xr,$yl-$shadow_width-1);
  6428. $this->FilledRectangle($xl+$shadow_width,$yl-$shadow_width,$xr,$yl);
  6429. //$this->FilledRectangle($xl+$shadow_width,$yu+$shadow_width,$xr,$yl);
  6430. $this->PopColor();
  6431. if( $fcolor==false )
  6432. $this->Rectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1);
  6433. else {
  6434. $this->PushColor($fcolor);
  6435. $this->FilledRectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1);
  6436. $this->PopColor();
  6437. $this->Rectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1);
  6438. }
  6439. }
  6440. function FilledRoundedRectangle($xt,$yt,$xr,$yl,$r=5) {
  6441. if( $r==0 ) {
  6442. $this->FilledRectangle($xt,$yt,$xr,$yl);
  6443. return;
  6444. }
  6445. // To avoid overlapping fillings (which will look strange
  6446. // when alphablending is enabled) we have no choice but
  6447. // to fill the five distinct areas one by one.
  6448. // Center square
  6449. $this->FilledRectangle($xt+$r,$yt+$r,$xr-$r,$yl-$r);
  6450. // Top band
  6451. $this->FilledRectangle($xt+$r,$yt,$xr-$r,$yt+$r-1);
  6452. // Bottom band
  6453. $this->FilledRectangle($xt+$r,$yl-$r+1,$xr-$r,$yl);
  6454. // Left band
  6455. $this->FilledRectangle($xt,$yt+$r+1,$xt+$r-1,$yl-$r);
  6456. // Right band
  6457. $this->FilledRectangle($xr-$r+1,$yt+$r,$xr,$yl-$r);
  6458. // Topleft & Topright arc
  6459. $this->FilledArc($xt+$r,$yt+$r,$r*2,$r*2,180,270);
  6460. $this->FilledArc($xr-$r,$yt+$r,$r*2,$r*2,270,360);
  6461. // Bottomleft & Bottom right arc
  6462. $this->FilledArc($xt+$r,$yl-$r,$r*2,$r*2,90,180);
  6463. $this->FilledArc($xr-$r,$yl-$r,$r*2,$r*2,0,90);
  6464. }
  6465. function RoundedRectangle($xt,$yt,$xr,$yl,$r=5) {
  6466. if( $r==0 ) {
  6467. $this->Rectangle($xt,$yt,$xr,$yl);
  6468. return;
  6469. }
  6470. // Top & Bottom line
  6471. $this->Line($xt+$r,$yt,$xr-$r,$yt);
  6472. $this->Line($xt+$r,$yl,$xr-$r,$yl);
  6473. // Left & Right line
  6474. $this->Line($xt,$yt+$r,$xt,$yl-$r);
  6475. $this->Line($xr,$yt+$r,$xr,$yl-$r);
  6476. // Topleft & Topright arc
  6477. $this->Arc($xt+$r,$yt+$r,$r*2,$r*2,180,270);
  6478. $this->Arc($xr-$r,$yt+$r,$r*2,$r*2,270,360);
  6479. // Bottomleft & Bottomright arc
  6480. $this->Arc($xt+$r,$yl-$r,$r*2,$r*2,90,180);
  6481. $this->Arc($xr-$r,$yl-$r,$r*2,$r*2,0,90);
  6482. }
  6483. function FilledBevel($x1,$y1,$x2,$y2,$depth=2,$color1='white@0.4',$color2='darkgray@0.4') {
  6484. $this->FilledRectangle($x1,$y1,$x2,$y2);
  6485. $this->Bevel($x1,$y1,$x2,$y2,$depth,$color1,$color2);
  6486. }
  6487. function Bevel($x1,$y1,$x2,$y2,$depth=2,$color1='white@0.4',$color2='black@0.5') {
  6488. $this->PushColor($color1);
  6489. for( $i=0; $i < $depth; ++$i ) {
  6490. $this->Line($x1+$i,$y1+$i,$x1+$i,$y2-$i);
  6491. $this->Line($x1+$i,$y1+$i,$x2-$i,$y1+$i);
  6492. }
  6493. $this->PopColor();
  6494. $this->PushColor($color2);
  6495. for( $i=0; $i < $depth; ++$i ) {
  6496. $this->Line($x1+$i,$y2-$i,$x2-$i,$y2-$i);
  6497. $this->Line($x2-$i,$y1+$i,$x2-$i,$y2-$i-1);
  6498. }
  6499. $this->PopColor();
  6500. }
  6501. function StyleLineTo($x,$y) {
  6502. $this->StyleLine($this->lastx,$this->lasty,$x,$y);
  6503. $this->lastx=$x;
  6504. $this->lasty=$y;
  6505. }
  6506. function LineTo($x,$y) {
  6507. $this->Line($this->lastx,$this->lasty,$x,$y);
  6508. $this->lastx=$x;
  6509. $this->lasty=$y;
  6510. }
  6511. function Point($x,$y) {
  6512. imagesetpixel($this->img,round($x),round($y),$this->current_color);
  6513. }
  6514. function Fill($x,$y) {
  6515. imagefill($this->img,round($x),round($y),$this->current_color);
  6516. }
  6517. function FillToBorder($x,$y,$aBordColor) {
  6518. $bc = $this->rgb->allocate($aBordColor);
  6519. if( $bc == -1 ) {
  6520. JpGraphError::RaiseL(25106);//('Image::FillToBorder : Can not allocate more colors');
  6521. exit();
  6522. }
  6523. imagefilltoborder($this->img,round($x),round($y),$bc,$this->current_color);
  6524. }
  6525. function DashedLine($x1,$y1,$x2,$y2,$dash_length=1,$dash_space=4) {
  6526. $x1 = round($x1);
  6527. $x2 = round($x2);
  6528. $y1 = round($y1);
  6529. $y2 = round($y2);
  6530. // Code based on, but not identical to, work by Ariel Garza and James Pine
  6531. $line_length = ceil (sqrt(pow(($x2 - $x1),2) + pow(($y2 - $y1),2)) );
  6532. $dx = ($line_length) ? ($x2 - $x1) / $line_length : 0;
  6533. $dy = ($line_length) ? ($y2 - $y1) / $line_length : 0;
  6534. $lastx = $x1; $lasty = $y1;
  6535. $xmax = max($x1,$x2);
  6536. $xmin = min($x1,$x2);
  6537. $ymax = max($y1,$y2);
  6538. $ymin = min($y1,$y2);
  6539. for ($i = 0; $i < $line_length; $i += ($dash_length + $dash_space)) {
  6540. $x = ($dash_length * $dx) + $lastx;
  6541. $y = ($dash_length * $dy) + $lasty;
  6542. // The last section might overshoot so we must take a computational hit
  6543. // and check this.
  6544. if( $x>$xmax ) $x=$xmax;
  6545. if( $y>$ymax ) $y=$ymax;
  6546. if( $x<$xmin ) $x=$xmin;
  6547. if( $y<$ymin ) $y=$ymin;
  6548. $this->Line($lastx,$lasty,$x,$y);
  6549. $lastx = $x + ($dash_space * $dx);
  6550. $lasty = $y + ($dash_space * $dy);
  6551. }
  6552. }
  6553. function SetExpired($aFlg=true) {
  6554. $this->expired = $aFlg;
  6555. }
  6556. // Generate image header
  6557. function Headers() {
  6558. // In case we are running from the command line with the client version of
  6559. // PHP we can't send any headers.
  6560. $sapi = php_sapi_name();
  6561. if( $sapi == 'cli' )
  6562. return;
  6563. if( headers_sent($file,$lineno) ) {
  6564. $file=basename($file);
  6565. $t = new ErrMsgText();
  6566. $msg = $t->Get(10,$file,$lineno);
  6567. die($msg);
  6568. }
  6569. if ($this->expired) {
  6570. header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
  6571. header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT");
  6572. header("Cache-Control: no-cache, must-revalidate");
  6573. header("Pragma: no-cache");
  6574. }
  6575. header("Content-type: image/$this->img_format");
  6576. }
  6577. // Adjust image quality for formats that allow this
  6578. function SetQuality($q) {
  6579. $this->quality = $q;
  6580. }
  6581. // Stream image to browser or to file
  6582. function Stream($aFile="") {
  6583. $func="image".$this->img_format;
  6584. if( $this->img_format=="jpeg" && $this->quality != null ) {
  6585. $res = @$func($this->img,$aFile,$this->quality);
  6586. }
  6587. else {
  6588. if( $aFile != "" ) {
  6589. $res = @$func($this->img,$aFile);
  6590. if( !$res )
  6591. JpGraphError::RaiseL(25107,$aFile);//("Can't write to file '$aFile'. Check that the process running PHP has enough permission.");
  6592. }
  6593. else {
  6594. $res = @$func($this->img);
  6595. if( !$res )
  6596. JpGraphError::RaiseL(25108);//("Can't stream image. This is most likely due to a faulty PHP/GD setup. Try to recompile PHP and use the built-in GD library that comes with PHP.");
  6597. }
  6598. }
  6599. }
  6600. // Clear resource tide up by image
  6601. function Destroy() {
  6602. imagedestroy($this->img);
  6603. }
  6604. // Specify image format. Note depending on your installation
  6605. // of PHP not all formats may be supported.
  6606. function SetImgFormat($aFormat,$aQuality=75) {
  6607. $this->quality = $aQuality;
  6608. $aFormat = strtolower($aFormat);
  6609. $tst = true;
  6610. $supported = imagetypes();
  6611. if( $aFormat=="auto" ) {
  6612. if( $supported & IMG_PNG )
  6613. $this->img_format="png";
  6614. elseif( $supported & IMG_JPG )
  6615. $this->img_format="jpeg";
  6616. elseif( $supported & IMG_GIF )
  6617. $this->img_format="gif";
  6618. else
  6619. JpGraphError::RaiseL(25109);//("Your PHP (and GD-lib) installation does not appear to support any known graphic formats. You need to first make sure GD is compiled as a module to PHP. If you also want to use JPEG images you must get the JPEG library. Please see the PHP docs for details.");
  6620. return true;
  6621. }
  6622. else {
  6623. if( $aFormat=="jpeg" || $aFormat=="png" || $aFormat=="gif" ) {
  6624. if( $aFormat=="jpeg" && !($supported & IMG_JPG) )
  6625. $tst=false;
  6626. elseif( $aFormat=="png" && !($supported & IMG_PNG) )
  6627. $tst=false;
  6628. elseif( $aFormat=="gif" && !($supported & IMG_GIF) )
  6629. $tst=false;
  6630. else {
  6631. $this->img_format=$aFormat;
  6632. return true;
  6633. }
  6634. }
  6635. else
  6636. $tst=false;
  6637. if( !$tst )
  6638. JpGraphError::RaiseL(25110,$aFormat);//(" Your PHP installation does not support the chosen graphic format: $aFormat");
  6639. }
  6640. }
  6641. } // CLASS
  6642. //===================================================
  6643. // CLASS RotImage
  6644. // Description: Exactly as Image but draws the image at
  6645. // a specified angle around a specified rotation point.
  6646. //===================================================
  6647. class RotImage extends Image {
  6648. var $m=array();
  6649. var $a=0;
  6650. var $dx=0,$dy=0,$transx=0,$transy=0;
  6651. function RotImage($aWidth,$aHeight,$a=0,$aFormat=DEFAULT_GFORMAT) {
  6652. $this->Image($aWidth,$aHeight,$aFormat);
  6653. $this->dx=$this->left_margin+$this->plotwidth/2;
  6654. $this->dy=$this->top_margin+$this->plotheight/2;
  6655. $this->SetAngle($a);
  6656. }
  6657. function SetCenter($dx,$dy) {
  6658. $old_dx = $this->dx;
  6659. $old_dy = $this->dy;
  6660. $this->dx=$dx;
  6661. $this->dy=$dy;
  6662. $this->SetAngle($this->a);
  6663. return array($old_dx,$old_dy);
  6664. }
  6665. function SetTranslation($dx,$dy) {
  6666. $old = array($this->transx,$this->transy);
  6667. $this->transx = $dx;
  6668. $this->transy = $dy;
  6669. return $old;
  6670. }
  6671. function UpdateRotMatrice() {
  6672. $a = $this->a;
  6673. $a *= M_PI/180;
  6674. $sa=sin($a); $ca=cos($a);
  6675. // Create the rotation matrix
  6676. $this->m[0][0] = $ca;
  6677. $this->m[0][1] = -$sa;
  6678. $this->m[0][2] = $this->dx*(1-$ca) + $sa*$this->dy ;
  6679. $this->m[1][0] = $sa;
  6680. $this->m[1][1] = $ca;
  6681. $this->m[1][2] = $this->dy*(1-$ca) - $sa*$this->dx ;
  6682. }
  6683. function SetAngle($a) {
  6684. $tmp = $this->a;
  6685. $this->a = $a;
  6686. $this->UpdateRotMatrice();
  6687. return $tmp;
  6688. }
  6689. function Circle($xc,$yc,$r) {
  6690. // Circle get's rotated through the Arc() call
  6691. // made in the parent class
  6692. parent::Circle($xc,$yc,$r);
  6693. }
  6694. function FilledCircle($xc,$yc,$r) {
  6695. // If we use GD1 then Image::FilledCircle will use a
  6696. // call to Arc so it will get rotated through the Arc
  6697. // call.
  6698. if( $GLOBALS['gd2'] ) {
  6699. list($xc,$yc) = $this->Rotate($xc,$yc);
  6700. }
  6701. parent::FilledCircle($xc,$yc,$r);
  6702. }
  6703. function Arc($xc,$yc,$w,$h,$s,$e) {
  6704. list($xc,$yc) = $this->Rotate($xc,$yc);
  6705. $s += $this->a;
  6706. $e += $this->a;
  6707. parent::Arc($xc,$yc,$w,$h,$s,$e);
  6708. }
  6709. function FilledArc($xc,$yc,$w,$h,$s,$e) {
  6710. list($xc,$yc) = $this->Rotate($xc,$yc);
  6711. $s += $this->a;
  6712. $e += $this->a;
  6713. parent::FilledArc($xc,$yc,$w,$h,$s,$e);
  6714. }
  6715. function SetMargin($lm,$rm,$tm,$bm) {
  6716. parent::SetMargin($lm,$rm,$tm,$bm);
  6717. $this->dx=$this->left_margin+$this->plotwidth/2;
  6718. $this->dy=$this->top_margin+$this->plotheight/2;
  6719. $this->UpdateRotMatrice();
  6720. }
  6721. function Rotate($x,$y) {
  6722. // Optimization. Ignore rotation if Angle==0 || ANgle==360
  6723. if( $this->a == 0 || $this->a == 360 ) {
  6724. return array($x + $this->transx, $y + $this->transy );
  6725. }
  6726. else {
  6727. $x1=round($this->m[0][0]*$x + $this->m[0][1]*$y,1) + $this->m[0][2] + $this->transx;
  6728. $y1=round($this->m[1][0]*$x + $this->m[1][1]*$y,1) + $this->m[1][2] + $this->transy;
  6729. return array($x1,$y1);
  6730. }
  6731. }
  6732. function CopyMerge($fromImg,$toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$fromWidth=-1,$fromHeight=-1,$aMix=100) {
  6733. list($toX,$toY) = $this->Rotate($toX,$toY);
  6734. parent::CopyMerge($fromImg,$toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$fromWidth,$fromHeight,$aMix);
  6735. }
  6736. function ArrRotate($pnts) {
  6737. $n = count($pnts)-1;
  6738. for($i=0; $i < $n; $i+=2) {
  6739. list ($x,$y) = $this->Rotate($pnts[$i],$pnts[$i+1]);
  6740. $pnts[$i] = $x; $pnts[$i+1] = $y;
  6741. }
  6742. return $pnts;
  6743. }
  6744. function Line($x1,$y1,$x2,$y2) {
  6745. list($x1,$y1) = $this->Rotate($x1,$y1);
  6746. list($x2,$y2) = $this->Rotate($x2,$y2);
  6747. parent::Line($x1,$y1,$x2,$y2);
  6748. }
  6749. function Rectangle($x1,$y1,$x2,$y2) {
  6750. // Rectangle uses Line() so it will be rotated through that call
  6751. parent::Rectangle($x1,$y1,$x2,$y2);
  6752. }
  6753. function FilledRectangle($x1,$y1,$x2,$y2) {
  6754. if( $y1==$y2 || $x1==$x2 )
  6755. $this->Line($x1,$y1,$x2,$y2);
  6756. else
  6757. $this->FilledPolygon(array($x1,$y1,$x2,$y1,$x2,$y2,$x1,$y2));
  6758. }
  6759. function Polygon($pnts,$closed=FALSE,$fast=false) {
  6760. //Polygon uses Line() so it will be rotated through that call
  6761. parent::Polygon($pnts,$closed,$fast);
  6762. }
  6763. function FilledPolygon($pnts) {
  6764. parent::FilledPolygon($this->ArrRotate($pnts));
  6765. }
  6766. function Point($x,$y) {
  6767. list($xp,$yp) = $this->Rotate($x,$y);
  6768. parent::Point($xp,$yp);
  6769. }
  6770. function StrokeText($x,$y,$txt,$dir=0,$paragraph_align="left",$debug=false) {
  6771. list($xp,$yp) = $this->Rotate($x,$y);
  6772. return parent::StrokeText($xp,$yp,$txt,$dir,$paragraph_align,$debug);
  6773. }
  6774. }
  6775. //===================================================
  6776. // CLASS ImgStreamCache
  6777. // Description: Handle caching of graphs to files
  6778. //===================================================
  6779. class ImgStreamCache {
  6780. var $cache_dir;
  6781. var $img=null;
  6782. var $timeout=0; // Infinite timeout
  6783. //---------------
  6784. // CONSTRUCTOR
  6785. function ImgStreamCache(&$aImg, $aCacheDir=CACHE_DIR) {
  6786. $this->img = &$aImg;
  6787. $this->cache_dir = $aCacheDir;
  6788. }
  6789. //---------------
  6790. // PUBLIC METHODS
  6791. // Specify a timeout (in minutes) for the file. If the file is older then the
  6792. // timeout value it will be overwritten with a newer version.
  6793. // If timeout is set to 0 this is the same as infinite large timeout and if
  6794. // timeout is set to -1 this is the same as infinite small timeout
  6795. function SetTimeout($aTimeout) {
  6796. $this->timeout=$aTimeout;
  6797. }
  6798. // Output image to browser and also write it to the cache
  6799. function PutAndStream(&$aImage,$aCacheFileName,$aInline,$aStrokeFileName) {
  6800. // Some debugging code to brand the image with numbe of colors
  6801. // used
  6802. GLOBAL $gJpgBrandTiming;
  6803. if( $gJpgBrandTiming ) {
  6804. global $tim;
  6805. $t=$tim->Pop()/1000.0;
  6806. $c=$aImage->SetColor("black");
  6807. $t=sprintf(BRAND_TIME_FORMAT,round($t,3));
  6808. imagestring($this->img->img,2,5,$this->img->height-20,$t,$c);
  6809. }
  6810. // Check if we should stroke the image to an arbitrary file
  6811. if( _FORCE_IMGTOFILE ) {
  6812. $aStrokeFileName = _FORCE_IMGDIR.GenImgName();
  6813. }
  6814. if( $aStrokeFileName!="" ) {
  6815. if( $aStrokeFileName == "auto" )
  6816. $aStrokeFileName = GenImgName();
  6817. if( file_exists($aStrokeFileName) ) {
  6818. // Delete the old file
  6819. if( !@unlink($aStrokeFileName) )
  6820. JpGraphError::RaiseL(25111,$aStrokeFileName);//(" Can't delete cached image $aStrokeFileName. Permission problem?");
  6821. }
  6822. $aImage->Stream($aStrokeFileName);
  6823. return;
  6824. }
  6825. if( $aCacheFileName != "" && USE_CACHE) {
  6826. $aCacheFileName = $this->cache_dir . $aCacheFileName;
  6827. if( file_exists($aCacheFileName) ) {
  6828. if( !$aInline ) {
  6829. // If we are generating image off-line (just writing to the cache)
  6830. // and the file exists and is still valid (no timeout)
  6831. // then do nothing, just return.
  6832. $diff=time()-filemtime($aCacheFileName);
  6833. if( $diff < 0 )
  6834. JpGraphError::RaiseL(25112,$aCacheFileName);//(" Cached imagefile ($aCacheFileName) has file date in the future!!");
  6835. if( $this->timeout>0 && ($diff <= $this->timeout*60) )
  6836. return;
  6837. }
  6838. if( !@unlink($aCacheFileName) )
  6839. JpGraphError::RaiseL(25113,$aStrokeFileName);//(" Can't delete cached image $aStrokeFileName. Permission problem?");
  6840. $aImage->Stream($aCacheFileName);
  6841. }
  6842. else {
  6843. $this->MakeDirs(dirname($aCacheFileName));
  6844. if( !is_writeable(dirname($aCacheFileName)) ) {
  6845. JpGraphError::RaiseL(25114,$aCacheFileName);//('PHP has not enough permissions to write to the cache file '.$aCacheFileName.'. Please make sure that the user running PHP has write permission for this file if you wan to use the cache system with JpGraph.');
  6846. }
  6847. $aImage->Stream($aCacheFileName);
  6848. }
  6849. $res=true;
  6850. // Set group to specified
  6851. if( CACHE_FILE_GROUP != "" )
  6852. $res = @chgrp($aCacheFileName,CACHE_FILE_GROUP);
  6853. if( CACHE_FILE_MOD != "" )
  6854. $res = @chmod($aCacheFileName,CACHE_FILE_MOD);
  6855. if( !$res )
  6856. JpGraphError::RaiseL(25115,$aStrokeFileName);//(" Can't set permission for cached image $aStrokeFileName. Permission problem?");
  6857. $aImage->Destroy();
  6858. if( $aInline ) {
  6859. if ($fh = @fopen($aCacheFileName, "rb") ) {
  6860. $this->img->Headers();
  6861. fpassthru($fh);
  6862. return;
  6863. }
  6864. else
  6865. JpGraphError::RaiseL(25116,$aFile);//(" Cant open file from cache [$aFile]");
  6866. }
  6867. }
  6868. elseif( $aInline ) {
  6869. $this->img->Headers();
  6870. $aImage->Stream();
  6871. return;
  6872. }
  6873. }
  6874. // Check if a given image is in cache and in that case
  6875. // pass it directly on to web browser. Return false if the
  6876. // image file doesn't exist or exists but is to old
  6877. function GetAndStream($aCacheFileName) {
  6878. $aCacheFileName = $this->cache_dir.$aCacheFileName;
  6879. if ( USE_CACHE && file_exists($aCacheFileName) && $this->timeout>=0 ) {
  6880. $diff=time()-filemtime($aCacheFileName);
  6881. if( $this->timeout>0 && ($diff > $this->timeout*60) ) {
  6882. return false;
  6883. }
  6884. else {
  6885. if ($fh = @fopen($aCacheFileName, "rb")) {
  6886. $this->img->Headers();
  6887. fpassthru($fh);
  6888. return true;
  6889. }
  6890. else
  6891. JpGraphError::RaiseL(25117,$aCacheFileName);//(" Can't open cached image \"$aCacheFileName\" for reading.");
  6892. }
  6893. }
  6894. return false;
  6895. }
  6896. //---------------
  6897. // PRIVATE METHODS
  6898. // Create all necessary directories in a path
  6899. function MakeDirs($aFile) {
  6900. $dirs = array();
  6901. while ( !(file_exists($aFile)) ) {
  6902. $dirs[] = $aFile;
  6903. $aFile = dirname($aFile);
  6904. }
  6905. for ($i = sizeof($dirs)-1; $i>=0; $i--) {
  6906. if(! @mkdir($dirs[$i],0777) )
  6907. JpGraphError::RaiseL(25118,$aFile);//(" Can't create directory $aFile. Make sure PHP has write permission to this directory.");
  6908. // We also specify mode here after we have changed group.
  6909. // This is necessary if Apache user doesn't belong the
  6910. // default group and hence can't specify group permission
  6911. // in the previous mkdir() call
  6912. if( CACHE_FILE_GROUP != "" ) {
  6913. $res=true;
  6914. $res =@chgrp($dirs[$i],CACHE_FILE_GROUP);
  6915. $res &= @chmod($dirs[$i],0777);
  6916. if( !$res )
  6917. JpGraphError::RaiseL(25119,$aFile);//(" Can't set permissions for $aFile. Permission problems?");
  6918. }
  6919. }
  6920. return true;
  6921. }
  6922. } // CLASS Cache
  6923. //===================================================
  6924. // CLASS Legend
  6925. // Description: Responsible for drawing the box containing
  6926. // all the legend text for the graph
  6927. //===================================================
  6928. DEFINE('_DEFAULT_LPM_SIZE',8);
  6929. class Legend {
  6930. var $color=array(0,0,0); // Default fram color
  6931. var $fill_color=array(235,235,235); // Default fill color
  6932. var $shadow=true; // Shadow around legend "box"
  6933. var $shadow_color='gray';
  6934. var $txtcol=array();
  6935. var $mark_abs_hsize=_DEFAULT_LPM_SIZE, $mark_abs_vsize=_DEFAULT_LPM_SIZE;
  6936. var $xmargin=5,$ymargin=3,$shadow_width=2;
  6937. var $xlmargin=2, $ylmargin='';
  6938. var $xpos=0.05, $ypos=0.15, $xabspos=-1, $yabspos=-1;
  6939. var $halign="right", $valign="top";
  6940. var $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12;
  6941. var $font_color='black';
  6942. var $hide=false,$layout_n=1;
  6943. var $weight=1,$frameweight=1;
  6944. var $csimareas='';
  6945. var $reverse = false ;
  6946. //---------------
  6947. // CONSTRUCTOR
  6948. function Legend() {
  6949. // Empty
  6950. }
  6951. //---------------
  6952. // PUBLIC METHODS
  6953. function Hide($aHide=true) {
  6954. $this->hide=$aHide;
  6955. }
  6956. function SetHColMargin($aXMarg) {
  6957. $this->xmargin = $aXMarg;
  6958. }
  6959. function SetVColMargin($aSpacing) {
  6960. $this->ymargin = $aSpacing ;
  6961. }
  6962. function SetLeftMargin($aXMarg) {
  6963. $this->xlmargin = $aXMarg;
  6964. }
  6965. // Synonym
  6966. function SetLineSpacing($aSpacing) {
  6967. $this->ymargin = $aSpacing ;
  6968. }
  6969. function SetShadow($aShow='gray',$aWidth=2) {
  6970. if( is_string($aShow) ) {
  6971. $this->shadow_color = $aShow;
  6972. $this->shadow=true;
  6973. }
  6974. else
  6975. $this->shadow=$aShow;
  6976. $this->shadow_width=$aWidth;
  6977. }
  6978. function SetMarkAbsSize($aSize) {
  6979. $this->mark_abs_vsize = $aSize ;
  6980. $this->mark_abs_hsize = $aSize ;
  6981. }
  6982. function SetMarkAbsVSize($aSize) {
  6983. $this->mark_abs_vsize = $aSize ;
  6984. }
  6985. function SetMarkAbsHSize($aSize) {
  6986. $this->mark_abs_hsize = $aSize ;
  6987. }
  6988. function SetLineWeight($aWeight) {
  6989. $this->weight = $aWeight;
  6990. }
  6991. function SetFrameWeight($aWeight) {
  6992. $this->frameweight = $aWeight;
  6993. }
  6994. function SetLayout($aDirection=LEGEND_VERT) {
  6995. $this->layout_n = $aDirection==LEGEND_VERT ? 1 : 99 ;
  6996. }
  6997. function SetColumns($aCols) {
  6998. $this->layout_n = $aCols ;
  6999. }
  7000. function SetReverse($f=true) {
  7001. $this->reverse = $f ;
  7002. }
  7003. // Set color on frame around box
  7004. function SetColor($aFontColor,$aColor='black') {
  7005. $this->font_color=$aFontColor;
  7006. $this->color=$aColor;
  7007. }
  7008. function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
  7009. $this->font_family = $aFamily;
  7010. $this->font_style = $aStyle;
  7011. $this->font_size = $aSize;
  7012. }
  7013. function SetPos($aX,$aY,$aHAlign="right",$aVAlign="top") {
  7014. $this->Pos($aX,$aY,$aHAlign,$aVAlign);
  7015. }
  7016. function SetAbsPos($aX,$aY,$aHAlign="right",$aVAlign="top") {
  7017. $this->xabspos=$aX;
  7018. $this->yabspos=$aY;
  7019. $this->halign=$aHAlign;
  7020. $this->valign=$aVAlign;
  7021. }
  7022. function Pos($aX,$aY,$aHAlign="right",$aVAlign="top") {
  7023. if( !($aX<1 && $aY<1) )
  7024. JpGraphError::RaiseL(25120);//(" Position for legend must be given as percentage in range 0-1");
  7025. $this->xpos=$aX;
  7026. $this->ypos=$aY;
  7027. $this->halign=$aHAlign;
  7028. $this->valign=$aVAlign;
  7029. }
  7030. function SetFillColor($aColor) {
  7031. $this->fill_color=$aColor;
  7032. }
  7033. function Add($aTxt,$aColor,$aPlotmark="",$aLinestyle=0,$csimtarget="",$csimalt="") {
  7034. $this->txtcol[]=array($aTxt,$aColor,$aPlotmark,$aLinestyle,$csimtarget,$csimalt);
  7035. }
  7036. function GetCSIMAreas() {
  7037. return $this->csimareas;
  7038. }
  7039. function Stroke(&$aImg) {
  7040. // Constant
  7041. $fillBoxFrameWeight=1;
  7042. if( $this->hide ) return;
  7043. $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  7044. if( $this->reverse ) {
  7045. $this->txtcol = array_reverse($this->txtcol);
  7046. }
  7047. $n=count($this->txtcol);
  7048. if( $n == 0 ) return;
  7049. // Find out the max width and height of each column to be able
  7050. // to size the legend box.
  7051. $numcolumns = ($n > $this->layout_n ? $this->layout_n : $n);
  7052. for( $i=0; $i < $numcolumns; ++$i ) {
  7053. $colwidth[$i] = $aImg->GetTextWidth($this->txtcol[$i][0]) +
  7054. 2*$this->xmargin + 2*$this->mark_abs_hsize;
  7055. $colheight[$i] = 0;
  7056. }
  7057. // Find our maximum height in each row
  7058. $rows = 0 ; $rowheight[0] = 0;
  7059. for( $i=0; $i < $n; ++$i ) {
  7060. $h = max($this->mark_abs_vsize,$aImg->GetTextHeight($this->txtcol[$i][0]))+$this->ymargin;
  7061. if( $i % $numcolumns == 0 ) {
  7062. $rows++;
  7063. $rowheight[$rows-1] = 0;
  7064. }
  7065. $rowheight[$rows-1] = max($rowheight[$rows-1],$h);
  7066. }
  7067. $abs_height = 0;
  7068. for( $i=0; $i < $rows; ++$i ) {
  7069. $abs_height += $rowheight[$i] ;
  7070. }
  7071. // Make sure that the height is at least as high as mark size + ymargin
  7072. $abs_height = max($abs_height,$this->mark_abs_vsize);
  7073. // We add 3 extra pixels height to compensate for the difficult in
  7074. // calculating font height
  7075. $abs_height += $this->ymargin+3;
  7076. // Find out the maximum width in each column
  7077. for( $i=$numcolumns; $i < $n; ++$i ) {
  7078. $colwidth[$i % $numcolumns] = max(
  7079. $aImg->GetTextWidth($this->txtcol[$i][0])+2*$this->xmargin+2*$this->mark_abs_hsize,$colwidth[$i % $numcolumns]);
  7080. }
  7081. // Get the total width
  7082. $mtw = 0;
  7083. for( $i=0; $i < $numcolumns; ++$i ) {
  7084. $mtw += $colwidth[$i] ;
  7085. }
  7086. // Find out maximum width we need for legend box
  7087. $abs_width = $mtw+$this->xlmargin;
  7088. if( $this->xabspos === -1 && $this->yabspos === -1 ) {
  7089. $this->xabspos = $this->xpos*$aImg->width ;
  7090. $this->yabspos = $this->ypos*$aImg->height ;
  7091. }
  7092. // Positioning of the legend box
  7093. if( $this->halign=="left" )
  7094. $xp = $this->xabspos;
  7095. elseif( $this->halign=="center" )
  7096. $xp = $this->xabspos - $abs_width/2;
  7097. else
  7098. $xp = $aImg->width - $this->xabspos - $abs_width;
  7099. $yp=$this->yabspos;
  7100. if( $this->valign=="center" )
  7101. $yp-=$abs_height/2;
  7102. elseif( $this->valign=="bottom" )
  7103. $yp-=$abs_height;
  7104. // Stroke legend box
  7105. $aImg->SetColor($this->color);
  7106. $aImg->SetLineWeight($this->frameweight);
  7107. $aImg->SetLineStyle('solid');
  7108. if( $this->shadow )
  7109. $aImg->ShadowRectangle($xp,$yp,$xp+$abs_width+$this->shadow_width,
  7110. $yp+$abs_height+$this->shadow_width,
  7111. $this->fill_color,$this->shadow_width,$this->shadow_color);
  7112. else {
  7113. $aImg->SetColor($this->fill_color);
  7114. $aImg->FilledRectangle($xp,$yp,$xp+$abs_width,$yp+$abs_height);
  7115. $aImg->SetColor($this->color);
  7116. $aImg->Rectangle($xp,$yp,$xp+$abs_width,$yp+$abs_height);
  7117. }
  7118. // x1,y1 is the position for the legend mark
  7119. $x1=$xp+$this->mark_abs_hsize+$this->xlmargin;
  7120. $y1=$yp + $this->ymargin;
  7121. $f2 = round($aImg->GetTextHeight('X')/2);
  7122. $grad = new Gradient($aImg);
  7123. $patternFactory = null;
  7124. // Now stroke each legend in turn
  7125. // Each plot has added the following information to the legend
  7126. // p[0] = Legend text
  7127. // p[1] = Color,
  7128. // p[2] = For markers a reference to the PlotMark object
  7129. // p[3] = For lines the line style, for gradient the negative gradient style
  7130. // p[4] = CSIM target
  7131. // p[5] = CSIM Alt text
  7132. $i = 1 ; $row = 0;
  7133. foreach($this->txtcol as $p) {
  7134. // STROKE DEBUG BOX
  7135. if( _JPG_DEBUG ) {
  7136. $aImg->SetLineWeight(1);
  7137. $aImg->SetColor('red');
  7138. $aImg->SetLineStyle('solid');
  7139. $aImg->Rectangle($xp,$y1,$xp+$abs_width,$y1+$rowheight[$row]);
  7140. }
  7141. $aImg->SetLineWeight($this->weight);
  7142. $x1 = round($x1); $y1=round($y1);
  7143. if ( $p[2] != "" && $p[2]->GetType() > -1 ) {
  7144. // Make a plot mark legend
  7145. $aImg->SetColor($p[1]);
  7146. if( is_string($p[3]) || $p[3]>0 ) {
  7147. $aImg->SetLineStyle($p[3]);
  7148. $aImg->StyleLine($x1-$this->mark_abs_hsize,$y1+$f2,$x1+$this->mark_abs_hsize,$y1+$f2);
  7149. }
  7150. // Stroke a mark with the standard size
  7151. // (As long as it is not an image mark )
  7152. if( $p[2]->GetType() != MARK_IMG ) {
  7153. $p[2]->iFormatCallback = '';
  7154. // Since size for circles is specified as the radius
  7155. // this means that we must half the size to make the total
  7156. // width behave as the other marks
  7157. if( $p[2]->GetType() == MARK_FILLEDCIRCLE || $p[2]->GetType() == MARK_CIRCLE ) {
  7158. $p[2]->SetSize(min($this->mark_abs_vsize,$this->mark_abs_hsize)/2);
  7159. $p[2]->Stroke($aImg,$x1,$y1+$f2);
  7160. }
  7161. else {
  7162. $p[2]->SetSize(min($this->mark_abs_vsize,$this->mark_abs_hsize));
  7163. $p[2]->Stroke($aImg,$x1,$y1+$f2);
  7164. }
  7165. }
  7166. }
  7167. elseif ( $p[2] != "" && (is_string($p[3]) || $p[3]>0 ) ) {
  7168. // Draw a styled line
  7169. $aImg->SetColor($p[1]);
  7170. $aImg->SetLineStyle($p[3]);
  7171. $aImg->StyleLine($x1-1,$y1+$f2,$x1+$this->mark_abs_hsize,$y1+$f2);
  7172. $aImg->StyleLine($x1-1,$y1+$f2+1,$x1+$this->mark_abs_hsize,$y1+$f2+1);
  7173. }
  7174. else {
  7175. // Draw a colored box
  7176. $color = $p[1] ;
  7177. // We make boxes slightly larger to better show
  7178. $boxsize = min($this->mark_abs_vsize,$this->mark_abs_hsize) + 2 ;
  7179. $ym = round($y1 + $f2 - $boxsize/2);
  7180. // We either need to plot a gradient or a
  7181. // pattern. To differentiate we use a kludge.
  7182. // Patterns have a p[3] value of < -100
  7183. if( $p[3] < -100 ) {
  7184. // p[1][0] == iPattern, p[1][1] == iPatternColor, p[1][2] == iPatternDensity
  7185. if( $patternFactory == null ) {
  7186. $patternFactory = new RectPatternFactory();
  7187. }
  7188. $prect = $patternFactory->Create($p[1][0],$p[1][1],1);
  7189. $prect->SetBackground($p[1][3]);
  7190. $prect->SetDensity($p[1][2]+1);
  7191. $prect->SetPos(new Rectangle($x1,$ym,$boxsize,$boxsize));
  7192. $prect->Stroke($aImg);
  7193. $prect=null;
  7194. }
  7195. else {
  7196. if( is_array($color) && count($color)==2 ) {
  7197. // The client want a gradient color
  7198. $grad->FilledRectangle($x1,$ym,
  7199. $x1+$boxsize,$ym+$boxsize,
  7200. $color[0],$color[1],-$p[3]);
  7201. }
  7202. else {
  7203. $aImg->SetColor($p[1]);
  7204. $aImg->FilledRectangle($x1,$ym,$x1+$boxsize,$ym+$boxsize);
  7205. }
  7206. $aImg->SetColor($this->color);
  7207. $aImg->SetLineWeight($fillBoxFrameWeight);
  7208. $aImg->Rectangle($x1,$ym,$x1+$boxsize,$ym+$boxsize);
  7209. }
  7210. }
  7211. $aImg->SetColor($this->font_color);
  7212. $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  7213. $aImg->SetTextAlign("left","top");
  7214. $aImg->StrokeText(round($x1+$this->mark_abs_hsize+$this->xmargin),$y1,$p[0]);
  7215. // Add CSIM for Legend if defined
  7216. if( $p[4] != "" ) {
  7217. $xe = $x1 + $this->xmargin+$this->mark_abs_hsize+$aImg->GetTextWidth($p[0]);
  7218. $ye = $y1 + max($this->mark_abs_vsize,$aImg->GetTextHeight($p[0]));
  7219. $coords = "$x1,$y1,$xe,$y1,$xe,$ye,$x1,$ye";
  7220. if( ! empty($p[4]) ) {
  7221. $this->csimareas .= "<area shape=\"poly\" coords=\"$coords\" href=\"".$p[4]."\"";
  7222. if( !empty($p[5]) ) {
  7223. $tmp=sprintf($p[5],$p[0]);
  7224. $this->csimareas .= " title=\"$tmp\"";
  7225. }
  7226. $this->csimareas .= " alt=\"\" />\n";
  7227. }
  7228. }
  7229. if( $i >= $this->layout_n ) {
  7230. $x1 = $xp+$this->mark_abs_hsize+$this->xlmargin;
  7231. $y1 += $rowheight[$row++];
  7232. $i = 1;
  7233. }
  7234. else {
  7235. $x1 += $colwidth[($i-1) % $numcolumns] ;
  7236. ++$i;
  7237. }
  7238. }
  7239. }
  7240. } // Class
  7241. //===================================================
  7242. // CLASS DisplayValue
  7243. // Description: Used to print data values at data points
  7244. //===================================================
  7245. class DisplayValue {
  7246. var $show=false,$format="%.1f",$negformat="";
  7247. var $iFormCallback='';
  7248. var $angle=0;
  7249. var $ff=FF_FONT1,$fs=FS_NORMAL,$fsize=10;
  7250. var $color="navy",$negcolor="";
  7251. var $margin=5,$valign="",$halign="center";
  7252. var $iHideZero=false;
  7253. function Show($aFlag=true) {
  7254. $this->show=$aFlag;
  7255. }
  7256. function SetColor($aColor,$aNegcolor="") {
  7257. $this->color = $aColor;
  7258. $this->negcolor = $aNegcolor;
  7259. }
  7260. function SetFont($aFontFamily,$aFontStyle=FS_NORMAL,$aFontSize=10) {
  7261. $this->ff=$aFontFamily;
  7262. $this->fs=$aFontStyle;
  7263. $this->fsize=$aFontSize;
  7264. }
  7265. function SetMargin($aMargin) {
  7266. $this->margin = $aMargin;
  7267. }
  7268. function SetAngle($aAngle) {
  7269. $this->angle = $aAngle;
  7270. }
  7271. function SetAlign($aHAlign,$aVAlign='') {
  7272. $this->halign = $aHAlign;
  7273. $this->valign = $aVAlign;
  7274. }
  7275. function SetFormat($aFormat,$aNegFormat="") {
  7276. $this->format= $aFormat;
  7277. $this->negformat= $aNegFormat;
  7278. }
  7279. function SetFormatCallback($aFunc) {
  7280. $this->iFormCallback = $aFunc;
  7281. }
  7282. function HideZero($aFlag=true) {
  7283. $this->iHideZero=$aFlag;
  7284. }
  7285. function Stroke($img,$aVal,$x,$y) {
  7286. if( $this->show )
  7287. {
  7288. if( $this->negformat=="" ) $this->negformat=$this->format;
  7289. if( $this->negcolor=="" ) $this->negcolor=$this->color;
  7290. if( $aVal===NULL || (is_string($aVal) && ($aVal=="" || $aVal=="-" || $aVal=="x" ) ) )
  7291. return;
  7292. if( is_numeric($aVal) && $aVal==0 && $this->iHideZero ) {
  7293. return;
  7294. }
  7295. // Since the value is used in different cirumstances we need to check what
  7296. // kind of formatting we shall use. For example, to display values in a line
  7297. // graph we simply display the formatted value, but in the case where the user
  7298. // has already specified a text string we don't fo anything.
  7299. if( $this->iFormCallback != '' ) {
  7300. $f = $this->iFormCallback;
  7301. $sval = call_user_func($f,$aVal);
  7302. }
  7303. elseif( is_numeric($aVal) ) {
  7304. if( $aVal >= 0 )
  7305. $sval=sprintf($this->format,$aVal);
  7306. else
  7307. $sval=sprintf($this->negformat,$aVal);
  7308. }
  7309. else
  7310. $sval=$aVal;
  7311. $y = $y-sign($aVal)*$this->margin;
  7312. $txt = new Text($sval,$x,$y);
  7313. $txt->SetFont($this->ff,$this->fs,$this->fsize);
  7314. if( $this->valign == "" ) {
  7315. if( $aVal >= 0 )
  7316. $valign = "bottom";
  7317. else
  7318. $valign = "top";
  7319. }
  7320. else
  7321. $valign = $this->valign;
  7322. $txt->Align($this->halign,$valign);
  7323. $txt->SetOrientation($this->angle);
  7324. if( $aVal > 0 )
  7325. $txt->SetColor($this->color);
  7326. else
  7327. $txt->SetColor($this->negcolor);
  7328. $txt->Stroke($img);
  7329. }
  7330. }
  7331. }
  7332. //===================================================
  7333. // CLASS Plot
  7334. // Description: Abstract base class for all concrete plot classes
  7335. //===================================================
  7336. class Plot {
  7337. var $line_weight=1;
  7338. var $coords=array();
  7339. var $legend='',$hidelegend=false;
  7340. var $csimtargets=array(); // Array of targets for CSIM
  7341. var $csimareas=""; // Resultant CSIM area tags
  7342. var $csimalts=null; // ALT:s for corresponding target
  7343. var $color="black";
  7344. var $numpoints=0;
  7345. var $weight=1;
  7346. var $value;
  7347. var $center=false;
  7348. var $legendcsimtarget='';
  7349. var $legendcsimalt='';
  7350. //---------------
  7351. // CONSTRUCTOR
  7352. function Plot(&$aDatay,$aDatax=false) {
  7353. $this->numpoints = count($aDatay);
  7354. if( $this->numpoints==0 )
  7355. JpGraphError::RaiseL(25121);//("Empty input data array specified for plot. Must have at least one data point.");
  7356. $this->coords[0]=$aDatay;
  7357. if( is_array($aDatax) )
  7358. $this->coords[1]=$aDatax;
  7359. $this->value = new DisplayValue();
  7360. }
  7361. //---------------
  7362. // PUBLIC METHODS
  7363. // Stroke the plot
  7364. // "virtual" function which must be implemented by
  7365. // the subclasses
  7366. function Stroke(&$aImg,&$aXScale,&$aYScale) {
  7367. JpGraphError::RaiseL(25122);//("JpGraph: Stroke() must be implemented by concrete subclass to class Plot");
  7368. }
  7369. function HideLegend($f=true) {
  7370. $this->hidelegend = $f;
  7371. }
  7372. function DoLegend(&$graph) {
  7373. if( !$this->hidelegend )
  7374. $this->Legend($graph);
  7375. }
  7376. function StrokeDataValue($img,$aVal,$x,$y) {
  7377. $this->value->Stroke($img,$aVal,$x,$y);
  7378. }
  7379. // Set href targets for CSIM
  7380. function SetCSIMTargets($aTargets,$aAlts=null) {
  7381. $this->csimtargets=$aTargets;
  7382. $this->csimalts=$aAlts;
  7383. }
  7384. // Get all created areas
  7385. function GetCSIMareas() {
  7386. return $this->csimareas;
  7387. }
  7388. // "Virtual" function which gets called before any scale
  7389. // or axis are stroked used to do any plot specific adjustment
  7390. function PreStrokeAdjust(&$aGraph) {
  7391. if( substr($aGraph->axtype,0,4) == "text" && (isset($this->coords[1])) )
  7392. JpGraphError::RaiseL(25123);//("JpGraph: You can't use a text X-scale with specified X-coords. Use a \"int\" or \"lin\" scale instead.");
  7393. return true;
  7394. }
  7395. // Get minimum values in plot
  7396. function Min() {
  7397. if( isset($this->coords[1]) )
  7398. $x=$this->coords[1];
  7399. else
  7400. $x="";
  7401. if( $x != "" && count($x) > 0 )
  7402. $xm=min($x);
  7403. else
  7404. $xm=0;
  7405. $y=$this->coords[0];
  7406. $cnt = count($y);
  7407. if( $cnt > 0 ) {
  7408. /*
  7409. if( ! isset($y[0]) ) {
  7410. JpGraphError('The input data array must have consecutive values from position 0 and forward. The given y-array starts with empty values (NULL)');
  7411. }
  7412. */
  7413. //$ym = $y[0];
  7414. $i=0;
  7415. while( $i<$cnt && !is_numeric($ym=$y[$i]) )
  7416. $i++;
  7417. while( $i < $cnt) {
  7418. if( is_numeric($y[$i]) )
  7419. $ym=min($ym,$y[$i]);
  7420. ++$i;
  7421. }
  7422. }
  7423. else
  7424. $ym="";
  7425. return array($xm,$ym);
  7426. }
  7427. // Get maximum value in plot
  7428. function Max() {
  7429. if( isset($this->coords[1]) )
  7430. $x=$this->coords[1];
  7431. else
  7432. $x="";
  7433. if( $x!="" && count($x) > 0 )
  7434. $xm=max($x);
  7435. else {
  7436. $xm = $this->numpoints-1;
  7437. }
  7438. $y=$this->coords[0];
  7439. if( count($y) > 0 ) {
  7440. /*
  7441. if( !isset($y[0]) ) {
  7442. JpGraphError::Raise('The input data array must have consecutive values from position 0 and forward. The given y-array starts with empty values (NULL)');
  7443. //$y[0] = 0;
  7444. // Change in 1.5.1 Don't treat this as an error any more. Just silently convert to 0
  7445. // Change in 1.17 Treat his as an error again !! This is the right way to do !!
  7446. }
  7447. */
  7448. $cnt = count($y);
  7449. $i=0;
  7450. while( $i<$cnt && !is_numeric($ym=$y[$i]) )
  7451. $i++;
  7452. while( $i < $cnt ) {
  7453. if( is_numeric($y[$i]) )
  7454. $ym=max($ym,$y[$i]);
  7455. ++$i;
  7456. }
  7457. }
  7458. else
  7459. $ym="";
  7460. return array($xm,$ym);
  7461. }
  7462. function SetColor($aColor) {
  7463. $this->color=$aColor;
  7464. }
  7465. function SetLegend($aLegend,$aCSIM="",$aCSIMAlt="") {
  7466. $this->legend = $aLegend;
  7467. $this->legendcsimtarget = $aCSIM;
  7468. $this->legendcsimalt = $aCSIMAlt;
  7469. }
  7470. function SetWeight($aWeight) {
  7471. $this->weight=$aWeight;
  7472. }
  7473. function SetLineWeight($aWeight=1) {
  7474. $this->line_weight=$aWeight;
  7475. }
  7476. function SetCenter($aCenter=true) {
  7477. $this->center = $aCenter;
  7478. }
  7479. // This method gets called by Graph class to plot anything that should go
  7480. // into the margin after the margin color has been set.
  7481. function StrokeMargin(&$aImg) {
  7482. return true;
  7483. }
  7484. // Framework function the chance for each plot class to set a legend
  7485. function Legend(&$aGraph) {
  7486. if( $this->legend != "" )
  7487. $aGraph->legend->Add($this->legend,$this->color,"",0,$this->legendcsimtarget,$this->legendcsimalt);
  7488. }
  7489. } // Class
  7490. //===================================================
  7491. // CLASS PlotLine
  7492. // Description:
  7493. // Data container class to hold properties for a static
  7494. // line that is drawn directly in the plot area.
  7495. // Usefull to add static borders inside a plot to show
  7496. // for example set-values
  7497. //===================================================
  7498. class PlotLine {
  7499. var $weight=1;
  7500. var $color="black";
  7501. var $direction=-1;
  7502. var $scaleposition;
  7503. //---------------
  7504. // CONSTRUCTOR
  7505. function PlotLine($aDir=HORIZONTAL,$aPos=0,$aColor="black",$aWeight=1) {
  7506. $this->direction = $aDir;
  7507. $this->color=$aColor;
  7508. $this->weight=$aWeight;
  7509. $this->scaleposition=$aPos;
  7510. }
  7511. //---------------
  7512. // PUBLIC METHODS
  7513. function SetPosition($aScalePosition) {
  7514. $this->scaleposition=$aScalePosition;
  7515. }
  7516. function SetDirection($aDir) {
  7517. $this->direction = $aDir;
  7518. }
  7519. function SetColor($aColor) {
  7520. $this->color=$aColor;
  7521. }
  7522. function SetWeight($aWeight) {
  7523. $this->weight=$aWeight;
  7524. }
  7525. function PreStrokeAdjust($aGraph) {
  7526. // Nothing to do
  7527. }
  7528. function Stroke(&$aImg,&$aXScale,&$aYScale) {
  7529. $aImg->SetColor($this->color);
  7530. $aImg->SetLineWeight($this->weight);
  7531. if( $this->direction == VERTICAL ) {
  7532. $ymin_abs=$aYScale->Translate($aYScale->GetMinVal());
  7533. $ymax_abs=$aYScale->Translate($aYScale->GetMaxVal());
  7534. $xpos_abs=$aXScale->Translate($this->scaleposition);
  7535. $aImg->Line($xpos_abs, $ymin_abs, $xpos_abs, $ymax_abs);
  7536. }
  7537. elseif( $this->direction == HORIZONTAL ) {
  7538. $xmin_abs=$aXScale->Translate($aXScale->GetMinVal());
  7539. $xmax_abs=$aXScale->Translate($aXScale->GetMaxVal());
  7540. $ypos_abs=$aYScale->Translate($this->scaleposition);
  7541. $aImg->Line($xmin_abs, $ypos_abs, $xmax_abs, $ypos_abs);
  7542. }
  7543. else
  7544. JpGraphError::RaiseL(25125);//(" Illegal direction for static line");
  7545. }
  7546. }
  7547. // <EOF>
  7548. ?>