jpgraph.php
上传用户:gzy2002
上传日期:2010-02-11
资源大小:1785k
文件大小:248k
源码类别:

电子政务应用

开发平台:

Java

  1. <?php 
  2. //=======================================================================
  3. // File: JPGRAPH.PHP
  4. // Description: PHP4 Graph Plotting library. Base module.
  5. // Created:  2001-01-08
  6. // Author: Johan Persson
  7. // Ver: $Id: jpgraph.php,v 1.25 2004/02/11 20:32:13 deskpro Exp $
  8. //
  9. // License: This code is released under QPL 1.0 
  10. // Copyright (C) 2001 - 2004 Johan Persson 
  11. //========================================================================
  12. //------------------------------------------------------------------------
  13. // Directories for cache and font directory.
  14. // Leave them undefined to use default values. 
  15. // 
  16. // Default values used if these defines are left commented out are:
  17. // 
  18. // UNIX: 
  19. //   CACHE_DIR = /tmp/jpgraph_cache/
  20. //   TTF_DIR   = /usr/X11R6/lib/X11/fonts/truetype/
  21. //
  22. // WINDOWS:
  23. //   CACHE_DIR = $SERVER_TEMP/jpgraph_cache/
  24. //   TTF_DIR   = $SERVER_SYSTEMROOT/fonts/
  25. //    
  26. //
  27. //------------------------------------------------------------------------
  28. // The full absolute name of the directory to be used to store the
  29. // cached image files. This directory will not be used if the USE_CACHE
  30. // define (further down) is false. If you enable the cache please note that
  31. // this directory MUST be readable and writable for the process running PHP. 
  32. // Must end with '/'
  33. $tmpdir = INCLUDE_PATH . '../admin/graphs';
  34. DEFINE("CACHE_DIR",$tmpdir);
  35. // Directory for jpGraph TTF fonts. Must end with '/'
  36. // DEFINE("TTF_DIR","/usr/X11R6/lib/X11/fonts/truetype/");
  37. //-------------------------------------------------------------------------
  38. // Cache directory specification for use with CSIM graphs that are
  39. // using the cache.
  40. // The directory must be the filesysystem name as seen by PHP
  41. // and the 'http' version must be the same directory but as 
  42. // seen by the HTTP server relative to the 'htdocs' ddirectory. 
  43. // If a relative path is specified it is taken to be relative from where
  44. // the image script is executed.
  45. // Note: The default setting is to create a subdirectory in the 
  46. // directory from where the image script is executed and store all files
  47. // there. As ususal this directory must be writeable by the PHP process.
  48. DEFINE("CSIMCACHE_DIR","csimcache/"); 
  49. DEFINE("CSIMCACHE_HTTP_DIR","csimcache/");
  50. //------------------------------------------------------------------------
  51. // Various JpGraph Settings. Adjust accordingly to your
  52. // preferences. Note that cache functionality is turned off by
  53. // default (Enable by setting USE_CACHE to true)
  54. //------------------------------------------------------------------------
  55. // Deafult graphic format set to "auto" which will automatically
  56. // choose the best available format in the order png,gif,jpg
  57. // (The supported format depends on what your PHP installation supports)
  58. DEFINE("DEFAULT_GFORMAT","auto");
  59. // Should the image be a truecolor image? 
  60. // Note 1: Has only effect with GD 2.0.1 and above.
  61. // Note 2: GD 2.0.1 + PHP 4.0.6 on Win32 crashes when trying to use 
  62. // trucolor. Truecolor support is to be considered alpha since GD 2.x
  63. // is still not considered stable (especially on Win32). 
  64. // Note 3: MUST be enabled to get background images working with GD2
  65. // Note 4: If enabled then truetype fonts will look very ugly with GD 2.0.1
  66. // => You can't have both background images and truetype fonts in the same
  67. // image until these bugs has been fixed in GD 2.01. There is a patch
  68. // available for GD 2.0.1 though. See the README file.
  69. DEFINE('USE_TRUECOLOR',true);
  70. // Specify what version of the GD library is installed.
  71. // If this is set to 'auto' the version will be automatically 
  72. // determined.
  73. // However since determining the library takes ~1ms you can also 
  74. // manually specify the version if you know what version you have. 
  75. // This means that you should 
  76. // set this define to true if you have GD 2.x installed to save 1ms. 
  77. DEFINE("USE_LIBRARY_GD2",'auto');
  78. // Should the cache be used at all? By setting this to false no
  79. // files will be generated in the cache directory.  
  80. // The difference from READ_CACHE being that setting READ_CACHE to
  81. // false will still create the image in the cache directory
  82. // just not use it. By setting USE_CACHE=false no files will even
  83. // be generated in the cache directory.
  84. DEFINE("USE_CACHE",false);
  85. // Should we try to find an image in the cache before generating it? 
  86. // Set this define to false to bypass the reading of the cache and always
  87. // regenerate the image. Note that even if reading the cache is 
  88. // disabled the cached will still be updated with the newly generated
  89. // image. Set also "USE_CACHE" below.
  90. DEFINE("READ_CACHE",true);
  91. // Determine if the error handler should be image based or purely
  92. // text based. Image based makes it easier since the script will
  93. // always return an image even in case of errors.
  94. if (!defined('NO_GRAPHIC_HANDLER')) {
  95. DEFINE("USE_IMAGE_ERROR_HANDLER",true);
  96. } else {
  97. DEFINE("USE_IMAGE_ERROR_HANDLER",false);
  98. }
  99. // If the color palette is full should JpGraph try to allocate
  100. // the closest match? If you plan on using background images or
  101. // gradient fills it might be a good idea to enable this.
  102. // If not you will otherwise get an error saying that the color palette is 
  103. // exhausted. The drawback of using approximations is that the colors 
  104. // might not be exactly what you specified. 
  105. // Note1: This does only apply to paletted images, not truecolor 
  106. // images since they don't have the limitations of maximum number
  107. // of colors.
  108. DEFINE("USE_APPROX_COLORS",true);
  109. // Special unicode cyrillic language support
  110. DEFINE("LANGUAGE_CYRILLIC",false);
  111. // If you are setting this config to true the conversion
  112. // will assume that the input text is windows 1251, if
  113. // false it will assume koi8-r
  114. DEFINE("CYRILLIC_FROM_WINDOWS",false);
  115. // Should usage of deprecated functions and parameters give a fatal error?
  116. // (Useful to check if code is future proof.)
  117. DEFINE("ERR_DEPRECATED",true);
  118. // Should the time taken to generate each picture be branded to the lower
  119. // left in corner in each generated image? Useful for performace measurements
  120. // generating graphs
  121. DEFINE("BRAND_TIMING",false);
  122. // What format should be used for the timing string?
  123. DEFINE("BRAND_TIME_FORMAT","(%01.3fs)");
  124. //------------------------------------------------------------------------
  125. // The following constants should rarely have to be changed !
  126. //------------------------------------------------------------------------
  127. // What group should the cached file belong to
  128. // (Set to "" will give the default group for the "PHP-user")
  129. // Please note that the Apache user must be a member of the
  130. // specified group since otherwise it is impossible for Apache
  131. // to set the specified group.
  132. DEFINE("CACHE_FILE_GROUP","wwwadmin");
  133. // What permissions should the cached file have
  134. // (Set to "" will give the default persmissions for the "PHP-user")
  135. DEFINE("CACHE_FILE_MOD",0664);
  136. // Decide if we should use the bresenham circle algorithm or the
  137. // built in Arc(). Bresenham gives better visual apperance of circles 
  138. // but is more CPU intensive and slower then the built in Arc() function
  139. // in GD. Turned off by default for speed
  140. DEFINE("USE_BRESENHAM",false);
  141. // Special file name to indicate that we only want to calc
  142. // the image map in the call to Graph::Stroke() used
  143. // internally from the GetHTMLCSIM() method.
  144. DEFINE("_CSIM_SPECIALFILE","_csim_special_");
  145. // HTTP GET argument that is used with image map
  146. // to indicate to the script to just generate the image
  147. // and not the full CSIM HTML page.
  148. DEFINE("_CSIM_DISPLAY","_jpg_csimd");
  149. // Special filename for Graph::Stroke(). If this filename is given
  150. // then the image will NOT be streamed to browser of file. Instead the
  151. // Stroke call will return the handler for the created GD image.
  152. DEFINE("_IMG_HANDLER","__handle");
  153. // DON'T SET THIS FLAG YORSELF THIS IS ONLY FOR INTERNAL TESTING
  154. // PURPOSES. ENABLING THIS FLAG WILL MAKE SOME OF YOUR SCRIPT 
  155. // STOP WORKING
  156. // Enable some extra debug information for CSIM etc to be shown. 
  157. DEFINE("JPG_DEBUG",false);
  158. // Version info
  159. DEFINE('JPG_VERSION','1.12');
  160. //------------------------------------------------------------------------
  161. // Automatic settings of path for cache and font directory
  162. // if they have not been previously specified
  163. //------------------------------------------------------------------------
  164. if (!defined('CACHE_DIR')) {
  165.     if ( strstr( PHP_OS, 'WIN') ) {
  166.         if( empty($_SERVER['TEMP']) ) {
  167.     die('JpGraph Error: No path specified for CACHE_DIR. Please specify a path for that DEFINE in jpgraph.php');
  168.         }
  169. else {
  170.    DEFINE('CACHE_DIR', $_SERVER['TEMP'] . '/');
  171.         }
  172.     } else {
  173. DEFINE('CACHE_DIR','/tmp/jpgraph_cache/');
  174.     }
  175. }
  176. if (!defined('TTF_DIR')) {
  177.     if (strstr( PHP_OS, 'WIN') ) {
  178.         if( empty($_SERVER['SystemRoot']) ) {
  179.     die('JpGraph Error: No path specified for TTF_DIR. Please specify a path for that DEFINE in jpgraph.php');
  180.         }
  181. else {
  182.   DEFINE('TTF_DIR', $_SERVER['SystemRoot'] . '/fonts/');
  183.         }
  184.     } else {
  185. DEFINE('TTF_DIR','/usr/X11R6/lib/X11/fonts/truetype/');
  186.     }
  187. }
  188. //------------------------------------------------------------------
  189. // Constants which are used as parameters for the method calls
  190. //------------------------------------------------------------------
  191. // TTF Font families
  192. DEFINE("FF_COURIER",10);
  193. DEFINE("FF_VERDANA",11);
  194. DEFINE("FF_TIMES",12);
  195. DEFINE("FF_COMIC",14);
  196. DEFINE("FF_ARIAL",15);
  197. DEFINE("FF_GEORGIA",16);
  198. DEFINE("FF_TREBUCHE",17);
  199. // Chinese font
  200. DEFINE("FF_SIMSUN",18);
  201. // Gnome Vera font
  202. DEFINE("FF_VERA",19);
  203. DEFINE("FF_VERAMONO",20);
  204. DEFINE("FF_VERASERIF",21);
  205. // Older deprecated fonts 
  206. DEFINE("FF_BOOK",91);    // Deprecated fonts from 1.9
  207. DEFINE("FF_HANDWRT",92); // Deprecated fonts from 1.9
  208. // TTF Font styles
  209. DEFINE("FS_NORMAL",9001);
  210. DEFINE("FS_BOLD",9002);
  211. DEFINE("FS_ITALIC",9003);
  212. DEFINE("FS_BOLDIT",9004);
  213. DEFINE("FS_BOLDITALIC",9004);
  214. //Definitions for internal font, new style
  215. DEFINE("FF_FONT0",1);
  216. DEFINE("FF_FONT1",2);
  217. DEFINE("FF_FONT2",4);
  218. //Definitions for internal font, old style
  219. // (Only defined here to be able to generate an error mesage
  220. // when used)
  221. DEFINE("FONT0",99); // Deprecated from 1.2
  222. DEFINE("FONT1",98); // Deprecated from 1.2
  223. DEFINE("FONT1_BOLD",97); // Deprecated from 1.2
  224. DEFINE("FONT2",96); // Deprecated from 1.2
  225. DEFINE("FONT2_BOLD",95);  // Deprecated from 1.2
  226. // Tick density
  227. DEFINE("TICKD_DENSE",1);
  228. DEFINE("TICKD_NORMAL",2);
  229. DEFINE("TICKD_SPARSE",3);
  230. DEFINE("TICKD_VERYSPARSE",4);
  231. // Side for ticks and labels. 
  232. DEFINE("SIDE_LEFT",-1);
  233. DEFINE("SIDE_RIGHT",1);
  234. DEFINE("SIDE_DOWN",-1);
  235. DEFINE("SIDE_BOTTOM",-1);
  236. DEFINE("SIDE_UP",1);
  237. DEFINE("SIDE_TOP",1);
  238. // Legend type stacked vertical or horizontal
  239. DEFINE("LEGEND_VERT",0);
  240. DEFINE("LEGEND_HOR",1);
  241. // Mark types for plot marks
  242. DEFINE("MARK_SQUARE",1);
  243. DEFINE("MARK_UTRIANGLE",2);
  244. DEFINE("MARK_DTRIANGLE",3);
  245. DEFINE("MARK_DIAMOND",4);
  246. DEFINE("MARK_CIRCLE",5);
  247. DEFINE("MARK_FILLEDCIRCLE",6);
  248. DEFINE("MARK_CROSS",7);
  249. DEFINE("MARK_STAR",8);
  250. DEFINE("MARK_X",9);
  251. DEFINE("MARK_LEFTTRIANGLE",10);
  252. DEFINE("MARK_RIGHTTRIANGLE",11);
  253. DEFINE("MARK_FLASH",12);
  254. DEFINE("MARK_IMG",13);
  255. // Builtin images
  256. DEFINE("MARK_IMG_PUSHPIN",50);
  257. DEFINE("MARK_IMG_SPUSHPIN",50);
  258. DEFINE("MARK_IMG_LPUSHPIN",51);
  259. DEFINE("MARK_IMG_DIAMOND",52);
  260. DEFINE("MARK_IMG_SQUARE",53);
  261. DEFINE("MARK_IMG_STAR",54);
  262. DEFINE("MARK_IMG_BALL",55);
  263. DEFINE("MARK_IMG_SBALL",55);
  264. DEFINE("MARK_IMG_MBALL",56);
  265. DEFINE("MARK_IMG_LBALL",57);
  266. DEFINE("MARK_IMG_BEVEL",58);
  267. // Styles for gradient color fill
  268. DEFINE("GRAD_VER",1);
  269. DEFINE("GRAD_VERT",1);
  270. DEFINE("GRAD_HOR",2);
  271. DEFINE("GRAD_MIDHOR",3);
  272. DEFINE("GRAD_MIDVER",4);
  273. DEFINE("GRAD_CENTER",5);
  274. DEFINE("GRAD_WIDE_MIDVER",6);
  275. DEFINE("GRAD_WIDE_MIDHOR",7);
  276. DEFINE("GRAD_LEFT_REFLECTION",8);
  277. DEFINE("GRAD_RIGHT_REFLECTION",9);
  278. // Inline defines
  279. DEFINE("INLINE_YES",1);
  280. DEFINE("INLINE_NO",0);
  281. // Format for background images
  282. DEFINE("BGIMG_FILLPLOT",1);
  283. DEFINE("BGIMG_FILLFRAME",2);
  284. DEFINE("BGIMG_COPY",3);
  285. DEFINE("BGIMG_CENTER",4);
  286. // Depth of objects
  287. DEFINE("DEPTH_BACK",0);
  288. DEFINE("DEPTH_FRONT",1);
  289. // Direction
  290. DEFINE("VERTICAL",1);
  291. DEFINE("HORIZONTAL",0);
  292. // Constants for types of static bands in plot area
  293. DEFINE("BAND_RDIAG",1); // Right diagonal lines
  294. DEFINE("BAND_LDIAG",2); // Left diagonal lines
  295. DEFINE("BAND_SOLID",3); // Solid one color
  296. DEFINE("BAND_VLINE",4); // Vertical lines
  297. DEFINE("BAND_HLINE",5);  // Horizontal lines
  298. DEFINE("BAND_3DPLANE",6);  // "3D" Plane
  299. DEFINE("BAND_HVCROSS",7);  // Vertical/Hor crosses
  300. DEFINE("BAND_DIAGCROSS",8); // Diagonal crosses
  301. // Axis styles for scientific style axis
  302. DEFINE('AXSTYLE_SIMPLE',1);
  303. DEFINE('AXSTYLE_BOXIN',2);
  304. DEFINE('AXSTYLE_BOXOUT',3);
  305. DEFINE('AXSTYLE_YBOXIN',4);
  306. DEFINE('AXSTYLE_YBOXOUT',5);
  307. // Style for title backgrounds
  308. DEFINE('TITLEBKG_STYLE1',1);
  309. DEFINE('TITLEBKG_STYLE2',2);
  310. DEFINE('TITLEBKG_STYLE3',3);
  311. DEFINE('TITLEBKG_FRAME_NONE',0);
  312. DEFINE('TITLEBKG_FRAME_FULL',1);
  313. DEFINE('TITLEBKG_FRAME_BOTTOM',2);
  314. DEFINE('TITLEBKG_FRAME_BEVEL',3);
  315. DEFINE('TITLEBKG_FILLSTYLE_HSTRIPED',1);
  316. DEFINE('TITLEBKG_FILLSTYLE_VSTRIPED',2);
  317. DEFINE('TITLEBKG_FILLSTYLE_SOLID',3);
  318. // Width of tab titles
  319. DEFINE('TABTITLE_WIDTHFIT',0);
  320. DEFINE('TABTITLE_WIDTHFULL',-1);
  321. //
  322. // Get hold of gradient class (In Version 2.x)
  323. // A client of the library has to manually include this
  324. //
  325. include INCLUDE_PATH . "graph/jpgraph_gradient.php";
  326. //
  327. // First of all set up a default error handler
  328. //
  329. //=============================================================
  330. // The default trivial text error handler.
  331. //=============================================================
  332. class JpGraphErrObject {
  333.     function JpGraphErrObject() {
  334. // Empty. Reserved for future use
  335.     }
  336.     // If aHalt is true then execution can't continue. Typical used for
  337.     // fatal errors
  338.     function Raise($aMsg,$aHalt=true) {
  339. $aMsg = "<b>JpGraph Error:</b> ".$aMsg;
  340. if( $aHalt )
  341.     die($aMsg);
  342. else 
  343.     echo $aMsg."<p>";
  344.     }
  345. }
  346. //==============================================================
  347. // An image based error handler
  348. //==============================================================
  349. class JpGraphErrObjectImg {
  350.     function Raise($aMsg,$aHalt=true) {
  351. $img_iconerror = 
  352.     'iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAMAAAC7IEhfAAAAaV'.
  353.     'BMVEX//////2Xy8mLl5V/Z2VvMzFi/v1WyslKlpU+ZmUyMjEh/'.
  354.     'f0VyckJlZT9YWDxMTDjAwMDy8sLl5bnY2K/MzKW/v5yyspKlpY'.
  355.     'iYmH+MjHY/PzV/f2xycmJlZVlZWU9MTEXY2Ms/PzwyMjLFTjea'.
  356.     'AAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACx'.
  357.     'IAAAsSAdLdfvwAAAAHdElNRQfTBgISOCqusfs5AAABLUlEQVR4'.
  358.     '2tWV3XKCMBBGWfkranCIVClKLd/7P2Q3QsgCxjDTq+6FE2cPH+'.
  359.     'xJ0Ogn2lQbsT+Wrs+buAZAV4W5T6Bs0YXBBwpKgEuIu+JERAX6'.
  360.     'wM2rHjmDdEITmsQEEmWADgZm6rAjhXsoMGY9B/NZBwJzBvn+e3'.
  361.     'wHntCAJdGu9SviwIwoZVDxPB9+Rc0TSEbQr0j3SA1gwdSn6Db0'.
  362.     '6Tm1KfV6yzWGQO7zdpvyKLKBDmRFjzeB3LYgK7r6A/noDAfjtS'.
  363.     'IXaIzbJSv6WgUebTMV4EoRB8a2mQiQjgtF91HdKDKZ1gtFtQjk'.
  364.     'YcWaR5OKOhkYt+ZsTFdJRfPAApOpQYJTNHvCRSJR6SJngQadfc'.
  365.     'vd69OLMddVOPCGVnmrFD8bVYd3JXfxXPtLR/+mtv59/ALWiiMx'.
  366.     'qL72fwAAAABJRU5ErkJggg==' ;
  367. if( headers_sent() ) {
  368.     // Special case for headers already sent error. Dont
  369.     // return an image since it can't be displayed
  370.     die("<b>JpGraph Error:</b> ".$aMsg);
  371. }
  372. // Create the error icon GD
  373. $erricon = imagecreatefromstring(base64_decode($img_iconerror));   
  374. // Create an image that contains the error text.
  375. $w=370; $h=100;
  376. $img = new Image($w,$h);
  377. // Drop shadow
  378. $img->SetColor("gray");
  379. $img->FilledRectangle(5,5,$w-1,$h-1,10);
  380. $img->SetColor("gray:0.7");
  381. $img->FilledRectangle(5,5,$w-3,$h-3,10);
  382. // Window background
  383. $img->SetColor("lightblue");
  384. $img->FilledRectangle(1,1,$w-5,$h-5);
  385. $img->CopyCanvasH($img->img,$erricon,5,30,0,0,40,40);
  386. // Window border
  387. $img->SetColor("black");
  388. $img->Rectangle(1,1,$w-5,$h-5);
  389. $img->Rectangle(0,0,$w-4,$h-4);
  390. // Window top row
  391. $img->SetColor("darkred");
  392. for($y=3; $y < 18; $y += 2 ) 
  393.     $img->Line(1,$y,$w-25,$y);
  394. $img->Line(1,17,$w-5,17);
  395. $img->Line($w-25,1,$w-25,16);
  396. // "Close button"
  397. $img->Line($w-18,6,$w-12,12);
  398. $img->Line($w-18,7,$w-12,13);
  399. $img->Line($w-18,12,$w-12,6);
  400. $img->Line($w-18,13,$w-12,7);
  401. // "White shadow"
  402. $img->SetColor("white");
  403. // "Button"
  404. $img->Line($w-24,3,$w-24,15);
  405. $img->Line($w-24,2,$w-7,2);
  406. // Left window edge
  407. $img->Line(2,2,2,$h-5);
  408. $img->Line(2,2,$w-27,2);
  409. // "Gray button shadow"
  410. $img->SetColor("darkgray");
  411. $img->Line($w-6,3,$w-6,16);
  412. $img->Line($w-24,16,$w-7,16);
  413. // Gray window shadow
  414. $img->Line(2,$h-6,$w-5,$h-6);
  415. $img->Line(3,$h-7,$w-5,$h-7);
  416. // Window title
  417. $m = floor($w/2-5);
  418. $l = 100;
  419. $img->SetColor("lightgray:1.3");
  420. $img->FilledRectangle($m-$l,2,$m+$l,16);
  421. // Stroke text
  422. $img->SetColor("darkred");
  423. $img->SetFont(FF_FONT2,FS_BOLD);
  424. $img->StrokeText($m-50,15,"JpGraph Error");
  425. $img->SetColor("black");
  426. $img->SetFont(FF_FONT1,FS_NORMAL);
  427. $txt = new Text(wordwrap($aMsg,52),52,25);
  428. $txt->Align("left","top");
  429. $txt->Stroke($img);
  430. $img->Headers();
  431. $img->Stream();
  432. die();
  433.     }
  434. }
  435. //
  436. // A wrapper class that is used to access the specified error object
  437. // (to hide the global error parameter and avoid having a GLOBAL directive
  438. // in all methods.
  439. //
  440. class JpGraphError {
  441.     function Install($aErrObject) {
  442. GLOBAL $__jpg_err;
  443. $__jpg_err = $aErrObject;
  444.     }
  445.     function Raise($aMsg,$aHalt=true){
  446. GLOBAL $__jpg_err;
  447. $tmp = new $__jpg_err;
  448. $tmp->Raise($aMsg,$aHalt);
  449.     }
  450. }
  451. //
  452. // ... and install the default error handler
  453. //
  454. if( USE_IMAGE_ERROR_HANDLER ) {
  455.     JpGraphError::Install("JpGraphErrObjectImg");
  456. }
  457. else {
  458.     JpGraphError::Install("JpGraphErrObject");
  459. }
  460. //
  461. // Setup PHP error handler
  462. //
  463. error_reporting(E_ALL ^ E_NOTICE ^ E_WARNING);
  464. function _phpErrorHandler($errno,$errmsg,$filename, $linenum, $vars) {
  465.     JpGraphError::Raise('In '.basename($filename).'#'.$linenum."n".$errmsg);
  466. }
  467. #set_error_handler("_phpErrorHandler");
  468. //
  469. //Check if there were any warnings, perhaps some wrong includes by the
  470. //user
  471. //
  472. if( isset($GLOBALS['php_errormsg']) ) {
  473.     JpGraphError::Raise("<b>General PHP error:</b><br>".$GLOBALS['php_errormsg']);
  474. }
  475. //
  476. // Routine to determine if GD1 or GD2 is installed
  477. //
  478. function CheckGDVersion() {
  479.     ob_start();
  480.     phpinfo(8); // Just get the modules loaded
  481.     $a = ob_get_contents();
  482.     ob_end_clean();
  483.     if( preg_match('/.*GD Version.*(1.).*/',$a,$m) ) {
  484. $r=1;$v=$m[1];
  485.     }
  486.     elseif( preg_match('/.*GD Version.*(2.).*/',$a,$m) ) {
  487. $r=2;$v=$m[1];
  488.     }
  489.     else {
  490. $r=0;$v=$m[1];
  491.     }
  492.     return $r;
  493. }
  494. //
  495. // Check what version of the GD library is installed.
  496. //
  497. if( USE_LIBRARY_GD2 === 'auto' ) {
  498.     $gdversion = CheckGDVersion();
  499.     if( $gdversion == 2 ) {
  500. $GLOBALS['gd2'] = true;
  501. $GLOBALS['copyfunc'] = 'imagecopyresampled';
  502.     }
  503.     elseif( $gdversion == 1 ) {
  504. $GLOBALS['gd2'] = false;
  505. $GLOBALS['copyfunc'] = 'imagecopyresized';
  506.     }
  507.     else {
  508. JpGraphError::Raise(" Your PHP installation does not seem to 
  509. have the required GD library.
  510. Please see the PHP documentation on how to install and enable the GD library.");
  511.     }
  512. }
  513. else {
  514.     $GLOBALS['gd2'] = USE_LIBRARY_GD2;
  515.     $GLOBALS['copyfunc'] = USE_LIBRARY_GD2 ? 'imagecopyresampled' : 'imagecopyresized';
  516. }
  517. // Usefull mathematical function
  518. function sign($a) {return $a >= 0 ? 1 : -1;}
  519. // Utility function to generate an image name based on the filename we
  520. // are running from and assuming we use auto detection of graphic format
  521. // (top level), i.e it is safe to call this function
  522. // from a script that uses JpGraph
  523. function GenImgName() {
  524.     global $_SERVER;
  525.     $supported = imagetypes();
  526.     if( $supported & IMG_PNG )
  527. $img_format="png";
  528.     elseif( $supported & IMG_GIF )
  529. $img_format="gif";
  530.     elseif( $supported & IMG_JPG )
  531. $img_format="jpeg";
  532.     if( !isset($_SERVER['PHP_SELF']) )
  533. JpGraphError::Raise(" Can't access PHP_SELF, PHP global variable. You can't run PHP from command line
  534. if you want to use the 'auto' naming of cache or image files.");
  535.     $fname=basename($_SERVER['PHP_SELF']);
  536.     // Replace the ".php" extension with the image format extension
  537.     return substr($fname,0,strlen($fname)-4).".".$img_format;
  538. }
  539. class LanguageConv {
  540.     var $g2312 = null ;
  541.     function Convert($aTxt,$aFF) {
  542. if( LANGUAGE_CYRILLIC ) {
  543.     if( CYRILLIC_FROM_WINDOWS ) {
  544. $aTxt = convert_cyr_string($aTxt, "w", "k"); 
  545.     }
  546.     $isostring = convert_cyr_string($aTxt, "k", "i");
  547.     $unistring = LanguageConv::iso2uni($isostring);
  548.     return $unistring;
  549. }
  550. elseif( $aFF === FF_SIMSUN ) {
  551.     // Do Chinese conversion
  552.     if( $this->g2312 == null ) {
  553. include_once 'jpgraph_gb2312.php' ;
  554. $this->g2312 = new GB2312toUTF8();
  555.     }
  556.     return $this->g2312->gb2utf8($aTxt);
  557. }
  558. else 
  559.     return $aTxt;
  560.     }
  561.     // Translate iso encoding to unicode
  562.     function iso2uni ($isoline){
  563. for ($i=0; $i < strlen($isoline); $i++){
  564.     $thischar=substr($isoline,$i,1);
  565.     $charcode=ord($thischar);
  566.     $uniline.=($charcode>175) ? "&#" . (1040+($charcode-176)). ";" : $thischar;
  567. }
  568. return $uniline;
  569.     }
  570. }
  571. //===================================================
  572. // CLASS JpgTimer
  573. // Description: General timing utility class to handle
  574. // timne measurement of generating graphs. Multiple
  575. // timers can be started by pushing new on a stack.
  576. //===================================================
  577. class JpgTimer {
  578.     var $start;
  579.     var $idx;
  580. //---------------
  581. // CONSTRUCTOR
  582.     function JpgTimer() {
  583. $this->idx=0;
  584.     }
  585. //---------------
  586. // PUBLIC METHODS
  587.     // Push a new timer start on stack
  588.     function Push() {
  589. list($ms,$s)=explode(" ",microtime());
  590. $this->start[$this->idx++]=floor($ms*1000) + 1000*$s;
  591.     }
  592.     // Pop the latest timer start and return the diff with the
  593.     // current time
  594.     function Pop() {
  595. assert($this->idx>0);
  596. list($ms,$s)=explode(" ",microtime());
  597. $etime=floor($ms*1000) + (1000*$s);
  598. $this->idx--;
  599. return $etime-$this->start[$this->idx];
  600.     }
  601. } // Class
  602. $gJpgBrandTiming = BRAND_TIMING;
  603. //===================================================
  604. // CLASS DateLocale
  605. // Description: Hold localized text used in dates
  606. // ToDOo: Rewrite this to use the real local locale
  607. // instead.
  608. //===================================================
  609. class DateLocale {
  610.  
  611.     var $iLocale = 'C'; // environmental locale be used by default
  612.     var $iDayAbb = null;
  613.     var $iShortDay = null;
  614.     var $iShortMonth = null;
  615.     var $iMonthName = null;
  616. //---------------
  617. // CONSTRUCTOR
  618.     function DateLocale() {
  619. settype($this->iDayAbb, 'array');
  620. settype($this->iShortDay, 'array');
  621. settype($this->iShortMonth, 'array');
  622. settype($this->iMonthName, 'array');
  623. $this->Set('C');
  624.     }
  625. //---------------
  626. // PUBLIC METHODS
  627.     function Set($aLocale) {
  628. if ( in_array($aLocale, array_keys($this->iDayAbb)) ){ 
  629.     $this->iLocale = $aLocale;
  630.     return TRUE;  // already cached nothing else to do!
  631. }
  632. $pLocale = setlocale(LC_TIME, 0); // get current locale for LC_TIME
  633. $res = setlocale(LC_TIME, $aLocale);
  634. if ( ! $res ){
  635.     JpGraphError::Raise("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.");
  636.     return FALSE;
  637. }
  638.  
  639. $this->iLocale = $aLocale;
  640. for ( $i = 0, $ofs = 0 - strftime('%w'); $i < 7; $i++, $ofs++ ){
  641.     $day = strftime('%a', strtotime("$ofs day"));
  642.     $day{0} = strtoupper($day{0});
  643.     $this->iDayAbb[$aLocale][]= $day{0};
  644.     $this->iShortDay[$aLocale][]= $day;
  645. }
  646. for($i=1; $i<=12; ++$i) {
  647.     list($short ,$full) = explode('|', strftime("%b|%B",strtotime("2001-$i-01")));
  648.     $this->iShortMonth[$aLocale][] = ucfirst($short);
  649.     $this->iMonthName [$aLocale][] = ucfirst($full);
  650. }
  651. setlocale(LC_TIME, $pLocale);
  652. return TRUE;
  653.     }
  654.     function GetDayAbb() {
  655. return $this->iDayAbb[$this->iLocale];
  656.     }
  657.     function GetShortDay() {
  658. return $this->iShortDay[$this->iLocale];
  659.     }
  660.     function GetShortMonth() {
  661. return $this->iShortMonth[$this->iLocale];
  662.     }
  663.     function GetShortMonthName($aNbr) {
  664. return $this->iShortMonth[$this->iLocale][$aNbr];
  665.     }
  666.     function GetLongMonthName($aNbr) {
  667. return $this->iMonthName[$this->iLocale][$aNbr];
  668.     }
  669.     function GetMonth() {
  670. return $this->iMonthName[$this->iLocale];
  671.     }
  672. }
  673. $gDateLocale = new DateLocale();
  674. $gJpgDateLocale = new DateLocale();
  675. //===================================================
  676. // CLASS FuncGenerator
  677. // Description: Utility class to help generate data for function plots. 
  678. // The class supports both parametric and regular functions.
  679. //===================================================
  680. class FuncGenerator {
  681.     var $iFunc='',$iXFunc='',$iMin,$iMax,$iStepSize;
  682.     function FuncGenerator($aFunc,$aXFunc='') {
  683. $this->iFunc = $aFunc;
  684. $this->iXFunc = $aXFunc;
  685.     }
  686.     function E($aXMin,$aXMax,$aSteps=50) {
  687. $this->iMin = $aXMin;
  688. $this->iMax = $aXMax;
  689. $this->iStepSize = ($aXMax-$aXMin)/$aSteps;
  690. if( $this->iXFunc != '' )
  691.     $t = 'for($i='.$aXMin.'; $i<='.$aXMax.'; $i += '.$this->iStepSize.') {$ya[]='.$this->iFunc.';$xa[]='.$this->iXFunc.';}';
  692. elseif( $this->iFunc != '' )
  693.     $t = 'for($x='.$aXMin.'; $x<='.$aXMax.'; $x += '.$this->iStepSize.') {$ya[]='.$this->iFunc.';$xa[]=$x;} $x='.$aXMax.';$ya[]='.$this->iFunc.';$xa[]=$x;';
  694. else
  695.     JpGraphError::Raise('FuncGenerator : No function specified. ');
  696. @eval($t);
  697. // If there is an error in the function specifcation this is the only
  698. // way we can discover that.
  699. if( empty($xa) || empty($ya) )
  700.     JpGraphError::Raise('FuncGenerator : Syntax error in function specification ');
  701. return array($xa,$ya);
  702.     }
  703. }
  704. //=======================================================
  705. // CLASS Footer
  706. // Description: Encapsulates the footer line in the Graph
  707. //
  708. //=======================================================
  709. class Footer {
  710.     var $left,$center,$right;
  711.     var $iLeftMargin = 3;
  712.     var $iRightMargin = 3;
  713.     var $iBottomMargin = 3;
  714.     function Footer() {
  715. $this->left = new Text();
  716. $this->left->ParagraphAlign('left');
  717. $this->center = new Text();
  718. $this->center->ParagraphAlign('center');
  719. $this->right = new Text();
  720. $this->right->ParagraphAlign('right');
  721.     }
  722.     function Stroke($aImg) {
  723. $y = $aImg->height - $this->iBottomMargin;
  724. $x = $this->iLeftMargin;
  725. $this->left->Align('left','bottom');
  726. $this->left->Stroke($aImg,$x,$y);
  727. $x = ($aImg->width - $this->iLeftMargin - $this->iRightMargin)/2;
  728. $this->center->Align('center','bottom');
  729. $this->center->Stroke($aImg,$x,$y);
  730. $x = $aImg->width - $this->iRightMargin;
  731. $this->right->Align('right','bottom');
  732. $this->right->Stroke($aImg,$x,$y);
  733.     }
  734. }
  735.     DEFINE('BGRAD_FRAME',1);
  736.     DEFINE('BGRAD_MARGIN',2);
  737.     DEFINE('BGRAD_PLOT',3);
  738. //===================================================
  739. // CLASS Graph
  740. // Description: Main class to handle graphs
  741. //===================================================
  742. class Graph {
  743.     var $cache=null; // Cache object (singleton)
  744.     var $img=null; // Img object (singleton)
  745.     var $plots=array(); // Array of all plot object in the graph (for Y 1 axis)
  746.     var $y2plots=array();// Array of all plot object in the graph (for Y 2 axis)
  747.     var $xscale=null; // X Scale object (could be instance of LinearScale or LogScale
  748.     var $yscale=null,$y2scale=null;
  749.     var $cache_name; // File name to be used for the current graph in the cache directory
  750.     var $xgrid=null; // X Grid object (linear or logarithmic)
  751.     var $ygrid=null,$y2grid=null; //dito for Y
  752.     var $doframe=true,$frame_color=array(0,0,0), $frame_weight=1; // Frame around graph
  753.     var $boxed=false, $box_color=array(0,0,0), $box_weight=1; // Box around plot area
  754.     var $doshadow=false,$shadow_width=4,$shadow_color=array(102,102,102); // Shadow for graph
  755.     var $xaxis=null; // X-axis (instane of Axis class)
  756.     var $yaxis=null, $y2axis=null; // Y axis (instance of Axis class)
  757.     var $margin_color=array(200,200,200); // Margin color of graph
  758.     var $plotarea_color=array(255,255,255); // Plot area color
  759.     var $title,$subtitle,$subsubtitle;  // Title and subtitle(s) text object
  760.     var $axtype="linlin"; // Type of axis
  761.     var $xtick_factor; // Factot to determine the maximum number of ticks depending on the plot with
  762.     var $texts=null; // Text object to ge shown in the graph
  763.     var $lines=null;
  764.     var $bands=null;
  765.     var $text_scale_off=0; // Text scale offset in world coordinates
  766.     var $background_image="",$background_image_type=-1,$background_image_format="png";
  767.     var $background_image_bright=0,$background_image_contr=0,$background_image_sat=0;
  768.     var $image_bright=0, $image_contr=0, $image_sat=0;
  769.     var $inline;
  770.     var $showcsim=0,$csimcolor="red"; //debug stuff, draw the csim boundaris on the image if <>0
  771.     var $grid_depth=DEPTH_BACK; // Draw grid under all plots as default
  772.     var $iAxisStyle = AXSTYLE_SIMPLE;
  773.     var $iCSIMdisplay=false,$iHasStroked = false;
  774.     var $footer;
  775.     var $csimcachename = '', $csimcachetimeout = 0;
  776.     var $iDoClipping = false;
  777.     var $y2orderback=true;
  778.     var $tabtitle;
  779.     var $bkg_gradtype=-1,$bkg_gradstyle=BGRAD_MARGIN;
  780.     var $bkg_gradfrom='navy', $bkg_gradto='silver';
  781.     var $titlebackground = false;
  782.     var $titlebackground_color = 'lightblue',
  783. $titlebackground_style = 1,
  784. $titlebackground_framecolor = 'blue',
  785. $titlebackground_framestyle = 2,
  786. $titlebackground_frameweight = 1,
  787. $titlebackground_bevelheight = 3 ;
  788.     var $titlebkg_fillstyle=TITLEBKG_FILLSTYLE_SOLID;
  789.     var $titlebkg_scolor1='black',$titlebkg_scolor2='white';
  790.     var $framebevel = false, $framebeveldepth = 2 ;
  791.     var $framebevelborder = false, $framebevelbordercolor='black';
  792.     var $framebevelcolor1='white@0.4', $framebevelcolor2='black@0.4';
  793. //---------------
  794. // CONSTRUCTOR
  795.     // aWIdth  Width in pixels of image
  796.     // aHeight   Height in pixels of image
  797.     // aCachedName Name for image file in cache directory 
  798.     // aTimeOut Timeout in minutes for image in cache
  799.     // aInline If true the image is streamed back in the call to Stroke()
  800.     // If false the image is just created in the cache
  801.     function Graph($aWidth=300,$aHeight=200,$aCachedName="",$aTimeOut=0,$aInline=true) {
  802. GLOBAL $gJpgBrandTiming;
  803. // If timing is used create a new timing object
  804. if( $gJpgBrandTiming ) {
  805.     global $tim;
  806.     $tim = new JpgTimer();
  807.     $tim->Push();
  808. }
  809. // Automatically generate the image file name based on the name of the script that
  810. // generates the graph
  811. if( $aCachedName=="auto" )
  812.     $aCachedName=GenImgName();
  813. // Should the image be streamed back to the browser or only to the cache?
  814. $this->inline=$aInline;
  815. $this->img = new RotImage($aWidth,$aHeight);
  816. $this->cache  = new ImgStreamCache($this->img);
  817. $this->cache->SetTimeOut($aTimeOut);
  818. $this->title = new Text();
  819. $this->title->ParagraphAlign('center');
  820. $this->title->SetFont(FF_FONT2,FS_BOLD);
  821. $this->title->SetMargin(3);
  822. $this->subtitle = new Text();
  823. $this->subtitle->ParagraphAlign('center');
  824. $this->subsubtitle = new Text();
  825. $this->subsubtitle->ParagraphAlign('center');
  826. $this->legend = new Legend();
  827. $this->footer = new Footer();
  828. // If the cached version exist just read it directly from the
  829. // cache, stream it back to browser and exit
  830. if( $aCachedName!="" && READ_CACHE && $aInline )
  831.     if( $this->cache->GetAndStream($aCachedName) ) {
  832. exit();
  833.     }
  834. $this->cache_name = $aCachedName;
  835. $this->SetTickDensity(); // Normal density
  836. $this->tabtitle = new GraphTabTitle();
  837.     }
  838. //---------------
  839. // PUBLIC METHODS
  840.     // Should the grid be in front or back of the plot?
  841.     function SetGridDepth($aDepth) {
  842. $this->grid_depth=$aDepth;
  843.     }
  844.     // Specify graph angle 0-360 degrees.
  845.     function SetAngle($aAngle) {
  846. $this->img->SetAngle($aAngle);
  847.     }
  848.     function SetAlphaBlending($aFlg=true) {
  849. $this->img->SetAlphaBlending($aFlg);
  850.     }
  851.     // Shortcut to image margin
  852.     function SetMargin($lm,$rm,$tm,$bm) {
  853. $this->img->SetMargin($lm,$rm,$tm,$bm);
  854.     }
  855.     function SetY2OrderBack($aBack=true) {
  856. $this->y2orderback = $aBack;
  857.     }
  858.     // Rotate the graph 90 degrees and set the margin 
  859.     // when we have done a 90 degree rotation
  860.     function Set90AndMargin($lm=0,$rm=0,$tm=0,$bm=0) {
  861. $lm = $lm ==0 ? floor(0.2 * $this->img->width)  : $lm ;
  862. $rm = $rm ==0 ? floor(0.1 * $this->img->width)  : $rm ;
  863. $tm = $tm ==0 ? floor(0.2 * $this->img->height) : $tm ;
  864. $bm = $bm ==0 ? floor(0.1 * $this->img->height) : $bm ;
  865. $adj = ($this->img->height - $this->img->width)/2;
  866. $this->img->SetMargin($tm-$adj,$bm-$adj,$rm+$adj,$lm+$adj);
  867. $this->img->SetCenter(floor($this->img->width/2),floor($this->img->height/2));
  868. $this->SetAngle(90);
  869. $this->xaxis->SetLabelAlign('right','center');
  870. $this->yaxis->SetLabelAlign('center','bottom');
  871.     }
  872.     function SetClipping($aFlg=true) {
  873. $this->iDoClipping = $aFlg ;
  874.     }
  875.     // Add a plot object to the graph
  876.     function Add(&$aPlot) {
  877. if( $aPlot == null )
  878.     JpGraphError::Raise("<b></b> Graph::Add() You tried to add a null plot to the graph.");
  879. if( is_array($aPlot) && count($aPlot) > 0 )
  880.     $cl = get_class($aPlot[0]);
  881. else
  882.     $cl = get_class($aPlot);
  883. if( $cl == 'text' ) 
  884.     $this->AddText($aPlot);
  885. elseif( $cl == 'plotline' )
  886.     $this->AddLine($aPlot);
  887. elseif( $cl == 'plotband' )
  888.     $this->AddBand($aPlot);
  889. else
  890.     $this->plots[] = &$aPlot;
  891.     }
  892.     // Add plot to second Y-scale
  893.     function AddY2(&$aPlot) {
  894. if( $aPlot == null )
  895.     JpGraphError::Raise("<b></b> Graph::AddY2() You tried to add a null plot to the graph.");
  896. $this->y2plots[] = &$aPlot;
  897.     }
  898.     // Add text object to the graph
  899.     function AddText(&$aTxt) {
  900. if( $aTxt == null )
  901.     JpGraphError::Raise("<b></b> Graph::AddText() You tried to add a null text to the graph.");
  902. if( is_array($aTxt) ) {
  903.     for($i=0; $i < count($aTxt); ++$i )
  904. $this->texts[]=&$aTxt[$i];
  905. }
  906. else
  907.     $this->texts[] = &$aTxt;
  908.     }
  909.     // Add a line object (class PlotLine) to the graph
  910.     function AddLine(&$aLine) {
  911. if( $aLine == null )
  912.     JpGraphError::Raise("<b></b> Graph::AddLine() You tried to add a null line to the graph.");
  913. if( is_array($aLine) ) {
  914.     for($i=0; $i<count($aLine); ++$i )
  915. $this->lines[]=&$aLine[$i];
  916. }
  917. else
  918.     $this->lines[] = &$aLine;
  919.     }
  920.     // Add vertical or horizontal band
  921.     function AddBand(&$aBand) {
  922. if( $aBand == null )
  923.     JpGraphError::Raise(" Graph::AddBand() You tried to add a null band to the graph.");
  924. if( is_array($aBand) ) {
  925.     for($i=0; $i<count($aBand); ++$i )
  926. $this->bands[] = &$aBand[$i];
  927. }
  928. else
  929.     $this->bands[] = &$aBand;
  930.     }
  931.     function SetBackgroundGradient($aFrom='navy',$aTo='silver',$aGradType=GRAD_HOR,$aStyle=BGRAD_FRAME) {
  932. $this->bkg_gradtype=$aGradType;
  933. $this->bkg_gradstyle=$aStyle;
  934. $this->bkg_gradfrom = $aFrom;
  935. $this->bkg_gradto = $aTo;
  936.     } 
  937.     // Specify a background image
  938.     function SetBackgroundImage($aFileName,$aBgType=BGIMG_FILLPLOT,$aImgFormat="auto") {
  939. if( $GLOBALS['gd2'] && !USE_TRUECOLOR ) {
  940.     JpGraphError::Raise("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.");
  941. }
  942. // Get extension to determine image type
  943. if( $aImgFormat == "auto" ) {
  944.     $e = explode('.',$aFileName);
  945.     if( !$e ) {
  946. JpGraphError::Raise('Incorrect file name for Graph::SetBackgroundImage() : '.$aFileName.' Must have a valid image extension (jpg,gif,png) when using autodetection of image type');
  947.     }
  948.     $valid_formats = array('png', 'jpg', 'gif');
  949.     $aImgFormat = strtolower($e[count($e)-1]);
  950.     if ($aImgFormat == 'jpeg')  {
  951. $aImgFormat = 'jpg';
  952.     }
  953.     elseif (!in_array($aImgFormat, $valid_formats) )  {
  954. JpGraphError::Raise('Unknown file extension ($aImgFormat) in Graph::SetBackgroundImage() for filename: '.$aFileName);
  955.     }    
  956. }
  957. $this->background_image = $aFileName;
  958. $this->background_image_type=$aBgType;
  959. $this->background_image_format=$aImgFormat;
  960.     }
  961.     // Adjust brightness and constrast for background image
  962.     function AdjBackgroundImage($aBright,$aContr=0,$aSat=0) {
  963. $this->background_image_bright=$aBright;
  964. $this->background_image_contr=$aContr;
  965. $this->background_image_sat=$aSat;
  966.     }
  967.     // Adjust brightness and constrast for image
  968.     function AdjImage($aBright,$aContr=0,$aSat=0) {
  969. $this->image_bright=$aBright;
  970. $this->image_contr=$aContr;
  971. $this->image_sat=$aSat;
  972.     }
  973.     // Specify axis style (boxed or single)
  974.     function SetAxisStyle($aStyle) {
  975.         $this->iAxisStyle = $aStyle ;
  976.     }
  977.     // Set a frame around the plot area
  978.     function SetBox($aDrawPlotFrame=true,$aPlotFrameColor=array(0,0,0),$aPlotFrameWeight=1) {
  979. $this->boxed = $aDrawPlotFrame;
  980. $this->box_weight = $aPlotFrameWeight;
  981. $this->box_color = $aPlotFrameColor;
  982.     }
  983.     // Specify color for the plotarea (not the margins)
  984.     function SetColor($aColor) {
  985. $this->plotarea_color=$aColor;
  986.     }
  987.     // Specify color for the margins (all areas outside the plotarea)
  988.     function SetMarginColor($aColor) {
  989. $this->margin_color=$aColor;
  990.     }
  991.     // Set a frame around the entire image
  992.     function SetFrame($aDrawImgFrame=true,$aImgFrameColor=array(0,0,0),$aImgFrameWeight=1) {
  993. $this->doframe = $aDrawImgFrame;
  994. $this->frame_color = $aImgFrameColor;
  995. $this->frame_weight = $aImgFrameWeight;
  996.     }
  997.     function SetFrameBevel($aDepth=3,$aBorder=false,$aBorderColor='black',$aColor1='white@0.4',$aColor2='darkgray@0.4',$aFlg=true) {
  998. $this->framebevel = $aFlg ;
  999. $this->framebeveldepth = $aDepth ;
  1000. $this->framebevelborder = $aBorder ;
  1001. $this->framebevelbordercolor = $aBorderColor ;
  1002. $this->framebevelcolor1 = $aColor1 ;
  1003. $this->framebevelcolor2 = $aColor2 ;
  1004. $this->doshadow = false ;
  1005.     }
  1006.     // Set the shadow around the whole image
  1007.     function SetShadow($aShowShadow=true,$aShadowWidth=5,$aShadowColor=array(102,102,102)) {
  1008. $this->doshadow = $aShowShadow;
  1009. $this->shadow_color = $aShadowColor;
  1010. $this->shadow_width = $aShadowWidth;
  1011. $this->footer->iBottomMargin += $aShadowWidth;
  1012. $this->footer->iRightMargin += $aShadowWidth;
  1013.     }
  1014.     // Specify x,y scale. Note that if you manually specify the scale
  1015.     // you must also specify the tick distance with a call to Ticks::Set()
  1016.     function SetScale($aAxisType,$aYMin=1,$aYMax=1,$aXMin=1,$aXMax=1) {
  1017. $this->axtype = $aAxisType;
  1018. if( $aYMax < $aYMin || $aXMax < $aXMin )
  1019.     JpGraphError::Raise('Graph::SetScale(): Specified Max value must be larger than the specified Min value.');
  1020. $yt=substr($aAxisType,-3,3);
  1021. if( $yt=="lin" )
  1022.     $this->yscale = new LinearScale($aYMin,$aYMax);
  1023. elseif( $yt == "int" ) {
  1024.     $this->yscale = new LinearScale($aYMin,$aYMax);
  1025.     $this->yscale->SetIntScale();
  1026. }
  1027. elseif( $yt=="log" )
  1028.     $this->yscale = new LogScale($aYMin,$aYMax);
  1029. else
  1030.     JpGraphError::Raise("Unknown scale specification for Y-scale. ($aAxisType)");
  1031. $xt=substr($aAxisType,0,3);
  1032. if( $xt == "lin" || $xt == "tex" ) {
  1033.     $this->xscale = new LinearScale($aXMin,$aXMax,"x");
  1034.     $this->xscale->textscale = ($xt == "tex");
  1035. }
  1036. elseif( $xt == "int" ) {
  1037.     $this->xscale = new LinearScale($aXMin,$aXMax,"x");
  1038.     $this->xscale->SetIntScale();
  1039. }
  1040. elseif( $xt == "log" )
  1041.     $this->xscale = new LogScale($aXMin,$aXMax,"x");
  1042. else
  1043.     JpGraphError::Raise(" Unknown scale specification for X-scale. ($aAxisType)");
  1044. $this->xscale->Init($this->img);
  1045. $this->yscale->Init($this->img);
  1046. $this->xaxis = new Axis($this->img,$this->xscale);
  1047. $this->yaxis = new Axis($this->img,$this->yscale);
  1048. $this->xgrid = new Grid($this->xaxis);
  1049. $this->ygrid = new Grid($this->yaxis);
  1050. $this->ygrid->Show();
  1051.     }
  1052.     // Specify secondary Y scale
  1053.     function SetY2Scale($aAxisType="lin",$aY2Min=1,$aY2Max=1) {
  1054. if( $aAxisType=="lin" ) 
  1055.     $this->y2scale = new LinearScale($aY2Min,$aY2Max);
  1056. elseif( $aAxisType == "int" ) {
  1057.     $this->y2scale = new LinearScale($aY2Min,$aY2Max);
  1058.     $this->y2scale->SetIntScale();
  1059. }
  1060. elseif( $aAxisType=="log" ) {
  1061.     $this->y2scale = new LogScale($aY2Min,$aY2Max);
  1062. }
  1063. else JpGraphError::Raise("JpGraph: Unsupported Y2 axis type: $axtype<br>");
  1064. $this->y2scale->Init($this->img);
  1065. $this->y2axis = new Axis($this->img,$this->y2scale);
  1066. $this->y2axis->scale->ticks->SetDirection(SIDE_LEFT); 
  1067. $this->y2axis->SetLabelSide(SIDE_RIGHT); 
  1068. // Deafult position is the max x-value
  1069. $this->y2grid = new Grid($this->y2axis);
  1070.     }
  1071.     // Specify density of ticks when autoscaling 'normal', 'dense', 'sparse', 'verysparse'
  1072.     // The dividing factor have been determined heuristically according to my aesthetic 
  1073.     // sense (or lack off) y.m.m.v !
  1074.     function SetTickDensity($aYDensity=TICKD_NORMAL,$aXDensity=TICKD_NORMAL) {
  1075. $this->xtick_factor=30;
  1076. $this->ytick_factor=25;
  1077. switch( $aYDensity ) {
  1078.     case TICKD_DENSE:
  1079. $this->ytick_factor=12;
  1080. break;
  1081.     case TICKD_NORMAL:
  1082. $this->ytick_factor=25;
  1083. break;
  1084.     case TICKD_SPARSE:
  1085. $this->ytick_factor=40;
  1086. break;
  1087.     case TICKD_VERYSPARSE:
  1088. $this->ytick_factor=100;
  1089. break;
  1090.     default:
  1091. JpGraphError::Raise("JpGraph: Unsupported Tick density: $densy");
  1092. }
  1093. switch( $aXDensity ) {
  1094.     case TICKD_DENSE:
  1095. $this->xtick_factor=15;
  1096. break;
  1097.     case TICKD_NORMAL:
  1098. $this->xtick_factor=30;
  1099. break;
  1100.     case TICKD_SPARSE:
  1101. $this->xtick_factor=45;
  1102. break;
  1103.     case TICKD_VERYSPARSE:
  1104. $this->xtick_factor=60;
  1105. break;
  1106.     default:
  1107. JpGraphError::Raise("JpGraph: Unsupported Tick density: $densx");
  1108. }
  1109.     }
  1110.     // Get a string of all image map areas
  1111.     function GetCSIMareas() {
  1112. if( !$this->iHasStroked )
  1113.     $this->Stroke(_CSIM_SPECIALFILE);
  1114. $csim=$this->legend->GetCSIMAreas();
  1115. $n = count($this->plots);
  1116. for( $i=0; $i<$n; ++$i ) 
  1117.     $csim .= $this->plots[$i]->GetCSIMareas();
  1118. $n = count($this->y2plots);
  1119. for( $i=0; $i<$n; ++$i ) 
  1120.     $csim .= $this->y2plots[$i]->GetCSIMareas();
  1121. return $csim;
  1122.     }
  1123.     // Get a complete <MAP>..</MAP> tag for the final image map
  1124.     function GetHTMLImageMap($aMapName) {
  1125. $im = "<MAP NAME="$aMapName">n";
  1126. $im .= $this->GetCSIMareas();
  1127. $im .= "</MAP>"; 
  1128. return $im;
  1129.     }
  1130.     function CheckCSIMCache($aCacheName,$aTimeOut=60) {
  1131. global $_SERVER;
  1132. if( $aCacheName=='auto' )
  1133.     $aCacheName=basename($_SERVER['PHP_SELF']);
  1134. $this->csimcachename = CSIMCACHE_DIR.$aCacheName;
  1135. $this->csimcachetimeout = $aTimeOut;
  1136. // First determine if we need to check for a cached version
  1137. // This differs from the standard cache in the sense that the
  1138. // image and CSIM map HTML file is written relative to the directory
  1139. // the script executes in and not the specified cache directory.
  1140. // The reason for this is that the cache directory is not necessarily
  1141. // accessible from the HTTP server.
  1142. if( $this->csimcachename != '' ) {
  1143.     $dir = dirname($this->csimcachename);
  1144.     $base = basename($this->csimcachename);
  1145.     $base = strtok($base,'.');
  1146.     $suffix = strtok('.');
  1147.     $basecsim = $dir.'/'.$base.'_csim_.html';
  1148.     $baseimg = $dir.'/'.$base.'.'.$this->img->img_format;
  1149.     $timedout=false;
  1150.     // Does it exist at all ?
  1151.     
  1152.     if( file_exists($basecsim) && file_exists($baseimg) ) {
  1153. // Check that it hasn't timed out
  1154. $diff=time()-filemtime($basecsim);
  1155. if( $this->csimcachetimeout>0 && ($diff > $this->csimcachetimeout*60) ) {
  1156.     $timedout=true;
  1157.     @unlink($basecsim);
  1158.     @unlink($baseimg);
  1159. }
  1160. else {
  1161.     if ($fh = @fopen($basecsim, "r")) {
  1162. fpassthru($fh);
  1163. exit();
  1164.     }
  1165.     else
  1166. JpGraphError::Raise(" Can't open cached CSIM "$basecsim" for reading.");
  1167. }
  1168.     }
  1169. }
  1170. return false;
  1171.     }
  1172.     function StrokeCSIM($aScriptName='',$aCSIMName='',$aBorder=0) {
  1173. GLOBAL $HTTP_GET_VARS;
  1174. if( $aCSIMName=='' ) {
  1175.     // create a random map name
  1176.     $r = rand(0,100000);
  1177.     $aCSIMName='__mapname'.$r.'__';
  1178. }
  1179. if( empty($HTTP_GET_VARS[_CSIM_DISPLAY]) ) {
  1180.     // First determine if we need to check for a cached version
  1181.     // This differs from the standard cache in the sense that the
  1182.     // image and CSIM map HTML file is written relative to the directory
  1183.     // the script executes in and not the specified cache directory.
  1184.     // The reason for this is that the cache directory is not necessarily
  1185.     // accessible from the HTTP server.
  1186.     if( $this->csimcachename != '' ) {
  1187. $dir = dirname($this->csimcachename);
  1188. $base = basename($this->csimcachename);
  1189. $base = strtok($base,'.');
  1190. $suffix = strtok('.');
  1191. $basecsim = $dir.'/'.$base.'_csim_.html';
  1192. $baseimg = $base.'.'.$this->img->img_format;
  1193. // Check that apache can write to directory specified
  1194. if( file_exists($dir) && !is_writeable($dir) ) {
  1195.     JpgraphError::Raise('Apache/PHP does not have permission to write to the CSIM cache directory ('.$dir.'). Check permissions.');
  1196. }
  1197. // Make sure directory exists
  1198. $this->cache->MakeDirs($dir);
  1199. // Write the image file
  1200. $this->Stroke(CSIMCACHE_DIR.$baseimg);
  1201. // Construct wrapper HTML and write to file and send it back to browser
  1202. $htmlwrap = $this->GetHTMLImageMap($aCSIMName)."n".
  1203.     '<img src="'.CSIMCACHE_HTTP_DIR.$baseimg.'" ISMAP USEMAP="#'.$aCSIMName.'" border='.$aBorder.'>'."n";
  1204. if($fh =  @fopen($basecsim,'w') ) {
  1205.     fwrite($fh,$htmlwrap);
  1206.     fclose($fh);
  1207.     echo $htmlwrap;
  1208. }
  1209. else
  1210.     JpGraphError::Raise(" Can't write CSIM "$basecsim" for writing. Check free space and permissions.");
  1211.     }
  1212.     else {
  1213. if( $aScriptName=='' ) {
  1214.     JpGraphError::Raise('Missing script name in call to StrokeCSIM(). You must specify the name of the actual image script as the first parameter to StrokeCSIM().');
  1215.     exit();
  1216. }
  1217. // Construct the HTML wrapper page
  1218. // Get all user defined URL arguments
  1219. reset($HTTP_GET_VARS);
  1220. // This is a JPGRAPH internal defined that prevents
  1221. // us from recursively coming here again
  1222. $urlarg='?'._CSIM_DISPLAY.'=1';
  1223. while( list($key,$value) = each($HTTP_GET_VARS) ) {
  1224.     if( is_array($value) ) {
  1225. $n = count($value);
  1226. for( $i=0; $i < $n; ++$i ) {
  1227.     $urlarg .= '&'.$key.'%5B%5D='.urlencode($value[$i]);
  1228. }
  1229.     }
  1230.     else {
  1231. $urlarg .= '&'.$key.'='.urlencode($value);
  1232.     }
  1233. }
  1234. echo $this->GetHTMLImageMap($aCSIMName);
  1235. echo "<img src='".$aScriptName.$urlarg."' ISMAP USEMAP='#".$aCSIMName."' border=$aBorder>";
  1236.     }
  1237. }
  1238. else {
  1239.     $this->Stroke();
  1240. }
  1241.     }
  1242.     function GetTextsYMinMax() {
  1243. $n = count($this->texts);
  1244. $min=null;
  1245. $max=null;
  1246. for( $i=0; $i < $n; ++$i ) {
  1247.     if( $this->texts[$i]->iScalePosY !== null && 
  1248. $this->texts[$i]->iScalePosX !== null  ) {
  1249. if( $min === null  ) {
  1250.     $min = $max = $this->texts[$i]->iScalePosY ;
  1251. }
  1252. else {
  1253.     $min = min($min,$this->texts[$i]->iScalePosY);
  1254.     $max = max($max,$this->texts[$i]->iScalePosY);
  1255. }
  1256.     }
  1257. }
  1258. if( $min !== null ) {
  1259.     return array($min,$max);
  1260. }
  1261. else
  1262.     return null;
  1263.     }
  1264.     function GetTextsXMinMax() {
  1265. $n = count($this->texts);
  1266. $min=null;
  1267. $max=null;
  1268. for( $i=0; $i < $n; ++$i ) {
  1269.     if( $this->texts[$i]->iScalePosY !== null && 
  1270. $this->texts[$i]->iScalePosX !== null  ) {
  1271. if( $min === null  ) {
  1272.     $min = $max = $this->texts[$i]->iScalePosX ;
  1273. }
  1274. else {
  1275.     $min = min($min,$this->texts[$i]->iScalePosX);
  1276.     $max = max($max,$this->texts[$i]->iScalePosX);
  1277. }
  1278.     }
  1279. }
  1280. if( $min !== null ) {
  1281.     return array($min,$max);
  1282. }
  1283. else
  1284.     return null;
  1285.     }
  1286.     function GetXMinMax() {
  1287. list($min,$ymin) = $this->plots[0]->Min();
  1288. list($max,$ymax) = $this->plots[0]->Max();
  1289. foreach( $this->plots as $p ) {
  1290.     list($xmin,$ymin) = $p->Min();
  1291.     list($xmax,$ymax) = $p->Max();
  1292.     $min = Min($xmin,$min);
  1293.     $max = Max($xmax,$max);
  1294. }
  1295. if( $this->y2axis != null ) {
  1296.     foreach( $this->y2plots as $p ) {
  1297. list($xmin,$ymin) = $p->Min();
  1298. list($xmax,$ymax) = $p->Max();
  1299. $min = Min($xmin,$min);
  1300. $max = Max($xmax,$max);
  1301.     }     
  1302. }
  1303. return array($min,$max);
  1304.     }
  1305.     function AdjustMarginsForTitles() {
  1306. $totrequired = 
  1307.     ($this->title->t != '' ? 
  1308.      $this->title->GetTextHeight($this->img) + $this->title->margin + 5 : 0 ) +
  1309.     ($this->subtitle->t != '' ? 
  1310.      $this->subtitle->GetTextHeight($this->img) + $this->subtitle->margin + 5 : 0 ) + 
  1311.     ($this->subsubtitle->t != '' ? 
  1312.      $this->subsubtitle->GetTextHeight($this->img) + $this->subsubtitle->margin + 5 : 0 ) ;
  1313. $btotrequired = 0;
  1314. if( !$this->xaxis->hide && !$this->xaxis->hide_labels ) {
  1315.     // Minimum bottom margin
  1316.     if( $this->xaxis->title->t != '' ) {
  1317. if( $this->img->a == 90 ) 
  1318.     $btotrequired = $this->yaxis->title->GetTextHeight($this->img) + 5 ;
  1319. else
  1320.     $btotrequired = $this->xaxis->title->GetTextHeight($this->img) + 5 ;
  1321.     }
  1322.     else
  1323. $btotrequired = 0;
  1324.     
  1325.     if( $this->img->a == 90 ) {
  1326. $this->img->SetFont($this->yaxis->font_family,$this->yaxis->font_style,
  1327.     $this->yaxis->font_size);
  1328. $lh = $this->img->GetTextHeight('Mg',$this->yaxis->label_angle);
  1329.     }
  1330.     else {
  1331. $this->img->SetFont($this->xaxis->font_family,$this->xaxis->font_style,
  1332.     $this->xaxis->font_size);
  1333. $lh = $this->img->GetTextHeight('Mg',$this->xaxis->label_angle);
  1334.     }
  1335.     
  1336.     $btotrequired += $lh + 5;
  1337. }
  1338. if( $this->img->a == 90 ) {
  1339.     // DO Nothing. It gets too messy to do this properly for 90 deg...
  1340. }
  1341. else{
  1342.     if( $this->img->top_margin < $totrequired ) {
  1343. $this->SetMargin($this->img->left_margin,$this->img->right_margin,
  1344.  $totrequired,$this->img->bottom_margin);
  1345.     }
  1346.     if( $this->img->bottom_margin < $btotrequired ) {
  1347. $this->SetMargin($this->img->left_margin,$this->img->right_margin,
  1348.  $this->img->top_margin,$btotrequired);
  1349.     }
  1350. }
  1351.     }
  1352.     // Stroke the graph
  1353.     // $aStrokeFileName If != "" the image will be written to this file and NOT
  1354.     // streamed back to the browser
  1355.     function Stroke($aStrokeFileName="") {
  1356. // Start by adjusting the margin so that potential titles will fit.
  1357. $this->AdjustMarginsForTitles();
  1358. // If the filename is the predefined value = '_csim_special_'
  1359. // we assume that the call to stroke only needs to do enough
  1360. // to correctly generate the CSIM maps.
  1361. // We use this variable to skip things we don't strictly need
  1362. // to do to generate the image map to improve performance
  1363. // a best we can. Therefor you will see a lot of tests !$_csim in the
  1364. // code below.
  1365. $_csim = ($aStrokeFileName===_CSIM_SPECIALFILE);
  1366. // We need to know if we have stroked the plot in the
  1367. // GetCSIMareas. Otherwise the CSIM hasn't been generated
  1368. // and in the case of GetCSIM called before stroke to generate
  1369. // CSIM without storing an image to disk GetCSIM must call Stroke.
  1370. $this->iHasStroked = true;
  1371. // Do any pre-stroke adjustment that is needed by the different plot types
  1372. // (i.e bar plots want's to add an offset to the x-labels etc)
  1373. for($i=0; $i<count($this->plots) ; ++$i ) {
  1374.     $this->plots[$i]->PreStrokeAdjust($this);
  1375.     $this->plots[$i]->DoLegend($this);
  1376. }
  1377. // Any plots on the second Y scale?
  1378. if( $this->y2scale != null ) {
  1379.     for($i=0; $i<count($this->y2plots) ; ++$i ) {
  1380. $this->y2plots[$i]->PreStrokeAdjust($this);
  1381. $this->y2plots[$i]->DoLegend($this);
  1382.     }
  1383. }
  1384. // Bail out if any of the Y-axis not been specified and
  1385. // has no plots. (This means it is impossible to do autoscaling and
  1386. // no other scale was given so we can't possible draw anything). If you use manual
  1387. // scaling you also have to supply the tick steps as well.
  1388. if( (!$this->yscale->IsSpecified() && count($this->plots)==0) ||
  1389.     ($this->y2scale!=null && !$this->y2scale->IsSpecified() && count($this->y2plots)==0) ) {
  1390.     $e = "Can't draw unspecified Y-scale.<br>nYou have either:<br>n";
  1391.     $e .= "1. Specified an Y axis for autoscaling but have not supplied any plots<br>n";
  1392.     $e .= "2. Specified a scale manually but have forgot to specify the tick steps";
  1393.     JpGraphError::Raise($e);
  1394. }
  1395. // Bail out if no plots and no specified X-scale
  1396. if( (!$this->xscale->IsSpecified() && count($this->plots)==0 && count($this->y2plots)==0) )
  1397.     JpGraphError::Raise("<strong>JpGraph: Can't draw unspecified X-scale.</strong><br>No plots.<br>");
  1398. //Check if we should autoscale y-axis
  1399. if( !$this->yscale->IsSpecified() && count($this->plots)>0 ) {
  1400.     list($min,$max) = $this->GetPlotsYMinMax($this->plots);
  1401.       $lres = $this->GetLinesYMinMax($this->lines);
  1402.     if( $lres ) {
  1403. list($linmin,$linmax) = $lres ;
  1404. $min = min($min,$linmin);
  1405. $max = max($max,$linmax);
  1406.     }
  1407.     $tres = $this->GetTextsYMinMax();
  1408.     if( $tres ) {
  1409. list($tmin,$tmax) = $tres ;
  1410. $min = min($min,$tmin);
  1411. $max = max($max,$tmax);
  1412.     }
  1413.     $this->yscale->AutoScale($this->img,$min,$max,
  1414.      $this->img->plotheight/$this->ytick_factor);
  1415. }
  1416. elseif( $this->yscale->IsSpecified() && 
  1417. ( $this->yscale->auto_ticks || !$this->yscale->ticks->IsSpecified()) ) {
  1418.     // The tick calculation will use the user suplied min/max values to determine
  1419.     // the ticks. If auto_ticks is false the exact user specifed min and max
  1420.     // values will be used for the scale. 
  1421.     // If auto_ticks is true then the scale might be slightly adjusted
  1422.     // so that the min and max values falls on an even major step.
  1423.     $min = $this->yscale->scale[0];
  1424.     $max = $this->yscale->scale[1];
  1425.     $this->yscale->AutoScale($this->img,$min,$max,
  1426.      $this->img->plotheight/$this->ytick_factor,
  1427.      $this->yscale->auto_ticks);
  1428. }
  1429. if( $this->y2scale != null) {
  1430.     if( !$this->y2scale->IsSpecified() && count($this->y2plots)>0 ) {
  1431. list($min,$max) = $this->GetPlotsYMinMax($this->y2plots);
  1432. $this->y2scale->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor);
  1433.     }
  1434.     elseif( $this->y2scale->IsSpecified() && 
  1435.     ( $this->y2scale->auto_ticks || !$this->y2scale->ticks->IsSpecified()) ) {
  1436. // The tick calculation will use the user suplied min/max values to determine
  1437. // the ticks. If auto_ticks is false the exact user specifed min and max
  1438. // values will be used for the scale. 
  1439. // If auto_ticks is true then the scale might be slightly adjusted
  1440. // so that the min and max values falls on an even major step.
  1441. $min = $this->y2scale->scale[0];
  1442. $max = $this->y2scale->scale[1];
  1443. $this->y2scale->AutoScale($this->img,$min,$max,
  1444.   $this->img->plotheight/$this->ytick_factor,
  1445.   $this->y2scale->auto_ticks);
  1446.     }
  1447. }
  1448. //Check if we should autoscale x-axis
  1449. if( !$this->xscale->IsSpecified() ) {
  1450.     if( substr($this->axtype,0,4) == "text" ) {
  1451. $max=0;
  1452. foreach( $this->plots as $p ) {
  1453.     $max=max($max,$p->numpoints-1);
  1454. }
  1455. $min=0;
  1456. if( $this->y2axis != null ) {
  1457.     foreach( $this->y2plots as $p ) {
  1458. $max=max($max,$p->numpoints-1);
  1459.     }     
  1460. }
  1461. $this->xscale->Update($this->img,$min,$max);
  1462. $this->xscale->ticks->Set($this->xaxis->tick_step,1);
  1463. $this->xscale->ticks->SupressMinorTickMarks();
  1464.     }
  1465.     else {
  1466. list($min,$max) = $this->GetXMinMax();
  1467. $lres = $this->GetLinesXMinMax($this->lines);
  1468. if( $lres ) {
  1469.     list($linmin,$linmax) = $lres ;
  1470.     $min = min($min,$linmin);
  1471.     $max = max($max,$linmax);
  1472. }
  1473. $tres = $this->GetTextsXMinMax();
  1474. if( $tres ) {
  1475.     list($tmin,$tmax) = $tres ;
  1476.     $min = min($min,$tmin);
  1477.     $max = max($max,$tmax);
  1478. }
  1479. $this->xscale->AutoScale($this->img,$min,$max,$this->img->plotwidth/$this->xtick_factor);
  1480.     }
  1481.     //Adjust position of y-axis and y2-axis to minimum/maximum of x-scale
  1482.     if( !is_numeric($this->yaxis->pos) && !is_string($this->yaxis->pos) )
  1483.      $this->yaxis->SetPos($this->xscale->GetMinVal());
  1484.     if( $this->y2axis != null ) {
  1485. if( !is_numeric($this->y2axis->pos) && !is_string($this->y2axis->pos) )
  1486.     $this->y2axis->SetPos($this->xscale->GetMaxVal());
  1487. $this->y2axis->SetTitleSide(SIDE_RIGHT);
  1488.     }
  1489. }
  1490. elseif( $this->xscale->IsSpecified() &&  
  1491. ( $this->xscale->auto_ticks || !$this->xscale->ticks->IsSpecified()) ) {
  1492.     // The tick calculation will use the user suplied min/max values to determine
  1493.     // the ticks. If auto_ticks is false the exact user specifed min and max
  1494.     // values will be used for the scale. 
  1495.     // If auto_ticks is true then the scale might be slightly adjusted
  1496.     // so that the min and max values falls on an even major step.
  1497.     $min = $this->xscale->scale[0];
  1498.     $max = $this->xscale->scale[1];
  1499.     $this->xscale->AutoScale($this->img,$min,$max,
  1500.      $this->img->plotwidth/$this->xtick_factor,
  1501.      false);
  1502.     if( $this->y2axis != null ) {
  1503. if( !is_numeric($this->y2axis->pos) && !is_string($this->y2axis->pos) )
  1504.     $this->y2axis->SetPos($this->xscale->GetMaxVal());
  1505. $this->y2axis->SetTitleSide(SIDE_RIGHT);
  1506.     }
  1507. }
  1508. // If we have a negative values and x-axis position is at 0
  1509. // we need to supress the first and possible the last tick since
  1510. // they will be drawn on top of the y-axis (and possible y2 axis)
  1511. // The test below might seem strange the reasone being that if
  1512. // the user hasn't specified a value for position this will not
  1513. // be set until we do the stroke for the axis so as of now it
  1514. // is undefined.
  1515. // For X-text scale we ignore all this since the tick are usually
  1516. // much further in and not close to the Y-axis. Hence the test 
  1517. // for 'text'
  1518. if( ($this->yaxis->pos==$this->xscale->GetMinVal() || 
  1519.      (is_string($this->yaxis->pos) && $this->yaxis->pos=='min')) &&  
  1520.     !is_numeric($this->xaxis->pos) && $this->yscale->GetMinVal() < 0 && 
  1521.     substr($this->axtype,0,4) != 'text' && $this->xaxis->pos!="min" ) {
  1522.     //$this->yscale->ticks->SupressZeroLabel(false);
  1523.     $this->xscale->ticks->SupressFirst();
  1524.     if( $this->y2axis != null ) {
  1525. $this->xscale->ticks->SupressLast();
  1526.     }
  1527. }
  1528. elseif( !is_numeric($this->yaxis->pos) && $this->yaxis->pos=='max' ) {
  1529.     $this->xscale->ticks->SupressLast();
  1530. }
  1531. if( !$_csim ) {
  1532.     $this->StrokePlotArea();
  1533.     $this->StrokeAxis();
  1534. }
  1535. // Stroke bands
  1536. if( $this->bands != null && !$_csim) 
  1537.     for($i=0; $i<count($this->bands); ++$i) {
  1538. // Stroke all bands that asks to be in the background
  1539. if( $this->bands[$i]->depth == DEPTH_BACK )
  1540.     $this->bands[$i]->Stroke($this->img,$this->xscale,$this->yscale);
  1541.     }
  1542. if( $this->grid_depth == DEPTH_BACK && !$_csim) {
  1543.     $this->ygrid->Stroke();
  1544.     $this->xgrid->Stroke();
  1545. }
  1546. // Stroke Y2-axis
  1547. if( $this->y2axis != null && !$_csim) {
  1548.     $this->y2axis->Stroke($this->xscale); 
  1549.     $this->y2grid->Stroke();
  1550. }
  1551. $oldoff=$this->xscale->off;
  1552. if(substr($this->axtype,0,4)=="text") {
  1553.     $this->xscale->off += 
  1554. ceil($this->xscale->scale_factor*$this->text_scale_off*$this->xscale->ticks->minor_step);
  1555. }
  1556. if( $this->iDoClipping ) {
  1557.     $oldimage = $this->img->CloneCanvasH();
  1558. }
  1559. if( ! $this->y2orderback ) {
  1560.     // Stroke all plots for Y1 axis
  1561.     for($i=0; $i < count($this->plots); ++$i) {
  1562. $this->plots[$i]->Stroke($this->img,$this->xscale,$this->yscale);
  1563. $this->plots[$i]->StrokeMargin($this->img);
  1564.     }
  1565. }
  1566. // Stroke all plots for Y2 axis
  1567. if( $this->y2scale != null )
  1568.     for($i=0; $i< count($this->y2plots); ++$i ) {
  1569. $this->y2plots[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
  1570.     }
  1571. if( $this->y2orderback ) {
  1572.     // Stroke all plots for Y1 axis
  1573.     for($i=0; $i < count($this->plots); ++$i) {
  1574. $this->plots[$i]->Stroke($this->img,$this->xscale,$this->yscale);
  1575. $this->plots[$i]->StrokeMargin($this->img);
  1576.     }
  1577. }
  1578. if( $this->iDoClipping ) {
  1579.     // Clipping only supports graphs at 0 and 90 degrees
  1580.     if( $this->img->a == 0 ) {
  1581. $this->img->CopyCanvasH($oldimage,$this->img->img,
  1582. $this->img->left_margin,$this->img->top_margin,
  1583. $this->img->left_margin,$this->img->top_margin,
  1584. $this->img->plotwidth+1,$this->img->plotheight);
  1585.     }
  1586.     elseif( $this->img->a == 90 ) {
  1587. $adj = ($this->img->height - $this->img->width)/2;
  1588. $this->img->CopyCanvasH($oldimage,$this->img->img,
  1589. $this->img->bottom_margin-$adj,$this->img->left_margin+$adj,
  1590. $this->img->bottom_margin-$adj,$this->img->left_margin+$adj,
  1591. $this->img->plotheight+1,$this->img->plotwidth);
  1592.     }
  1593.     else {
  1594. JpGraphError::Raise('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.');
  1595.     }
  1596.     $this->img->Destroy();
  1597.     $this->img->SetCanvasH($oldimage);
  1598. }
  1599. $this->xscale->off=$oldoff;
  1600. if( $this->grid_depth == DEPTH_FRONT && !$_csim ) {
  1601.     $this->ygrid->Stroke();
  1602.     $this->xgrid->Stroke();
  1603. }
  1604. // Stroke bands
  1605. if( $this->bands!= null )
  1606.     for($i=0; $i<count($this->bands); ++$i) {
  1607. // Stroke all bands that asks to be in the foreground
  1608. if( $this->bands[$i]->depth == DEPTH_FRONT )
  1609.     $this->bands[$i]->Stroke($this->img,$this->xscale,$this->yscale);
  1610.     }
  1611. // Stroke any lines added
  1612. if( $this->lines != null ) {
  1613.     for($i=0; $i<count($this->lines); ++$i) {
  1614. $this->lines[$i]->Stroke($this->img,$this->xscale,$this->yscale);
  1615.     }
  1616. }
  1617. // Finally draw the axis again since some plots may have nagged
  1618. // the axis in the edges.
  1619. if( !$_csim )
  1620.     $this->StrokeAxis();
  1621. if( $this->y2scale != null && !$_csim ) 
  1622.     $this->y2axis->Stroke($this->xscale); 
  1623. if( !$_csim ) {
  1624.     $this->StrokePlotBox();
  1625. }
  1626. if( !$_csim ) {
  1627.     // The titles and legends never gets rotated so make sure
  1628.     // that the angle is 0 before stroking them
  1629.     $aa = $this->img->SetAngle(0);
  1630.     $this->StrokeTitles();
  1631.     $this->footer->Stroke($this->img);
  1632. }
  1633. $this->legend->Stroke($this->img);
  1634. if( !$_csim ) {
  1635.     $this->StrokeTexts();
  1636.     $this->img->SetAngle($aa);
  1637.     // Draw an outline around the image map
  1638.     if(JPG_DEBUG)
  1639. $this->DisplayClientSideaImageMapAreas();
  1640.     
  1641.     // Adjust the appearance of the image
  1642.     $this->AdjustSaturationBrightnessContrast();
  1643.     // If the filename is given as the special "__handle"
  1644.     // then the image handler is returned and the image is NOT
  1645.     // streamed back
  1646.     if( $aStrokeFileName == _IMG_HANDLER ) {
  1647. return $this->img->img;
  1648.     }
  1649.     else {
  1650. // Finally stream the generated picture
  1651. $this->cache->PutAndStream($this->img,$this->cache_name,$this->inline,
  1652.    $aStrokeFileName);
  1653.     }
  1654. }
  1655.     }
  1656. //---------------
  1657. // PRIVATE METHODS
  1658.     function StrokeAxis() {
  1659. // Stroke axis
  1660. if( $this->iAxisStyle != AXSTYLE_SIMPLE ) {
  1661.     switch( $this->iAxisStyle ) {
  1662.         case AXSTYLE_BOXIN :
  1663.             $toppos = SIDE_DOWN;
  1664.     $bottompos = SIDE_UP;
  1665.             $leftpos = SIDE_RIGHT;
  1666.             $rightpos = SIDE_LEFT;
  1667.             break;
  1668. case AXSTYLE_BOXOUT :
  1669.     $toppos = SIDE_UP;
  1670.             $bottompos = SIDE_DOWN;     
  1671.             $leftpos = SIDE_LEFT;
  1672.     $rightpos = SIDE_RIGHT;
  1673.             break;
  1674. case AXSTYLE_YBOXIN:
  1675.             $toppos = -100;
  1676.     $bottompos = SIDE_UP;
  1677.             $leftpos = SIDE_RIGHT;
  1678.             $rightpos = SIDE_LEFT;
  1679.     break;
  1680. case AXSTYLE_YBOXOUT:
  1681.     $toppos = -100;
  1682.             $bottompos = SIDE_DOWN;     
  1683.             $leftpos = SIDE_LEFT;
  1684.     $rightpos = SIDE_RIGHT;
  1685.     break;
  1686. default:
  1687.             JpGRaphError::Raise('Unknown AxisStyle() : '.$this->iAxisStyle);
  1688.             break;
  1689.     }
  1690.     $this->xaxis->SetPos('min');
  1691.     
  1692.     // By default we hide the first label so it doesn't cross the
  1693.     // Y-axis in case the positon hasn't been set by the user.
  1694.     // However, if we use a box we always want the first value
  1695.     // displayed so we make sure it will be displayed.
  1696.     $this->xscale->ticks->SupressFirst(false);
  1697.     
  1698.     $this->xaxis->SetLabelSide(SIDE_DOWN);
  1699.     $this->xaxis->scale->ticks->SetSide($bottompos);
  1700.     $this->xaxis->Stroke($this->yscale);
  1701.     if( $toppos != -100 ) {
  1702. // To avoid side effects we work on a new copy
  1703. $maxis = $this->xaxis;
  1704. $maxis->SetPos('max');
  1705. $maxis->SetLabelSide(SIDE_UP);
  1706. $maxis->SetLabelMargin(7);
  1707. $this->xaxis->scale->ticks->SetSide($toppos);
  1708. $maxis->Stroke($this->yscale);
  1709.     }
  1710.     $this->yaxis->SetPos('min');
  1711.     $this->yaxis->SetLabelMargin(10);
  1712.     $this->yaxis->SetLabelSide(SIDE_LEFT);
  1713.     $this->yaxis->scale->ticks->SetSide($leftpos);
  1714.     $this->yaxis->Stroke($this->xscale);
  1715.     $myaxis = $this->yaxis;
  1716.     $myaxis->SetPos('max');
  1717.     $myaxis->SetLabelMargin(10);
  1718.     $myaxis->SetLabelSide(SIDE_RIGHT);
  1719.     $myaxis->title->Set('');
  1720.     $myaxis->scale->ticks->SetSide($rightpos);
  1721.     $myaxis->Stroke($this->xscale);
  1722.     
  1723. }
  1724. else {
  1725.     $this->xaxis->Stroke($this->yscale);
  1726.     $this->yaxis->Stroke($this->xscale);
  1727. }
  1728.     }
  1729.     // Private helper function for backgound image
  1730.     function LoadBkgImage($aImgFormat='',$aFile='') {
  1731. if( $aFile == '' )
  1732.     $aFile = $this->background_image;
  1733. // Remove case sensitivity and setup appropriate function to create image
  1734. // Get file extension. This should be the LAST '.' separated part of the filename
  1735. $e = explode('.',$aFile);
  1736. $ext = strtolower($e[count($e)-1]);
  1737. if ($ext == "jpeg")  {
  1738.     $ext = "jpg";
  1739. }
  1740. if( trim($ext) == '' ) 
  1741.     $ext = 'png';  // Assume PNG if no extension specified
  1742. if( $aImgFormat == '' )
  1743.     $imgtag = $ext;
  1744. else
  1745.     $imgtag = $aImgFormat;
  1746. if( $imgtag == "jpg" || $imgtag == "jpeg")
  1747. {
  1748. $f = "imagecreatefromjpeg";
  1749. $imgtag = "jpg";
  1750. }
  1751. else
  1752. {
  1753. $f = "imagecreatefrom".$imgtag;
  1754. }
  1755. // Compare specified image type and file extension
  1756. if( $imgtag != $ext ) {
  1757.     $t = " Background image seems to be of different type (has different file extension)".
  1758.  " than specified imagetype. <br>Specified: '".
  1759. $aImgFormat."'<br>File: '".$aFile."'";
  1760.     JpGraphError::Raise($t);
  1761. }
  1762. $img = @$f($aFile);
  1763. if( !$img ) {
  1764.     JpGraphError::Raise(" Can't read background image: '".$aFile."'");   
  1765. }
  1766. return $img;
  1767.     }
  1768.     function StrokeBackgroundGrad() {
  1769. if( $this->bkg_gradtype < 0  ) 
  1770.     return;
  1771. $grad = new Gradient($this->img);
  1772. if( $this->bkg_gradstyle == BGRAD_PLOT ) {
  1773.     $xl = $this->img->left_margin;
  1774.     $yt = $this->img->top_margin;
  1775.     $xr = $xl + $this->img->plotwidth ;
  1776.     $yb = $yt + $this->img->plotheight ;
  1777. }
  1778. else {
  1779.     $xl = 0;
  1780.     $yt = 0;
  1781.     $xr = $xl + $this->img->width - 1;
  1782.     $yb = $yt + $this->img->height - 1;
  1783. }
  1784. if( $this->doshadow  ) {
  1785.     $xr -= $this->shadow_width; 
  1786.     $yb -= $this->shadow_width; 
  1787. }
  1788. $grad->FilledRectangle($xl,$yt,$xr,$yb,
  1789.        $this->bkg_gradfrom,$this->bkg_gradto,
  1790.        $this->bkg_gradtype);
  1791.     }
  1792.     function StrokeFrameBackground() {
  1793. if( $this->background_image == "" ) 
  1794.     return;
  1795. $bkgimg = $this->LoadBkgImage($this->background_image_format);
  1796. $this->img->_AdjBrightContrast($bkgimg,$this->background_image_bright,
  1797.        $this->background_image_contr);
  1798. $this->img->_AdjSat($bkgimg,$this->background_image_sat);
  1799. $bw = ImageSX($bkgimg);
  1800. $bh = ImageSY($bkgimg);
  1801. // No matter what the angle is we always stroke the image and frame
  1802. // assuming it is 0 degree
  1803. $aa = $this->img->SetAngle(0);
  1804. switch( $this->background_image_type ) {
  1805.     case BGIMG_FILLPLOT: // Resize to just fill the plotarea
  1806. $this->StrokeFrame();
  1807. $GLOBALS['copyfunc']($this->img->img,$bkgimg,
  1808.      $this->img->left_margin,$this->img->top_margin,
  1809.      0,0,$this->img->plotwidth,$this->img->plotheight,
  1810.      $bw,$bh);
  1811. break;
  1812.     case BGIMG_FILLFRAME: // Fill the whole area from upper left corner, resize to just fit
  1813. $GLOBALS['copyfunc']($this->img->img,$bkgimg,
  1814.      0,0,0,0,
  1815.      $this->img->width,$this->img->height,
  1816.      $bw,$bh);
  1817. $this->StrokeFrame();
  1818. break;
  1819.     case BGIMG_COPY: // Just copy the image from left corner, no resizing
  1820. $GLOBALS['copyfunc']($this->img->img,$bkgimg,
  1821.      0,0,0,0,
  1822.      $bw,$bh,
  1823.      $bw,$bh);
  1824. $this->StrokeFrame();
  1825. break;
  1826.     case BGIMG_CENTER: // Center original image in the plot area
  1827. $centerx = round($this->img->plotwidth/2+$this->img->left_margin-$bw/2);
  1828. $centery = round($this->img->plotheight/2+$this->img->top_margin-$bh/2);
  1829. $GLOBALS['copyfunc']($this->img->img,$bkgimg,
  1830.      $centerx,$centery,
  1831.      0,0,
  1832.      $bw,$bh,
  1833.      $bw,$bh);
  1834. $this->StrokeFrame();
  1835. break;
  1836.     default:
  1837. JpGraphError::Raise(" Unknown background image layout");
  1838. }
  1839. $this->img->SetAngle($aa);
  1840.     }
  1841.     // Private
  1842.     // Draw a frame around the image
  1843.     function StrokeFrame() {
  1844. if( !$this->doframe ) return;
  1845. if( $this->background_image_type <= 1 && 
  1846.     ($this->bkg_gradtype < 0 || 
  1847.      ($this->bkg_gradtype > 0 && $this->bkg_gradstyle==BGRAD_PLOT) ) ) 
  1848.     $c = $this->margin_color;
  1849. else
  1850.     $c = false;
  1851. if( $this->doshadow ) {
  1852.     $this->img->SetColor($this->frame_color);
  1853.     $this->img->ShadowRectangle(0,0,$this->img->width,$this->img->height,
  1854. $c,$this->shadow_width,$this->shadow_color);
  1855. }
  1856. elseif( $this->framebevel ) {
  1857.     if( $c ) {
  1858. $this->img->SetColor($this->margin_color);
  1859. $this->img->FilledRectangle(0,0,$this->img->width-1,$this->img->height-1); 
  1860.     }
  1861.     $this->img->Bevel(1,1,$this->img->width-2,$this->img->height-2,
  1862.       $this->framebeveldepth,
  1863.       $this->framebevelcolor1,$this->framebevelcolor2);
  1864.     if( $this->framebevelborder ) {
  1865. $this->img->SetColor($this->framebevelbordercolor);
  1866. $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);
  1867.     }
  1868. }
  1869. else {
  1870.     $this->img->SetLineWeight($this->frame_weight);
  1871.     if( $c ) {
  1872. $this->img->SetColor($this->margin_color);
  1873. $this->img->FilledRectangle(0,0,$this->img->width-1,$this->img->height-1); 
  1874.     }
  1875.     $this->img->SetColor($this->frame_color);
  1876.     $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);
  1877. }
  1878.     }
  1879.     // Stroke the plot area with either a solid color or a background image
  1880.     function StrokePlotArea() {
  1881. // Note: To be consistent we really should take a possible shadow
  1882. // into account. However, that causes some problem for the LinearScale class
  1883. // since in the current design it does not have any links to class Graph which
  1884. // means it has no way of compensating for the adjusted plotarea in case of a 
  1885. // shadow. So, until I redesign LinearScale we can't compensate for this.
  1886. // So just set the two adjustment parameters to zero for now.
  1887. $boxadj = 0; //$this->doframe ? $this->frame_weight : 0 ;
  1888. $adj = 0; //$this->doshadow ? $this->shadow_width : 0 ;
  1889. if( $this->background_image != "" ) {
  1890.     $this->StrokeFrameBackground();
  1891. }
  1892. else {
  1893.     $aa = $this->img->SetAngle(0);
  1894.     $this->StrokeFrame();
  1895.     $aa = $this->img->SetAngle($aa);
  1896.     $this->StrokeBackgroundGrad();
  1897.     if( $this->bkg_gradtype < 0 || 
  1898. ($this->bkg_gradtype > 0 && $this->bkg_gradstyle==BGRAD_MARGIN ) ) {
  1899. $this->img->PushColor($this->plotarea_color);
  1900. $this->img->FilledRectangle($this->img->left_margin+$boxadj,
  1901.     $this->img->top_margin+$boxadj,
  1902.     $this->img->width-$this->img->right_margin-$adj-2*$boxadj,
  1903.     $this->img->height-$this->img->bottom_margin-$adj-2*$boxadj);
  1904. $this->img->PopColor();
  1905.     }
  1906. }
  1907.     }
  1908.     function StrokePlotBox() {
  1909. // Should we draw a box around the plot area?
  1910. if( $this->boxed ) {
  1911.     $this->img->SetLineWeight($this->box_weight);
  1912.     $this->img->SetColor($this->box_color);
  1913.     $this->img->Rectangle(
  1914. $this->img->left_margin,$this->img->top_margin,
  1915. $this->img->width-$this->img->right_margin,
  1916. $this->img->height-$this->img->bottom_margin);
  1917. }
  1918.     }
  1919.     function SetTitleBackgroundFillStyle($aStyle,$aColor1='black',$aColor2='white') {
  1920. $this->titlebkg_fillstyle = $aStyle;
  1921. $this->titlebkg_scolor1 = $aColor1;
  1922. $this->titlebkg_scolor2 = $aColor2;
  1923.     }
  1924.     function SetTitleBackground($aBackColor='gray', $aStyle=TITLEBKG_STYLE1, $aFrameStyle=TITLEBKG_FRAME_NONE, $aFrameColor='black', $aFrameWeight=1, $aBevelHeight=3, $aEnable=true) {
  1925. $this->titlebackground = $aEnable;
  1926. $this->titlebackground_color = $aBackColor;
  1927. $this->titlebackground_style = $aStyle;
  1928. $this->titlebackground_framecolor = $aFrameColor;
  1929. $this->titlebackground_framestyle = $aFrameStyle;
  1930. $this->titlebackground_frameweight = $aFrameWeight;
  1931. $this->titlebackground_bevelheight = $aBevelHeight ;
  1932.     }
  1933.     function StrokeTitles() {
  1934. $margin=3;
  1935. if( $this->titlebackground ) {
  1936.     // Find out height
  1937.     $this->title->margin += 2 ;
  1938.     $h = $this->title->GetTextHeight($this->img)+$this->title->margin+$margin;
  1939.     if( $this->subtitle->t != "" && !$this->subtitle->hide ) {
  1940. $h += $this->subtitle->GetTextHeight($this->img)+$margin+
  1941.     $this->subtitle->margin;
  1942.     }
  1943.     if( $this->subsubtitle->t != "" && !$this->subsubtitle->hide ) {
  1944. $h += $this->subsubtitle->GetTextHeight($this->img)+$margin+
  1945.     $this->subsubtitle->margin;
  1946.     }
  1947.     $this->img->PushColor($this->titlebackground_color);
  1948.     if( $this->titlebackground_style === 1 ) {
  1949. // Inside the frame
  1950. if( $this->framebevel ) {
  1951.     $x1 = $y1 = $this->framebeveldepth + 1 ;
  1952.     $x2 = $this->img->width - $this->framebeveldepth - 2 ; 
  1953.     $this->title->margin += $this->framebeveldepth + 1 ;
  1954.     $h += $y1 ;
  1955. }
  1956. else {
  1957.     $x1 = $y1 = $this->frame_weight;
  1958.     $x2 = $this->img->width - 2*$x1;
  1959. }
  1960.     }
  1961.     elseif( $this->titlebackground_style === 2 ) {
  1962. // Cover the frame as well
  1963. $x1 = $y1 = 0;
  1964. $x2 = $this->img->width - 1 ;
  1965.     }
  1966.     elseif( $this->titlebackground_style === 3) {
  1967. // Cover the frame as well (the difference is that
  1968. // for style==3 a bevel frame border is on top
  1969. // of the title background)
  1970. $x1 = $y1 = 0;
  1971. $x2 = $this->img->width - 1 ;
  1972. $h += $this->framebeveldepth ;
  1973. $this->title->margin += $this->framebeveldepth ;
  1974.     }
  1975.     else {
  1976. JpGraphError::Raise('Unknown title background style.');
  1977.     }
  1978.     if( $this->titlebackground_framestyle === 3 ) {
  1979. $h += $this->titlebackground_bevelheight*2 + 1  ;
  1980. $this->title->margin += $this->titlebackground_bevelheight ;
  1981.     }
  1982.     if( $this->doshadow ) {
  1983. $x2 -= $this->shadow_width ;
  1984.     }
  1985.     
  1986.     if( $this->titlebkg_fillstyle==TITLEBKG_FILLSTYLE_HSTRIPED ) {
  1987. $this->img->FilledRectangle2($x1,$y1,$x2,$h,
  1988.      $this->titlebkg_scolor1,
  1989.      $this->titlebkg_scolor2);
  1990.     }
  1991.     elseif( $this->titlebkg_fillstyle==TITLEBKG_FILLSTYLE_VSTRIPED ) {
  1992. $this->img->FilledRectangle2($x1,$y1,$x2,$h,
  1993.      $this->titlebkg_scolor1,
  1994.      $this->titlebkg_scolor2,2);
  1995.     }
  1996.     else {
  1997. // Solid fill
  1998. $this->img->FilledRectangle($x1,$y1,$x2,$h);
  1999.     }
  2000.     $this->img->PopColor();
  2001.     $this->img->PushColor($this->titlebackground_framecolor);
  2002.     $this->img->SetLineWeight($this->titlebackground_frameweight);
  2003.     if( $this->titlebackground_framestyle == 1 ) {
  2004. // Frame background
  2005. $this->img->Rectangle($x1,$y1,$x2,$h);
  2006.     }
  2007.     elseif( $this->titlebackground_framestyle == 2 ) {
  2008. // Bottom line only
  2009. $this->img->Line($x1,$h,$x2,$h);
  2010.     }
  2011.     elseif( $this->titlebackground_framestyle == 3 ) {
  2012. $this->img->Bevel($x1,$y1,$x2,$h,$this->titlebackground_bevelheight);
  2013.     }
  2014.     $this->img->PopColor();
  2015.     // This is clumsy. But we neeed to stroke the whole graph frame if it is
  2016.     // set to bevel to get the bevel shading on top of the text background
  2017.     if( $this->framebevel && $this->doframe && 
  2018. $this->titlebackground_style === 3 ) {
  2019. $this->img->Bevel(1,1,$this->img->width-2,$this->img->height-2,
  2020.   $this->framebeveldepth,
  2021.   $this->framebevelcolor1,$this->framebevelcolor2);
  2022. if( $this->framebevelborder ) {
  2023.     $this->img->SetColor($this->framebevelbordercolor);
  2024.     $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);
  2025. }
  2026.     }
  2027. }
  2028. // Stroke title
  2029. $y = $this->title->margin; 
  2030. $this->title->Center(0,$this->img->width,$y);
  2031. $this->title->Stroke($this->img);
  2032. // ... and subtitle
  2033. $y += $this->title->GetTextHeight($this->img) + $margin + $this->subtitle->margin;
  2034. $this->subtitle->Center(0,$this->img->width,$y);
  2035. $this->subtitle->Stroke($this->img);
  2036. // ... and subsubtitle
  2037. $y += $this->subtitle->GetTextHeight($this->img) + $margin + $this->subsubtitle->margin;
  2038. $this->subsubtitle->Center(0,$this->img->width,$y);
  2039. $this->subsubtitle->Stroke($this->img);
  2040. // ... and fancy title
  2041. $this->tabtitle->Stroke($this->img);
  2042.     }
  2043.     function StrokeTexts() {
  2044. // Stroke any user added text objects
  2045. if( $this->texts != null ) {
  2046.     for($i=0; $i<count($this->texts); ++$i) {
  2047. $this->texts[$i]->StrokeWithScale($this->img,$this->xscale,$this->yscale);
  2048.     }
  2049. }
  2050.     }
  2051.     function DisplayClientSideaImageMapAreas() {
  2052. // Debug stuff - display the outline of the image map areas
  2053. foreach ($this->plots as $p) {
  2054.     $csim.= $p->GetCSIMareas();
  2055. }
  2056. $csim .= $this->legend->GetCSIMareas();
  2057. if (preg_match_all("/area shape="(w+)" coords="([0-9, ]+)"/", $csim, $coords)) {
  2058.     $this->img->SetColor($this->csimcolor);
  2059.     for ($i=0; $i<count($coords[0]); $i++) {
  2060. if ($coords[1][$i]=="poly") {
  2061.     preg_match_all('/s*([0-9]+)s*,s*([0-9]+)s*,*/',$coords[2][$i],$pts);
  2062.     $this->img->SetStartPoint($pts[1][count($pts[0])-1],$pts[2][count($pts[0])-1]);
  2063.     for ($j=0; $j<count($pts[0]); $j++) {
  2064. $this->img->LineTo($pts[1][$j],$pts[2][$j]);
  2065.     }
  2066. } else if ($coords[1][$i]=="rect") {
  2067.     $pts = preg_split('/,/', $coords[2][$i]);
  2068.     $this->img->SetStartPoint($pts[0],$pts[1]);
  2069.     $this->img->LineTo($pts[2],$pts[1]);
  2070.     $this->img->LineTo($pts[2],$pts[3]);
  2071.     $this->img->LineTo($pts[0],$pts[3]);
  2072.     $this->img->LineTo($pts[0],$pts[1]);
  2073. }
  2074.     }
  2075. }
  2076.     }
  2077.     function AdjustSaturationBrightnessContrast() {
  2078. // Adjust the brightness and contrast of the image
  2079. if( $this->image_contr || $this->image_bright )
  2080.     $this->img->AdjBrightContrast($this->image_bright,$this->image_contr);
  2081. if( $this->image_sat )  
  2082.     $this->img->AdjSat($this->image_sat);
  2083.     }
  2084.     // Text scale offset in world coordinates
  2085.     function SetTextScaleOff($aOff) {
  2086. $this->text_scale_off = $aOff;
  2087. $this->xscale->text_scale_off = $aOff;
  2088.     }
  2089.     // Get Y min and max values for added lines
  2090.     function GetLinesYMinMax( $aLines ) {
  2091. $n = count($aLines);
  2092. if( $n == 0 ) return false;
  2093. $min = $aLines[0]->scaleposition ;
  2094. $max = $min ;
  2095. $flg = false;
  2096. for( $i=0; $i < $n; ++$i ) {
  2097.     if( $aLines[$i]->direction == HORIZONTAL ) {
  2098. $flg = true ;
  2099. $v = $aLines[$i]->scaleposition ;
  2100. if( $min > $v ) $min = $v ;
  2101. if( $max < $v ) $max = $v ;
  2102.     }
  2103. }
  2104. return $flg ? array($min,$max) : false ;
  2105.     }
  2106.     // Get X min and max values for added lines
  2107.     function GetLinesXMinMax( $aLines ) {
  2108. $n = count($aLines);
  2109. if( $n == 0 ) return false ;
  2110. $min = $aLines[0]->scaleposition ;
  2111. $max = $min ;
  2112. $flg = false;
  2113. for( $i=0; $i < $n; ++$i ) {
  2114.     if( $aLines[$i]->direction == VERTICAL ) {
  2115. $flg = true ;
  2116. $v = $aLines[$i]->scaleposition ;
  2117. if( $min > $v ) $min = $v ;
  2118. if( $max < $v ) $max = $v ;
  2119.     }
  2120. }
  2121. return $flg ? array($min,$max) : false ;
  2122.     }
  2123.     // Get min and max values for all included plots
  2124.     function GetPlotsYMinMax(&$aPlots) {
  2125. list($xmax,$max) = $aPlots[0]->Max();
  2126. list($xmin,$min) = $aPlots[0]->Min();
  2127. for($i=0; $i<count($aPlots); ++$i ) {
  2128.     list($xmax,$ymax)=$aPlots[$i]->Max();
  2129.     list($xmin,$ymin)=$aPlots[$i]->Min();
  2130.     if (!is_string($ymax) || $ymax != "") $max=max($max,$ymax);
  2131.     if (!is_string($ymin) || $ymin != "") $min=min($min,$ymin);
  2132. }
  2133. if( $min == "" ) $min = 0;
  2134. if( $max == "" ) $max = 0;
  2135. if( $min == 0 && $max == 0 ) {
  2136.     // Special case if all values are 0
  2137.     $min=0;$max=1;
  2138. }
  2139. return array($min,$max);
  2140.     }
  2141. } // Class
  2142. //===================================================
  2143. // CLASS TTF
  2144. // Description: Handle TTF font names
  2145. //===================================================
  2146. class TTF {
  2147.     var $font_files,$style_names;
  2148. //---------------
  2149. // CONSTRUCTOR
  2150.     function TTF() {
  2151. $this->style_names=array(FS_NORMAL=>'normal',FS_BOLD=>'bold',FS_ITALIC=>'italic',FS_BOLDITALIC=>'bolditalic');
  2152. // File names for available fonts
  2153. $this->font_files=array(
  2154.     FF_COURIER => array(FS_NORMAL=>'cour.ttf', FS_BOLD=>'courbd.ttf', FS_ITALIC=>'couri.ttf', FS_BOLDITALIC=>'courbi.ttf' ),
  2155.     FF_GEORGIA => array(FS_NORMAL=>'georgia.ttf', FS_BOLD=>'georgiab.ttf', FS_ITALIC=>'georgiai.ttf', FS_BOLDITALIC=>'' ),
  2156.     FF_TREBUCHE =>array(FS_NORMAL=>'trebuc.ttf', FS_BOLD=>'trebucbd.ttf',   FS_ITALIC=>'trebucit.ttf', FS_BOLDITALIC=>'trebucbi.ttf' ),
  2157.     FF_VERDANA => array(FS_NORMAL=>'verdana.ttf', FS_BOLD=>'verdanab.ttf',  FS_ITALIC=>'verdanai.ttf', FS_BOLDITALIC=>'' ),
  2158.     FF_TIMES =>   array(FS_NORMAL=>'times.ttf',   FS_BOLD=>'timesbd.ttf',   FS_ITALIC=>'timesi.ttf',   FS_BOLDITALIC=>'timesbi.ttf' ),
  2159.     FF_COMIC =>   array(FS_NORMAL=>'comic.ttf',   FS_BOLD=>'comicbd.ttf',   FS_ITALIC=>'.ttf',         FS_BOLDITALIC=>'' ),
  2160.     FF_ARIAL =>   array(FS_NORMAL=>'arial.ttf',   FS_BOLD=>'arialbd.ttf',   FS_ITALIC=>'ariali.ttf',   FS_BOLDITALIC=>'arialbi.ttf' ) ,
  2161.     FF_SIMSUN =>  array(FS_NORMAL=>'simsun.ttc',  FS_BOLD=>'simhei.ttf',   FS_ITALIC=>'',   FS_BOLDITALIC=>'' ),
  2162.     FF_VERA =>    array(FS_NORMAL=>'Vera.ttf',   FS_BOLD=>'VeraBd.ttf',   FS_ITALIC=>'VeraIt.ttf',   FS_BOLDITALIC=>'VeraBI.ttf' ),
  2163.     FF_VERAMONO => array(FS_NORMAL=>'VeraMono.ttf', FS_BOLD=>'VeraMoBd.ttf', FS_ITALIC=>'VeraMoIt.ttf', FS_BOLDITALIC=>'VeraMoBI.ttf' ),
  2164.     FF_VERASERIF => array(FS_NORMAL=>'VeraSe.ttf', FS_BOLD=>'VeraSeBd.ttf', FS_ITALIC=>'', FS_BOLDITALIC=>'' )    
  2165. );
  2166.     }
  2167. //---------------
  2168. // PUBLIC METHODS
  2169.     // Create the TTF file from the font specification
  2170.     function File($family,$style=FS_NORMAL) {
  2171. if( $family == FF_HANDWRT || $family==FF_BOOK ) {
  2172.     JpGraphError::Raise('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.');
  2173. }
  2174. $fam = @$this->font_files[$family];
  2175. if( !$fam ) {
  2176.     JpGraphError::Raise(
  2177.     "Specified TTF font family (id=$family) is unknown or does not exist. ".
  2178.     "Please note that TTF fonts are not distributed with JpGraph for copyright reasons.". 
  2179.     " You can find the MS TTF WEB-fonts");
  2180. }
  2181. $f = @$fam[$style];
  2182. if( $f==='' )
  2183.     JpGraphError::Raise('Style "'.$this->style_names[$style].'" is not available for font family '.$this->font_files[$family][FS_NORMAL].'.');
  2184. if( !$f )
  2185.     JpGraphError::Raise("Unknown font style specification [$fam].");
  2186. $f = TTF_DIR.$f;
  2187. if( file_exists($f) === false || is_readable($f) === false ) {
  2188.     JpGraphError::Raise("Font file "$f" is not readable or does not exist.");
  2189. }
  2190. return $f;
  2191.     }
  2192. } // Class
  2193. //===================================================
  2194. // CLASS LineProperty
  2195. // Description: Holds properties for a line
  2196. //===================================================
  2197. class LineProperty {
  2198.     var $iWeight=1, $iColor="black",$iStyle="solid";
  2199.     var $iShow=true;
  2200. //---------------
  2201. // PUBLIC METHODS
  2202.     function SetColor($aColor) {
  2203. $this->iColor = $aColor;
  2204.     }
  2205.     function SetWeight($aWeight) {
  2206. $this->iWeight = $aWeight;
  2207.     }
  2208.     function SetStyle($aStyle) {
  2209. $this->iStyle = $aStyle;
  2210.     }
  2211.     function Show($aShow=true) {
  2212. $this->iShow=$aShow;
  2213.     }
  2214.     function Stroke($aImg,$aX1,$aY1,$aX2,$aY2) {
  2215. if( $this->iShow ) {
  2216.     $aImg->SetColor($this->iColor);
  2217.     $aImg->SetLineWeight($this->iWeight);
  2218.     $aImg->SetLineStyle($this->iStyle);
  2219.     $aImg->StyleLine($aX1,$aY1,$aX2,$aY2);
  2220. }
  2221.     }
  2222. }
  2223. //===================================================
  2224. // CLASS Text
  2225. // Description: Arbitrary text object that can be added to the graph
  2226. //===================================================
  2227. class Text {
  2228.     var $t,$x=0,$y=0,$halign="left",$valign="top",$color=array(0,0,0);
  2229.     var $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12;
  2230.     var $hide=false, $dir=0;
  2231.     var $boxed=false; // Should the text be boxed
  2232.     var $paragraph_align="left";
  2233.     var $margin;
  2234.     var $icornerradius=0,$ishadowwidth=3;
  2235.     var $iScalePosY=null,$iScalePosX=null;
  2236. //---------------
  2237. // CONSTRUCTOR
  2238.     // Create new text at absolute pixel coordinates
  2239.     function Text($aTxt="",$aXAbsPos=0,$aYAbsPos=0) {
  2240. $this->t = $aTxt;
  2241. $this->x = round($aXAbsPos);
  2242. $this->y = round($aYAbsPos);
  2243. $this->margin = 0;
  2244.     }
  2245. //---------------
  2246. // PUBLIC METHODS
  2247.     // Set the string in the text object
  2248.     function Set($aTxt) {
  2249. $this->t = $aTxt;
  2250.     }
  2251.     // Alias for Pos()
  2252.     function SetPos($aXAbsPos=0,$aYAbsPos=0,$aHAlign="left",$aVAlign="top") {
  2253. $this->Pos($aXAbsPos,$aYAbsPos,$aHAlign,$aVAlign);
  2254.     }
  2255.     
  2256.     // Specify the position and alignment for the text object
  2257.     function Pos($aXAbsPos=0,$aYAbsPos=0,$aHAlign="left",$aVAlign="top") {
  2258. $this->x = $aXAbsPos;
  2259. $this->y = $aYAbsPos;
  2260. $this->halign = $aHAlign;
  2261. $this->valign = $aVAlign;
  2262.     }
  2263.     function SetScalePos($aX,$aY) {
  2264. $this->iScalePosX = $aX;
  2265. $this->iScalePosY = $aY;
  2266.     }
  2267.     // Specify alignment for the text
  2268.     function Align($aHAlign,$aVAlign="top",$aParagraphAlign="") {
  2269. $this->halign = $aHAlign;
  2270. $this->valign = $aVAlign;
  2271. if( $aParagraphAlign != "" )
  2272.     $this->paragraph_align = $aParagraphAlign;
  2273.     }
  2274.     
  2275.     // Alias
  2276.     function SetAlign($aHAlign,$aVAlign="top",$aParagraphAlign="") {
  2277. $this->Align($aHAlign,$aVAlign,$aParagraphAlign);
  2278.     }
  2279.     // Specifies the alignment for a multi line text
  2280.     function ParagraphAlign($aAlign) {
  2281. $this->paragraph_align = $aAlign;
  2282.     }
  2283.     function SetShadow($aShadowColor='gray',$aShadowWidth=3) {
  2284. $this->ishadowwidth=$aShadowWidth;
  2285. $this->shadow=$aShadowColor;
  2286. $this->boxed=true;
  2287.     }
  2288.     // Specify that the text should be boxed. fcolor=frame color, bcolor=border color,
  2289.     // $shadow=drop shadow should be added around the text.
  2290.     function SetBox($aFrameColor=array(255,255,255),$aBorderColor=array(0,0,0),$aShadowColor=false,$aCornerRadius=4,$aShadowWidth=3) {
  2291. if( $aFrameColor==false )
  2292.     $this->boxed=false;
  2293. else
  2294.     $this->boxed=true;
  2295. $this->fcolor=$aFrameColor;
  2296. $this->bcolor=$aBorderColor;
  2297. // For backwards compatibility when shadow was just true or false
  2298. if( $aShadowColor === true )
  2299.     $aShadowColor = 'gray';
  2300. $this->shadow=$aShadowColor;
  2301. $this->icornerradius=$aCornerRadius;
  2302. $this->ishadowwidth=$aShadowWidth;
  2303.     }
  2304.     // Hide the text
  2305.     function Hide($aHide=true) {
  2306. $this->hide=$aHide;
  2307.     }
  2308.     // This looks ugly since it's not a very orthogonal design 
  2309.     // but I added this "inverse" of Hide() to harmonize
  2310.     // with some classes which I designed more recently (especially) 
  2311.     // jpgraph_gantt
  2312.     function Show($aShow=true) {
  2313. $this->hide=!$aShow;
  2314.     }
  2315.     // Specify font
  2316.     function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
  2317. $this->font_family=$aFamily;
  2318. $this->font_style=$aStyle;
  2319. $this->font_size=$aSize;
  2320.     }
  2321.     // Center the text between $left and $right coordinates
  2322.     function Center($aLeft,$aRight,$aYAbsPos=false) {
  2323. $this->x = $aLeft + ($aRight-$aLeft )/2;
  2324. $this->halign = "center";
  2325. if( is_numeric($aYAbsPos) )
  2326.     $this->y = $aYAbsPos;
  2327.     }
  2328.     // Set text color
  2329.     function SetColor($aColor) {
  2330. $this->color = $aColor;
  2331.     }
  2332.     function SetAngle($aAngle) {
  2333. $this->SetOrientation($aAngle);
  2334.     }
  2335.     // Orientation of text. Note only TTF fonts can have an arbitrary angle
  2336.     function SetOrientation($aDirection=0) {
  2337. if( is_numeric($aDirection) )
  2338.     $this->dir=$aDirection;
  2339. elseif( $aDirection=="h" )
  2340.     $this->dir = 0;
  2341. elseif( $aDirection=="v" )
  2342.     $this->dir = 90;
  2343. else JpGraphError::Raise(" Invalid direction specified for text.");
  2344.     }
  2345.     // Total width of text
  2346.     function GetWidth($aImg) {
  2347. $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  2348. $w = $aImg->GetTextWidth($this->t,$this->dir);
  2349. return $w;
  2350.     }
  2351.     // Hight of font
  2352.     function GetFontHeight($aImg) {
  2353. $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  2354. $h = $aImg->GetFontHeight();
  2355. return $h;
  2356.     }
  2357.     function GetTextHeight($aImg) {
  2358. $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  2359. $h = $aImg->GetTextHeight($this->t,$this->dir);
  2360. return $h;
  2361.     }
  2362.     // Set the margin which will be interpretated differently depending
  2363.     // on the context.
  2364.     function SetMargin($aMarg) {
  2365. $this->margin = $aMarg;
  2366.     }
  2367.     function StrokeWithScale($aImg,$axscale,$ayscale) {
  2368. if( $this->iScalePosX === null ||
  2369.     $this->iScalePosY === null ) {
  2370.     $this->Stroke($aImg);
  2371. }
  2372. else {
  2373.     $this->Stroke($aImg,
  2374.   round($axscale->Translate($this->iScalePosX)),
  2375.   round($ayscale->Translate($this->iScalePosY)));
  2376. }
  2377.     }
  2378.     // Display text in image
  2379.     function Stroke($aImg,$x=null,$y=null) {
  2380. if( !empty($x) ) $this->x = round($x);
  2381. if( !empty($y) ) $this->y = round($y);
  2382. // If position been given as a fraction of the image size
  2383. // calculate the absolute position
  2384. if( $this->x < 1 && $this->x > 0 ) $this->x *= $aImg->width;
  2385. if( $this->y < 1 && $this->y > 0 ) $this->y *= $aImg->height;
  2386. $aImg->PushColor($this->color);
  2387. $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  2388. $aImg->SetTextAlign($this->halign,$this->valign);
  2389. if( $this->boxed ) {
  2390.     if( $this->fcolor=="nofill" ) 
  2391. $this->fcolor=false;
  2392.     $aImg->SetLineWeight(1);
  2393.     $aImg->StrokeBoxedText($this->x,$this->y,$this->t,
  2394.    $this->dir,$this->fcolor,$this->bcolor,$this->shadow,
  2395.    $this->paragraph_align,6,2,$this->icornerradius,
  2396.    $this->ishadowwidth);
  2397. }
  2398. else {
  2399.     $aImg->StrokeText($this->x,$this->y,$this->t,$this->dir,
  2400.       $this->paragraph_align);
  2401. }
  2402. $aImg->PopColor($this->color);
  2403.     }
  2404. } // Class
  2405. class GraphTabTitle extends Text{
  2406.     var $corner = 6 , $posx = 7, $posy = 4;
  2407.     var $color='darkred',$fillcolor='lightyellow',$bordercolor='black';
  2408.     var $align = 'left', $width=TABTITLE_WIDTHFIT;
  2409.     function GraphTabTitle() {
  2410. $this->t = '';
  2411. $this->font_style = FS_BOLD;
  2412. $this->hide = true;
  2413.     }
  2414.     function SetColor($aTxtColor,$aFillColor='lightyellow',$aBorderColor='black') {
  2415. $this->color = $aTxtColor;
  2416. $this->fillcolor = $aFillColor;
  2417. $this->bordercolor = $aBorderColor;
  2418.     }
  2419.     function SetFillColor($aFillColor) {
  2420. $this->fillcolor = $aFillColor;
  2421.     }
  2422.     function SetTabAlign($aAlign) {
  2423. // Synonym for SetPos
  2424. $this->align = $aAlign;
  2425.     }
  2426.     function SetPos($aAlign) {
  2427. $this->align = $aAlign;
  2428.     }
  2429.     
  2430.     function SetWidth($aWidth) {
  2431. $this->width = $aWidth ;
  2432.     }
  2433.     function Set($t) {
  2434. $this->t = $t;
  2435. $this->hide = false;
  2436.     }
  2437.     function SetCorner($aD) {
  2438. $this->corner = $aD ;
  2439.     }
  2440.     function Stroke($aImg) {
  2441. if( $this->hide ) 
  2442.     return;
  2443. $this->boxed = false;
  2444. $w = $this->GetWidth($aImg) + 2*$this->posx;
  2445. $h = $this->GetTextHeight($aImg) + 2*$this->posy;
  2446. $x = $aImg->left_margin;
  2447. $y = $aImg->top_margin;
  2448. if( $this->width === TABTITLE_WIDTHFIT ) {
  2449.     if( $this->align == 'left' ) {
  2450. $p = array($x,                $y,
  2451.    $x,                $y-$h+$this->corner,
  2452.    $x + $this->corner,$y-$h,
  2453.    $x + $w - $this->corner, $y-$h,
  2454.    $x + $w, $y-$h+$this->corner,
  2455.    $x + $w, $y);
  2456.     }
  2457.     elseif( $this->align == 'center' ) {
  2458. $x += round($aImg->plotwidth/2) - round($w/2);
  2459. $p = array($x, $y,
  2460.    $x, $y-$h+$this->corner,
  2461.    $x + $this->corner, $y-$h,
  2462.    $x + $w - $this->corner, $y-$h,
  2463.    $x + $w, $y-$h+$this->corner,
  2464.    $x + $w, $y);
  2465.     }
  2466.     else {
  2467. $x += $aImg->plotwidth -$w;
  2468. $p = array($x, $y,
  2469.    $x, $y-$h+$this->corner,
  2470.    $x + $this->corner,$y-$h,
  2471.    $x + $w - $this->corner, $y-$h,
  2472.    $x + $w, $y-$h+$this->corner,
  2473.    $x + $w, $y);
  2474.     }
  2475. }
  2476. else {
  2477.     if( $this->width === TABTITLE_WIDTHFULL )
  2478. $w = $aImg->plotwidth ;
  2479.     else
  2480. $w = $this->width ;
  2481.     // Make the tab fit the width of the plot area
  2482.     $p = array($x,                $y,
  2483.        $x,                $y-$h+$this->corner,
  2484.        $x + $this->corner,$y-$h,
  2485.        $x + $w - $this->corner, $y-$h,
  2486.        $x + $w, $y-$h+$this->corner,
  2487.        $x + $w, $y);
  2488.     
  2489. }
  2490. $aImg->SetTextAlign('left','bottom');
  2491. $x += $this->posx;
  2492. $y -= $this->posy;
  2493. $aImg->SetColor($this->fillcolor);
  2494. $aImg->FilledPolygon($p);
  2495. $aImg->SetColor($this->bordercolor);
  2496. $aImg->Polygon($p,true);
  2497. $aImg->SetColor($this->color);
  2498. $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  2499. $aImg->StrokeText($x,$y,$this->t,0,'center');
  2500.     }
  2501. }
  2502. //===================================================
  2503. // CLASS SuperScriptText
  2504. // Description: Format a superscript text
  2505. //===================================================
  2506. class SuperScriptText extends Text {
  2507.     var $iSuper="";
  2508.     var $sfont_family="",$sfont_style="",$sfont_size=8;
  2509.     var $iSuperMargin=2,$iVertOverlap=4,$iSuperScale=0.65;
  2510.     var $iSDir=0;
  2511.     var $iSimple=false;
  2512.     function SuperScriptText($aTxt="",$aSuper="",$aXAbsPos=0,$aYAbsPos=0) {
  2513. parent::Text($aTxt,$aXAbsPos,$aYAbsPos);
  2514. $this->iSuper = $aSuper;
  2515.     }
  2516.     function FromReal($aVal,$aPrecision=2) {
  2517. // Convert a floating point number to scientific notation
  2518. $neg=1.0;
  2519. if( $aVal < 0 ) {
  2520.     $neg = -1.0;
  2521.     $aVal = -$aVal;
  2522. }
  2523. $l = floor(log10($aVal));
  2524. $a = sprintf("%0.".$aPrecision."f",round($aVal / pow(10,$l),$aPrecision));
  2525. $a *= $neg;
  2526. if( $this->iSimple && ($a == 1 || $a==-1) ) $a = '';
  2527. if( $a != '' )
  2528.     $this->t = $a.' * 10';
  2529. else {
  2530.     if( $neg == 1 )
  2531. $this->t = '10';
  2532.     else
  2533. $this->t = '-10';
  2534. }
  2535. $this->iSuper = $l;
  2536.     }
  2537.     function Set($aTxt,$aSuper="") {
  2538. $this->t = $aTxt;
  2539. $this->iSuper = $aSuper;
  2540.     }
  2541.     function SetSuperFont($aFontFam,$aFontStyle=FS_NORMAL,$aFontSize=8) {
  2542. $this->sfont_family = $aFontFam;
  2543. $this->sfont_style = $aFontStyle;
  2544. $this->sfont_size = $aFontSize;
  2545.     }
  2546.     // Total width of text
  2547.     function GetWidth(&$aImg) {
  2548. $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  2549. $w = $aImg->GetTextWidth($this->t);
  2550. $aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size);
  2551. $w += $aImg->GetTextWidth($this->iSuper);
  2552. $w += $this->iSuperMargin;
  2553. return $w;
  2554.     }
  2555.     // Hight of font (approximate the height of the text)
  2556.     function GetFontHeight(&$aImg) {
  2557. $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  2558. $h = $aImg->GetFontHeight();
  2559. $aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size);
  2560. $h += $aImg->GetFontHeight();
  2561. return $h;
  2562.     }
  2563.     // Hight of text
  2564.     function GetTextHeight(&$aImg) {
  2565. $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  2566. $h = $aImg->GetTextHeight($this->t);
  2567. $aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size);
  2568. $h += $aImg->GetTextHeight($this->iSuper);
  2569. return $h;
  2570.     }
  2571.     function Stroke($aImg,$ax=-1,$ay=-1) {
  2572.         // To position the super script correctly we need different
  2573. // cases to handle the alignmewnt specified since that will
  2574. // determine how we can interpret the x,y coordinates
  2575. $w = parent::GetWidth($aImg);
  2576. $h = parent::GetTextHeight($aImg);
  2577. switch( $this->valign ) {
  2578.     case 'top':
  2579. $sy = $this->y;
  2580. break;
  2581.     case 'center':
  2582. $sy = $this->y - $h/2;
  2583. break;
  2584.     case 'bottom':
  2585. $sy = $this->y - $h;
  2586. break;
  2587.     default:
  2588. JpGraphError::Raise('PANIC: Internal error in SuperScript::Stroke(). Unknown vertical alignment for text');
  2589. exit();
  2590. }
  2591. switch( $this->halign ) {
  2592.     case 'left':
  2593. $sx = $this->x + $w;
  2594. break;
  2595.     case 'center':
  2596. $sx = $this->x + $w/2;
  2597. break;
  2598.     case 'right':
  2599. $sx = $this->x;
  2600. break;
  2601.     default:
  2602. JpGraphError::Raise('PANIC: Internal error in SuperScript::Stroke(). Unknown horizontal alignment for text');
  2603. exit();
  2604. }
  2605. $sx += $this->iSuperMargin;
  2606. $sy += $this->iVertOverlap;
  2607. // Should we automatically determine the font or
  2608. // has the user specified it explicetly?
  2609. if( $this->sfont_family == "" ) {
  2610.     if( $this->font_family <= FF_FONT2 ) {
  2611. if( $this->font_family == FF_FONT0 ) {
  2612.     $sff = FF_FONT0;
  2613. }
  2614. elseif( $this->font_family == FF_FONT1 ) {
  2615.     if( $this->font_style == FS_NORMAL )
  2616. $sff = FF_FONT0;
  2617.     else
  2618. $sff = FF_FONT1;
  2619. }
  2620. else {
  2621.     $sff = FF_FONT1;
  2622. }
  2623. $sfs = $this->font_style;
  2624. $sfz = $this->font_size;
  2625.     }
  2626.     else {
  2627. // TTF fonts
  2628. $sff = $this->font_family;
  2629. $sfs = $this->font_style;
  2630. $sfz = floor($this->font_size*$this->iSuperScale);
  2631. if( $sfz < 8 ) $sfz = 8;
  2632.     }     
  2633.     $this->sfont_family = $sff;
  2634.     $this->sfont_style = $sfs;
  2635.     $this->sfont_size = $sfz;     
  2636. else {
  2637.     $sff = $this->sfont_family;
  2638.     $sfs = $this->sfont_style;
  2639.     $sfz = $this->sfont_size;     
  2640. }
  2641. parent::Stroke($aImg,$ax,$ay);
  2642. // For the builtin fonts we need to reduce the margins
  2643. // since the bounding bx reported for the builtin fonts
  2644. // are much larger than for the TTF fonts.
  2645. if( $sff <= FF_FONT2 ) {
  2646.     $sx -= 2;
  2647.     $sy += 3;
  2648. }
  2649. $aImg->SetTextAlign('left','bottom');
  2650. $aImg->SetFont($sff,$sfs,$sfz);
  2651. $aImg->PushColor($this->color);
  2652. $aImg->StrokeText($sx,$sy,$this->iSuper,$this->iSDir,'left');
  2653. $aImg->PopColor();
  2654.     }
  2655. }
  2656. //===================================================
  2657. // CLASS Grid
  2658. // Description: responsible for drawing grid lines in graph
  2659. //===================================================
  2660. class Grid {
  2661.     var $img;
  2662.     var $scale;
  2663.     var $grid_color='#DDDDDD',$grid_mincolor='#DDDDDD';
  2664.     var $type="solid";
  2665.     var $show=false, $showMinor=false,$weight=1;
  2666.     var $fill=false,$fillcolor=array('#EFEFEF','#BBCCFF');
  2667. //---------------
  2668. // CONSTRUCTOR
  2669.     function Grid(&$aAxis) {
  2670. $this->scale = &$aAxis->scale;
  2671. $this->img = &$aAxis->img;
  2672.     }
  2673. //---------------
  2674. // PUBLIC METHODS
  2675.     function SetColor($aMajColor,$aMinColor=false) {
  2676. $this->grid_color=$aMajColor;
  2677. if( $aMinColor === false ) 
  2678.     $aMinColor = $aMajColor ;
  2679. $this->grid_mincolor = $aMinColor;
  2680.     }
  2681.     function SetWeight($aWeight) {
  2682. $this->weight=$aWeight;
  2683.     }
  2684.     // Specify if grid should be dashed, dotted or solid
  2685.     function SetLineStyle($aType) {
  2686. $this->type = $aType;
  2687.     }
  2688.     // Decide if both major and minor grid should be displayed
  2689.     function Show($aShowMajor=true,$aShowMinor=false) {
  2690. $this->show=$aShowMajor;
  2691. $this->showMinor=$aShowMinor;
  2692.     }
  2693.     
  2694.     function SetFill($aFlg=true,$aColor1='lightgray',$aColor2='lightblue') {
  2695. $this->fill = $aFlg;
  2696. $this->fillcolor = array( $aColor1, $aColor2 );
  2697.     }
  2698.     // Display the grid
  2699.     function Stroke() {
  2700. if( $this->showMinor ) {
  2701.     $tmp = $this->grid_color;
  2702.     $this->grid_color = $this->grid_mincolor;
  2703.     $this->DoStroke($this->scale->ticks->ticks_pos);
  2704.     $this->grid_color = $tmp;
  2705.     $this->DoStroke($this->scale->ticks->maj_ticks_pos);
  2706. }
  2707. else {
  2708.     $this->DoStroke($this->scale->ticks->maj_ticks_pos);
  2709. }
  2710.     }
  2711. //--------------
  2712. // Private methods
  2713.     // Draw the grid
  2714.     function DoStroke(&$aTicksPos) {
  2715. if( !$this->show )
  2716.     return;
  2717. $nbrgrids = count($aTicksPos);
  2718. if( $this->scale->type=="y" ) {
  2719.     $xl=$this->img->left_margin;
  2720.     $xr=$this->img->width-$this->img->right_margin;
  2721.     
  2722.     if( $this->fill ) {
  2723. // Draw filled areas
  2724. $y2 = $aTicksPos[0];
  2725. $i=1;
  2726. while( $i < $nbrgrids ) {
  2727.     $y1 = $y2;
  2728.     $y2 = $aTicksPos[$i++];
  2729.     $this->img->SetColor($this->fillcolor[$i & 1]);
  2730.     $this->img->FilledRectangle($xl,$y1,$xr,$y2);
  2731. }
  2732.     }
  2733.     $this->img->SetColor($this->grid_color);
  2734.     $this->img->SetLineWeight($this->weight);
  2735.     // Draw grid lines
  2736.     for($i=0; $i<$nbrgrids; ++$i) {
  2737. $y=$aTicksPos[$i];
  2738. if( $this->type == "solid" )
  2739.     $this->img->Line($xl,$y,$xr,$y);
  2740. elseif( $this->type == "dotted" )
  2741.     $this->img->DashedLine($xl,$y,$xr,$y,1,6);
  2742. elseif( $this->type == "dashed" )
  2743.     $this->img->DashedLine($xl,$y,$xr,$y,2,4);
  2744. elseif( $this->type == "longdashed" )
  2745.     $this->img->DashedLine($xl,$y,$xr,$y,8,6);
  2746.     }
  2747. }
  2748. elseif( $this->scale->type=="x" ) {
  2749.     $yu=$this->img->top_margin;
  2750.     $yl=$this->img->height-$this->img->bottom_margin;
  2751.     $limit=$this->img->width-$this->img->right_margin;
  2752.     if( $this->fill ) {
  2753. // Draw filled areas
  2754. $x2 = $aTicksPos[0];
  2755. $i=1;
  2756. while( $i < $nbrgrids ) {
  2757.     $x1 = $x2;
  2758.     $x2 = min($aTicksPos[$i++],$limit) ;
  2759.     $this->img->SetColor($this->fillcolor[$i & 1]);
  2760.     $this->img->FilledRectangle($x1,$yu,$x2,$yl);
  2761. }
  2762.     }
  2763.     $this->img->SetColor($this->grid_color);
  2764.     $this->img->SetLineWeight($this->weight);
  2765.     // We must also test for limit since we might have
  2766.     // an offset and the number of ticks is calculated with
  2767.     // assumption offset==0 so we might end up drawing one
  2768.     // to many gridlines
  2769.     $i=0;
  2770.     $x=$aTicksPos[$i];     
  2771.     while( $i<count($aTicksPos) && ($x=$aTicksPos[$i]) <= $limit ) {
  2772. if( $this->type == "solid" )
  2773.     $this->img->Line($x,$yl,$x,$yu);
  2774. elseif( $this->type == "dotted" )
  2775.     $this->img->DashedLine($x,$yl,$x,$yu,1,6);
  2776. elseif( $this->type == "dashed" )
  2777.     $this->img->DashedLine($x,$yl,$x,$yu,2,4);
  2778. elseif( $this->type == "longdashed" )
  2779.     $this->img->DashedLine($x,$yl,$x,$yu,8,6);
  2780. ++$i;  
  2781.     }
  2782. }
  2783. else {
  2784.     JpGraphError::Raise('Internal error: Unknown grid axis ['.$this->scale->type.']');
  2785. }
  2786. return true;
  2787.     }
  2788. } // Class
  2789. //===================================================
  2790. // CLASS Axis
  2791. // Description: Defines X and Y axis. Notes that at the
  2792. // moment the code is not really good since the axis on
  2793. // several occasion must know wheter it's an X or Y axis.
  2794. // This was a design decision to make the code easier to
  2795. // follow. 
  2796. //===================================================
  2797. class Axis {
  2798.     var $pos = false;
  2799.     var $weight=1;
  2800.     var $color=array(0,0,0),$label_color=array(0,0,0);
  2801.     var $img=null,$scale=null; 
  2802.     var $hide=false;
  2803.     var $ticks_label=false, $ticks_label_colors=null;
  2804.     var $show_first_label=true,$show_last_label=true;
  2805.     var $label_step=1; // Used by a text axis to specify what multiple of major steps
  2806.     // should be labeled.
  2807.     var $tick_step=1;
  2808.     var $labelPos=0;   // Which side of the axis should the labels be?
  2809.     var $title=null,$title_adjust,$title_margin,$title_side=SIDE_LEFT;
  2810.     var $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12,$label_angle=0;
  2811.     var $tick_label_margin=5;
  2812.     var $label_halign = '',$label_valign = '', $label_para_align='left';
  2813.     var $hide_line=false,$hide_labels=false;
  2814.     //var $hide_zero_label=false;
  2815. //---------------
  2816. // CONSTRUCTOR
  2817.     function Axis(&$img,&$aScale,$color=array(0,0,0)) {
  2818. $this->img = &$img;
  2819. $this->scale = &$aScale;
  2820. $this->color = $color;
  2821. $this->title=new Text("");
  2822. if( $aScale->type=="y" ) {
  2823.     $this->title_margin = 25;
  2824.     $this->title_adjust="middle";
  2825.     $this->title->SetOrientation(90);
  2826.     $this->tick_label_margin=7;
  2827.     $this->labelPos=SIDE_LEFT;
  2828.     //$this->SetLabelFormat('%.1f');
  2829. }
  2830. else {
  2831.     $this->title_margin = 5;
  2832.     $this->title_adjust="high";
  2833.     $this->title->SetOrientation(0);
  2834.     $this->tick_label_margin=3;
  2835.     $this->labelPos=SIDE_DOWN;
  2836.     //$this->SetLabelFormat('%.0f');
  2837. }
  2838.     }
  2839. //---------------
  2840. // PUBLIC METHODS
  2841.     function SetLabelFormat($aFormStr) {
  2842. $this->scale->ticks->SetLabelFormat($aFormStr);
  2843.     }
  2844.     function SetLabelFormatString($aFormStr) {
  2845. $this->scale->ticks->SetLabelFormat($aFormStr);
  2846.     }
  2847.     function SetLabelFormatCallback($aFuncName) {
  2848. $this->scale->ticks->SetFormatCallback($aFuncName);
  2849.     }
  2850.     function SetLabelAlign($aHAlign,$aVAlign="top",$aParagraphAlign='left') {
  2851. $this->label_halign = $aHAlign;
  2852. $this->label_valign = $aVAlign;
  2853. $this->label_para_align = $aParagraphAlign;
  2854.     }
  2855.     // Don't display the first label
  2856.     function HideFirstTickLabel($aShow=false) {
  2857. $this->show_first_label=$aShow;
  2858.     }
  2859.     function HideLastTickLabel($aShow=false) {
  2860. $this->show_last_label=$aShow;
  2861.     }
  2862.     function HideTicks($aHideMinor=true,$aHideMajor=true) {
  2863. $this->scale->ticks->SupressMinorTickMarks($aHideMinor);
  2864. $this->scale->ticks->SupressTickMarks($aHideMajor);
  2865.     }
  2866.     // Hide zero label
  2867.     function HideZeroLabel($aFlag=true) {
  2868. $this->scale->ticks->SupressZeroLabel();
  2869. //$this->hide_zero_label = $aFlag;
  2870.     }
  2871.     function HideFirstLastLabel() {
  2872. // The two first calls to ticks method will supress 
  2873. // automatically generated scale values. However, that
  2874. // will not affect manually specified value, e.g text-scales.
  2875. // therefor we also make a kludge here to supress manually
  2876. // specified scale labels.
  2877. $this->scale->ticks->SupressLast();
  2878. $this->scale->ticks->SupressFirst();
  2879. $this->show_first_label = false;
  2880. $this->show_last_label = false;
  2881.     }
  2882.     // Hide the axis
  2883.     function Hide($aHide=true) {
  2884. $this->hide=$aHide;
  2885.     }
  2886.     // Hide the actual axis-line, but still print the labels
  2887.     function HideLine($aHide=true) {
  2888. $this->hide_line = $aHide;
  2889.     }
  2890.     function HideLabels($aHide=true) {
  2891. $this->hide_labels = $aHide;
  2892.     }
  2893.     
  2894.     // Weight of axis
  2895.     function SetWeight($aWeight) {
  2896. $this->weight = $aWeight;
  2897.     }
  2898.     // Axis color
  2899.     function SetColor($aColor,$aLabelColor=false) {
  2900. $this->color = $aColor;
  2901. if( !$aLabelColor ) $this->label_color = $aColor;
  2902. else $this->label_color = $aLabelColor;
  2903.     }
  2904.     // Title on axis
  2905.     function SetTitle($aTitle,$aAdjustAlign="high") {
  2906. $this->title->Set($aTitle);
  2907. $this->title_adjust=$aAdjustAlign;
  2908.     }
  2909.     // Specify distance from the axis
  2910.     function SetTitleMargin($aMargin) {
  2911. $this->title_margin=$aMargin;
  2912.     }
  2913.     // Which side of the axis should the axis title be?
  2914.     function SetTitleSide($aSideOfAxis) {
  2915. $this->title_side = $aSideOfAxis;
  2916.     }
  2917.     // Utility function to set the direction for tick marks
  2918.     function SetTickDirection($aDir) {
  2919.      // Will be deprecated from 1.7    
  2920.      if( ERR_DEPRECATED )
  2921.     JpGraphError::Raise('Axis::SetTickDirection() is deprecated. Use Axis::SetTickSide() instead');
  2922. $this->scale->ticks->SetSide($aDir);
  2923.     }
  2924.     
  2925.     function SetTickSide($aDir) {
  2926. $this->scale->ticks->SetSide($aDir);
  2927.     }
  2928.     // Specify text labels for the ticks. One label for each data point
  2929.     function SetTickLabels($aLabelArray,$aLabelColorArray=null) {
  2930. $this->ticks_label = $aLabelArray;
  2931. $this->ticks_label_colors = $aLabelColorArray;
  2932.     }
  2933.     // How far from the axis should the labels be drawn
  2934.     function SetTickLabelMargin($aMargin) {
  2935. if( ERR_DEPRECATED )    
  2936.     JpGraphError::Raise('SetTickLabelMargin() is deprecated. Use Axis::SetLabelMargin() instead.');
  2937.        $this->tick_label_margin=$aMargin;
  2938.     }
  2939.     function SetLabelMargin($aMargin) {
  2940. $this->tick_label_margin=$aMargin;
  2941.     }
  2942.     // Specify that every $step of the ticks should be displayed starting
  2943.     // at $start
  2944.     // DEPRECATED FUNCTION: USE SetTextTickInterval() INSTEAD
  2945.     function SetTextTicks($step,$start=0) {
  2946. JpGraphError::Raise(" SetTextTicks() is deprecated. Use SetTextTickInterval() instead.");
  2947.     }
  2948.     // Specify that every $step of the ticks should be displayed starting
  2949.     // at $start
  2950.     function SetTextTickInterval($aStep,$aStart=0) {
  2951. $this->scale->ticks->SetTextLabelStart($aStart);
  2952. $this->tick_step=$aStep;
  2953.     }
  2954.  
  2955.     // Specify that every $step tick mark should have a label 
  2956.     // should be displayed starting
  2957.     function SetTextLabelInterval($aStep) {
  2958. if( $aStep < 1 )
  2959.     JpGraphError::Raise(" Text label interval must be specified >= 1.");
  2960. $this->label_step=$aStep;
  2961.     }
  2962.     // Which side of the axis should the labels be on?
  2963.     function SetLabelPos($aSidePos) {
  2964.      // This will be deprecated from 1.7
  2965. if( ERR_DEPRECATED )    
  2966.     JpGraphError::Raise('SetLabelPos() is deprecated. Use Axis::SetLabelSide() instead.');
  2967. $this->labelPos=$aSidePos;
  2968.     }
  2969.     
  2970.     function SetLabelSide($aSidePos) {
  2971. $this->labelPos=$aSidePos;
  2972.     }
  2973.     // Set the font
  2974.     function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
  2975. $this->font_family = $aFamily;
  2976. $this->font_style = $aStyle;
  2977. $this->font_size = $aSize;
  2978.     }
  2979.     // Position for axis line on the "other" scale
  2980.     function SetPos($aPosOnOtherScale) {
  2981. $this->pos=$aPosOnOtherScale;
  2982.     }
  2983.     // Specify the angle for the tick labels
  2984.     function SetLabelAngle($aAngle) {
  2985. $this->label_angle = $aAngle;
  2986.     }
  2987.     // Stroke the axis.
  2988.     function Stroke($aOtherAxisScale) {
  2989. if( $this->hide ) return;
  2990. if( is_numeric($this->pos) ) {
  2991.     $pos=$aOtherAxisScale->Translate($this->pos);
  2992. }
  2993. else { // Default to minimum of other scale if pos not set
  2994.     if( ($aOtherAxisScale->GetMinVal() >= 0 && $this->pos==false) || $this->pos=="min" ) {
  2995. $pos = $aOtherAxisScale->scale_abs[0];
  2996.     }
  2997.     elseif($this->pos == "max") {
  2998. $pos = $aOtherAxisScale->scale_abs[1];
  2999.     }
  3000.     else { // If negative set x-axis at 0
  3001. $this->pos=0;
  3002. $pos=$aOtherAxisScale->Translate(0);
  3003.     }
  3004. }
  3005. $this->img->SetLineWeight($this->weight);
  3006. $this->img->SetColor($this->color);
  3007. $this->img->SetFont($this->font_family,$this->font_style,$this->font_size);
  3008. if( $this->scale->type == "x" ) {
  3009.     if( !$this->hide_line ) 
  3010. $this->img->FilledRectangle($this->img->left_margin,$pos,
  3011.     $this->img->width-$this->img->right_margin,$pos+$this->weight-1);
  3012.     $y=$pos+$this->img->GetFontHeight()+$this->title_margin+$this->title->margin;
  3013.     if( $this->title_adjust=="high" )
  3014. $this->title->Pos($this->img->width-$this->img->right_margin,$y,"right","top");
  3015.     elseif( $this->title_adjust=="middle" || $this->title_adjust=="center" ) 
  3016. $this->title->Pos(($this->img->width-$this->img->left_margin-$this->img->right_margin)/2+$this->img->left_margin,$y,"center","top");
  3017.     elseif($this->title_adjust=="low")
  3018. $this->title->Pos($this->img->left_margin,$y,"left","top");
  3019.     else {
  3020. JpGraphError::Raise('Unknown alignment specified for X-axis title. ('.$this->title_adjust.')');
  3021.     }
  3022. }
  3023. elseif( $this->scale->type == "y" ) {
  3024.     // Add line weight to the height of the axis since
  3025.     // the x-axis could have a width>1 and we want the axis to fit nicely together.
  3026.     if( !$this->hide_line ) 
  3027. $this->img->FilledRectangle($pos-$this->weight+1,$this->img->top_margin,
  3028.     $pos,$this->img->height-$this->img->bottom_margin+$this->weight-1);
  3029.     $x=$pos ;
  3030.     if( $this->title_side == SIDE_LEFT ) {
  3031. $x -= $this->title_margin;
  3032. $x -= $this->title->margin;
  3033. $halign="right";
  3034.     }
  3035.     else {
  3036. $x += $this->title_margin;
  3037. $x += $this->title->margin;
  3038. $halign="left";
  3039.     }
  3040.     // If the user has manually specified an hor. align
  3041.     // then we override the automatic settings with this
  3042.     // specifed setting. Since default is 'left' we compare
  3043.     // with that. (This means a manually set 'left' align
  3044.     // will have no effect.)
  3045.     if( $this->title->halign != 'left' ) 
  3046. $halign = $this->title->halign;
  3047.     if( $this->title_adjust=="high" ) 
  3048. $this->title->Pos($x,$this->img->top_margin,$halign,"top"); 
  3049.     elseif($this->title_adjust=="middle" || $this->title_adjust=="center")  
  3050. $this->title->Pos($x,($this->img->height-$this->img->top_margin-$this->img->bottom_margin)/2+$this->img->top_margin,$halign,"center");
  3051.     elseif($this->title_adjust=="low")
  3052. $this->title->Pos($x,$this->img->height-$this->img->bottom_margin,$halign,"bottom");
  3053.     else
  3054. JpGraphError::Raise('Unknown alignment specified for Y-axis title. ('.$this->title_adjust.')');
  3055. }
  3056. $this->scale->ticks->Stroke($this->img,$this->scale,$pos);
  3057. if( !$this->hide_labels ) {
  3058.     $this->StrokeLabels($pos);
  3059. }
  3060. $this->title->Stroke($this->img);
  3061.     }
  3062. //---------------
  3063. // PRIVATE METHODS
  3064.     // Draw all the tick labels on major tick marks
  3065.     function StrokeLabels($aPos,$aMinor=false,$aAbsLabel=false) {
  3066. $this->img->SetColor($this->label_color);
  3067. $this->img->SetFont($this->font_family,$this->font_style,$this->font_size);
  3068. $yoff=$this->img->GetFontHeight()/2;
  3069. // Only draw labels at major tick marks
  3070. $nbr = count($this->scale->ticks->maj_ticks_label);
  3071. // We have the option to not-display the very first mark
  3072. // (Usefull when the first label might interfere with another
  3073. // axis.)
  3074. $i = $this->show_first_label ? 0 : 1 ;
  3075. if( !$this->show_last_label ) --$nbr;
  3076. // Now run through all labels making sure we don't overshoot the end
  3077. // of the scale.
  3078. $ncolor=0;
  3079. if( isset($this->ticks_label_colors) )
  3080.     $ncolor=count($this->ticks_label_colors);
  3081. while( $i<$nbr ) {
  3082.     // $tpos holds the absolute text position for the label
  3083.     $tpos=$this->scale->ticks->maj_ticklabels_pos[$i];
  3084.     // Note. the $limit is only used for the x axis since we
  3085.     // might otherwise overshoot if the scale has been centered
  3086.     // This is due to us "loosing" the last tick mark if we center.
  3087.     if( $this->scale->type=="x" && $tpos > $this->img->width-$this->img->right_margin+1 ) {
  3088.      return; 
  3089.     }
  3090.     // we only draw every $label_step label
  3091.     if( ($i % $this->label_step)==0 ) {
  3092. // Set specific label color if specified
  3093. if( $ncolor > 0 )
  3094.     $this->img->SetColor($this->ticks_label_colors[$i % $ncolor]);
  3095. // If the label has been specified use that and in other case
  3096. // just label the mark with the actual scale value 
  3097. $m=$this->scale->ticks->GetMajor();
  3098. // ticks_label has an entry for each data point and is the array
  3099. // that holds the labels set by the user. If the user hasn't 
  3100. // specified any values we use whats in the automatically asigned
  3101. // labels in the maj_ticks_label
  3102. if( isset($this->ticks_label[$i*$m]) )
  3103.     $label=$this->ticks_label[$i*$m];
  3104. else {
  3105.     if( $aAbsLabel ) 
  3106. $label=abs($this->scale->ticks->maj_ticks_label[$i]);
  3107.     else
  3108. $label=$this->scale->ticks->maj_ticks_label[$i];
  3109.     if( $this->scale->textscale ) {
  3110. ++$label;
  3111.     }
  3112. }
  3113. //if( $this->hide_zero_label && $label==0.0 ) {
  3114. // ++$i;
  3115. // continue;
  3116. //}
  3117. if( $this->scale->type == "x" ) {
  3118.     if( $this->labelPos == SIDE_DOWN ) {
  3119. if( $this->label_angle==0 || $this->label_angle==90 ) {
  3120.     if( $this->label_halign=='' && $this->label_valign=='')
  3121. $this->img->SetTextAlign('center','top');
  3122.     else
  3123.      $this->img->SetTextAlign($this->label_halign,$this->label_valign);
  3124.     
  3125. }
  3126. else {
  3127.     if( $this->label_halign=='' && $this->label_valign=='')
  3128. $this->img->SetTextAlign("right","top");
  3129.     else
  3130. $this->img->SetTextAlign($this->label_halign,$this->label_valign);
  3131. }
  3132. $this->img->StrokeText($tpos,$aPos+$this->tick_label_margin,$label,
  3133.        $this->label_angle,$this->label_para_align);
  3134.     }
  3135.     else {
  3136. if( $this->label_angle==0 || $this->label_angle==90 ) {
  3137.     if( $this->label_halign=='' && $this->label_valign=='')
  3138. $this->img->SetTextAlign("center","bottom");
  3139.     else
  3140.      $this->img->SetTextAlign($this->label_halign,$this->label_valign);
  3141. }
  3142. else {
  3143.     if( $this->label_halign=='' && $this->label_valign=='')
  3144. $this->img->SetTextAlign("right","bottom");
  3145.     else
  3146.      $this->img->SetTextAlign($this->label_halign,$this->label_valign);
  3147. }
  3148. $this->img->StrokeText($tpos,$aPos-$this->tick_label_margin,$label,
  3149.        $this->label_angle,$this->label_para_align);
  3150.     }
  3151. }
  3152. else {
  3153.     // scale->type == "y"
  3154.     //if( $this->label_angle!=0 ) 
  3155.     //JpGraphError::Raise(" Labels at an angle are not supported on Y-axis");
  3156.     if( $this->labelPos == SIDE_LEFT ) { // To the left of y-axis
  3157. if( $this->label_halign=='' && $this->label_valign=='')
  3158.     $this->img->SetTextAlign("right","center");
  3159. else
  3160.     $this->img->SetTextAlign($this->label_halign,$this->label_valign);
  3161. $this->img->StrokeText($aPos-$this->tick_label_margin,$tpos,$label,$this->label_angle,$this->label_para_align);
  3162.     }
  3163.     else { // To the right of the y-axis
  3164. if( $this->label_halign=='' && $this->label_valign=='')
  3165.     $this->img->SetTextAlign("left","center");
  3166. else
  3167.     $this->img->SetTextAlign($this->label_halign,$this->label_valign);
  3168. $this->img->StrokeText($aPos+$this->tick_label_margin,$tpos,$label,$this->label_angle,$this->label_para_align);
  3169.     }
  3170. }
  3171.     }
  3172.     ++$i;
  3173. }
  3174.     }
  3175. } // Class
  3176. //===================================================
  3177. // CLASS Ticks
  3178. // Description: Abstract base class for drawing linear and logarithmic
  3179. // tick marks on axis
  3180. //===================================================
  3181. class Ticks {
  3182.     var $minor_abs_size=3, $major_abs_size=5;
  3183.     var $direction=1; // Should ticks be in(=1) the plot area or outside (=-1)?
  3184.     var $scale;
  3185.     var $is_set=false;
  3186.     var $precision;
  3187.     var $supress_zerolabel=false,$supress_first=false;
  3188.     var $supress_last=false,$supress_tickmarks=false,$supress_minor_tickmarks=false;
  3189.     var $mincolor="",$majcolor="";
  3190.     var $weight=1;
  3191.     var $label_formatstr='';   // C-style format string to use for labels
  3192.     var $label_formfunc='';
  3193. //---------------
  3194. // CONSTRUCTOR
  3195.     function Ticks(&$aScale) {
  3196. $this->scale=&$aScale;
  3197. $this->precision = -1;
  3198.     }
  3199. //---------------
  3200. // PUBLIC METHODS
  3201.     // Set format string for automatic labels
  3202.     function SetLabelFormat($aFormatString) {
  3203. $this->label_formatstr=$aFormatString;
  3204.     }
  3205.     function SetFormatCallback($aCallbackFuncName) {
  3206. $this->label_formfunc = $aCallbackFuncName;
  3207.     }
  3208.     // Don't display the first zero label
  3209.     function SupressZeroLabel($aFlag=true) {
  3210. $this->supress_zerolabel=$aFlag;
  3211.     }
  3212.     // Don't display minor tick marks
  3213.     function SupressMinorTickMarks($aHide=true) {
  3214. $this->supress_minor_tickmarks=$aHide;
  3215.     }
  3216.     // Don't display major tick marks
  3217.     function SupressTickMarks($aHide=true) {
  3218. $this->supress_tickmarks=$aHide;
  3219.     }
  3220.     // Hide the first tick mark
  3221.     function SupressFirst($aHide=true) {
  3222. $this->supress_first=$aHide;
  3223.     }
  3224.     // Hide the last tick mark
  3225.     function SupressLast($aHide=true) {
  3226. $this->supress_last=$aHide;
  3227.     }
  3228.     // Size (in pixels) of minor tick marks
  3229.     function GetMinTickAbsSize() {
  3230. return $this->minor_abs_size;
  3231.     }
  3232.     // Size (in pixels) of major tick marks
  3233.     function GetMajTickAbsSize() {
  3234. return $this->major_abs_size;
  3235.     }
  3236.     function SetSize($aMajSize,$aMinSize=3) {
  3237. $this->major_abs_size = $aMajSize;
  3238. $this->minor_abs_size = $aMinSize;
  3239.     }
  3240.     // Have the ticks been specified
  3241.     function IsSpecified() {
  3242. return $this->is_set;
  3243.     }
  3244.     // Set the distance between major and minor tick marks
  3245.     function Set($aMaj,$aMin) {
  3246. // "Virtual method"
  3247. // Should be implemented by the concrete subclass
  3248. // if any action is wanted.
  3249.     }
  3250.     // Specify number of decimals in automatic labels
  3251.     // Deprecated from 1.4. Use SetFormatString() instead
  3252.     function SetPrecision($aPrecision) { 
  3253.      if( ERR_DEPRECATED )
  3254.     JpGraphError::Raise('Ticks::SetPrecision() is deprecated. Use Ticks::SetLabelFormat() (or Ticks::SetFormatCallback()) instead');
  3255. $this->precision=$aPrecision;
  3256.     }
  3257.     function SetSide($aSide) {
  3258. $this->direction=$aSide;
  3259.     }
  3260.     // Which side of the axis should the ticks be on
  3261.     function SetDirection($aSide=SIDE_RIGHT) {
  3262. $this->direction=$aSide;
  3263.     }
  3264.     // Set colors for major and minor tick marks
  3265.     function SetMarkColor($aMajorColor,$aMinorColor="") {
  3266. $this->SetColor($aMajorColor,$aMinorColor);
  3267.     }
  3268.     
  3269.     function SetColor($aMajorColor,$aMinorColor="") {
  3270. $this->majcolor=$aMajorColor;
  3271. // If not specified use same as major
  3272. if( $aMinorColor=="" ) 
  3273.     $this->mincolor=$aMajorColor;
  3274. else
  3275.     $this->mincolor=$aMinorColor;
  3276.     }
  3277.     function SetWeight($aWeight) {
  3278. $this->weight=$aWeight;
  3279.     }
  3280. } // Class
  3281. //===================================================
  3282. // CLASS LinearTicks
  3283. // Description: Draw linear ticks on axis
  3284. //===================================================
  3285. class LinearTicks extends Ticks {
  3286.     var $minor_step=1, $major_step=2;
  3287.     var $xlabel_offset=0,$xtick_offset=0;
  3288.     var $label_offset=0; // What offset should the displayed label have
  3289.     // i.e should we display 0,1,2 or 1,2,3,4 or 2,3,4 etc
  3290.     var $text_label_start=0;
  3291. //---------------
  3292. // CONSTRUCTOR
  3293.     function LinearTicks() {
  3294. $this->precision = -1;
  3295.     }
  3296. //---------------
  3297. // PUBLIC METHODS
  3298.     // Return major step size in world coordinates
  3299.     function GetMajor() {
  3300. return $this->major_step;
  3301.     }
  3302.     // Return minor step size in world coordinates
  3303.     function GetMinor() {
  3304. return $this->minor_step;
  3305.     }
  3306.     // Set Minor and Major ticks (in world coordinates)
  3307.     function Set($aMajStep,$aMinStep=false) {
  3308. if( $aMinStep==false ) 
  3309.     $aMinStep=$aMajStep;
  3310.     
  3311. if( $aMajStep <= 0 || $aMinStep <= 0 ) {
  3312.     JpGraphError::Raise(" Minor or major step size is 0. Check that you haven't
  3313. got an accidental SetTextTicks(0) in your code.<p>
  3314. If this is not the case you might have stumbled upon a bug in JpGraph.
  3315. Please report this and if possible include the data that caused the
  3316. problem.");
  3317. }
  3318. $this->major_step=$aMajStep;
  3319. $this->minor_step=$aMinStep;
  3320. $this->is_set = true;
  3321.     }
  3322.     // Draw linear ticks
  3323.     function Stroke(&$img,&$scale,$pos) {
  3324. $maj_step_abs = $scale->scale_factor*$this->major_step;
  3325. $min_step_abs = $scale->scale_factor*$this->minor_step;
  3326. if( $min_step_abs==0 || $maj_step_abs==0 ) 
  3327.     JpGraphError::Raise(" 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. Try increasing the graph size or correct the lineplot.");
  3328. $limit = $scale->scale_abs[1];
  3329. $nbrmajticks=floor(1.000001*(($scale->GetMaxVal()-$scale->GetMinVal())/$this->major_step))+1;
  3330. $first=0;
  3331. // If precision hasn't been specified set it to a sensible value
  3332. if( $this->precision==-1 ) { 
  3333.     $t = log10($this->minor_step);
  3334.     if( $t > 0 )
  3335. $precision = 0;
  3336.     else
  3337. $precision = -floor($t);
  3338. }
  3339. else
  3340.     $precision = $this->precision;
  3341. $img->SetLineWeight($this->weight);
  3342. // Handle ticks on X-axis
  3343. if( $scale->type == "x" ) {
  3344.     // Draw the minor tick marks
  3345.     $yu = $pos - $this->direction*$this->GetMinTickAbsSize();
  3346.     $label = $scale->GetMinVal();     
  3347.     $x=$scale->scale_abs[0];
  3348.     $i=0;
  3349.     $j=0;
  3350.     $step = round($maj_step_abs/$min_step_abs);
  3351.     while( $x < $limit ) {
  3352. $this->ticks_pos[]=$x;
  3353. $this->ticks_label[]=$label;
  3354. $label+=$this->minor_step;
  3355.   if( !$this->supress_tickmarks && !$this->supress_minor_tickmarks) {
  3356.     if( $this->mincolor!="" ) $img->PushColor($this->mincolor);
  3357.     $img->Line($x,$pos,$x,$yu); 
  3358.     if( $this->mincolor!="" ) $img->PopColor();
  3359. }
  3360. if( $i % $step == 0 ) {
  3361.     $this->maj_ticks_pos[$j]=round($x);//$xtick;
  3362.     ++$j;
  3363. }
  3364. ++$i;
  3365. $x += $min_step_abs;
  3366.     }
  3367.     $this->maj_ticks_pos[$j]=$x;
  3368.     // Draw the major tick marks
  3369.     $yu = $pos - $this->direction*$this->GetMajTickAbsSize();
  3370.     // TODO: Add logic to set label_offset for text labels
  3371.     $label = (float)$scale->GetMinVal()+$this->text_label_start+$this->label_offset;
  3372.     $start_abs=$scale->scale_factor*$this->text_label_start;
  3373.     $nbrmajticks=ceil(($scale->GetMaxVal()-$scale->GetMinVal()-$this->text_label_start )/$this->major_step)+1;
  3374.     
  3375.     
  3376.     $x = $scale->scale_abs[0]+$start_abs+$this->xlabel_offset*$min_step_abs;
  3377.     for( $i=0; $label<=$scale->GetMaxVal()+$this->label_offset; ++$i ) {
  3378. // Apply format
  3379. if( $this->label_formfunc != "" ) {
  3380.     $f=$this->label_formfunc;
  3381.     $l = $f($label);
  3382. }
  3383. elseif( $this->label_formatstr != "" ) 
  3384.     $l = sprintf($this->label_formatstr,$label);
  3385. else {
  3386.     $v = round($label,$precision);
  3387.     $l = sprintf("%01.".$precision."f",$v);
  3388. }
  3389. if( ($this->supress_zerolabel && $l==0) || 
  3390.     ($this->supress_first && $i==0) ||
  3391.     ($this->supress_last  && $i==$nbrmajticks-1) ) {
  3392.     $l="";
  3393. }
  3394. $this->maj_ticks_label[$i]=$l;
  3395. $label+=$this->major_step;
  3396. $this->maj_ticklabels_pos[$i] = $x;
  3397. // $this->maj_ticklabels_pos[$i] = $this->maj_ticks_pos[$i];
  3398. // The x-position of the tick marks can be different from the labels.
  3399. // Note that we record the tick position (not the label) so that the grid
  3400. // happen upon tick marks and not labels.
  3401. $xtick=$scale->scale_abs[0]+$start_abs+$this->xtick_offset*$min_step_abs+$i*$maj_step_abs;
  3402. $this->maj_ticks_pos[$i]=$xtick;
  3403. if(!($this->xtick_offset > 0 && $i==$nbrmajticks-1) && 
  3404.    !$this->supress_tickmarks) {
  3405.     if( $this->majcolor!="" ) $img->PushColor($this->majcolor);
  3406.     $img->Line($this->maj_ticks_pos[$i],$pos,$this->maj_ticks_pos[$i],$yu);
  3407.     if( $this->majcolor!="" ) $img->PopColor();
  3408. }
  3409. $x += $maj_step_abs;
  3410.     }
  3411. }
  3412. elseif( $scale->type == "y" ) {
  3413.     // Draw the major tick marks
  3414.     $xr = $pos + $this->direction*$this->GetMajTickAbsSize();
  3415.     $label = $scale->GetMinVal();
  3416.     
  3417.     $tmpmaj=array();
  3418.     $tmpmin=array();
  3419.     for( $i=0; $i<$nbrmajticks; ++$i) {
  3420. $y=$scale->scale_abs[0]+$i*$maj_step_abs;
  3421. $tmpmaj[]=$y;
  3422. // THe following two lines might seem to be unecessary but they are not!
  3423. // The reason being that for X-axis we separate the position of the labels
  3424. // and the tick marks which we don't do for the Y-axis.
  3425. // We therefore need to make sure both arrays are corcectly filled
  3426. // since Axis::StrokeLabels() uses the label positions and Grid::Stroke() uses
  3427. // the tick positions.
  3428. $this->maj_ticklabels_pos[$i]=$y;
  3429. $this->maj_ticks_pos[$i]=$y;
  3430. if( $this->label_formfunc != "" ) {
  3431.     $f=$this->label_formfunc;
  3432.     $l = $f($label);
  3433. }
  3434. elseif( $this->label_formatstr != "" ) 
  3435.     $l = sprintf($this->label_formatstr,$label);
  3436. else
  3437.     $l = sprintf("%01.".$precision."f",round($label,$precision));
  3438. if( ($this->supress_zerolabel && ($l + 0)==0) ||  ($this->supress_first && $i==0) ||
  3439.     ($this->supress_last  && $i==$nbrmajticks-1) ) {
  3440.     $l="";
  3441. }
  3442. $this->maj_ticks_label[$i]=$l; 
  3443. $label+=$this->major_step;
  3444. if( !$this->supress_tickmarks ) {
  3445.     if( $this->majcolor!="" ) $img->PushColor($this->majcolor);
  3446.     $img->Line($pos,$y,$xr,$y);
  3447.     if( $this->majcolor!="" ) $img->PopColor();
  3448. }
  3449.     }
  3450.     // Draw the minor tick marks
  3451.     $xr = $pos + $this->direction*$this->GetMinTickAbsSize();
  3452.     $label = $scale->GetMinVal();
  3453.     for( $i=0,$y=$scale->scale_abs[0]; $y>=$limit; ) {
  3454. $tmpmin[]=$y;
  3455. $this->ticks_pos[$i]=$y;
  3456. $this->ticks_label[$i]=$label;
  3457. $label+=$this->minor_step;
  3458. if( !$this->supress_tickmarks && !$this->supress_minor_tickmarks) {
  3459.     if( $this->mincolor!="" ) $img->PushColor($this->mincolor);
  3460.     $img->Line($pos,$y,$xr,$y);
  3461.     if( $this->mincolor!="" ) $img->PopColor();
  3462. }
  3463. ++$i;
  3464. $y=$scale->scale_abs[0]+$i*$min_step_abs;
  3465.     }
  3466. }
  3467.     }
  3468. //---------------
  3469. // PRIVATE METHODS
  3470.     // Spoecify the offset of the displayed tick mark with the tick "space"
  3471.     // Legal values for $o is [0,1] used to adjust where the tick marks and label 
  3472.     // should be positioned within the major tick-size
  3473.     // $lo specifies the label offset and $to specifies the tick offset
  3474.     // this comes in handy for example in bar graphs where we wont no offset for the
  3475.     // tick but have the labels displayed halfway under the bars.
  3476.     function SetXLabelOffset($aLabelOff,$aTickOff=-1) {
  3477. $this->xlabel_offset=$aLabelOff;
  3478. if( $aTickOff==-1 ) // Same as label offset
  3479.     $this->xtick_offset=$aLabelOff;
  3480. else
  3481.     $this->xtick_offset=$aTickOff;
  3482. if( $aLabelOff>0 )
  3483.     $this->SupressLast(); // The last tick wont fit
  3484.     }
  3485.     // Which tick label should we start with?
  3486.     function SetTextLabelStart($aTextLabelOff) {
  3487. $this->text_label_start=$aTextLabelOff;
  3488.     }
  3489. } // Class
  3490. //===================================================
  3491. // CLASS LinearScale
  3492. // Description: Handle linear scaling between screen and world 
  3493. //===================================================
  3494. class LinearScale {
  3495.     var $scale=array(0,0);
  3496.     var $scale_abs=array(0,0);
  3497.     var $scale_factor; // Scale factor between world and screen
  3498.     var $world_size; // Plot area size in world coordinates
  3499.     var $world_abs_size; // Plot area size in pixels
  3500.     var $off; // Offset between image edge and plot area
  3501.     var $type; // is this x or y scale ?
  3502.     var $ticks=null; // Store ticks
  3503.     var $autoscale_min=false; // Forced minimum value, auto determine max
  3504.     var $autoscale_max=false; // Forced maximum value, auto determine min
  3505.     var $gracetop=0,$gracebottom=0;
  3506.     var $intscale=false; // Restrict autoscale to integers
  3507.     var $textscale=false; // Just a flag to let the Plot class find out if
  3508.     // we are a textscale or not. This is a cludge since
  3509.     // this ionformatyion is availabale in Graph::axtype but
  3510.     // we don't have access to the graph object in the Plots
  3511.     // stroke method. So we let graph store the status here
  3512.     // when the linear scale is created. A real cludge...
  3513.     var $text_scale_off = 0;
  3514.     var $auto_ticks=false; // When using manual scale should the ticks be automatically set?
  3515.     var $name = 'lin';
  3516. //---------------
  3517. // CONSTRUCTOR
  3518.     function LinearScale($aMin=0,$aMax=0,$aType="y") {
  3519. assert($aType=="x" || $aType=="y" );
  3520. assert($aMin<=$aMax);
  3521. $this->type=$aType;
  3522. $this->scale=array($aMin,$aMax);
  3523. $this->world_size=$aMax-$aMin;
  3524. $this->ticks = new LinearTicks();
  3525.     }
  3526. //---------------
  3527. // PUBLIC METHODS
  3528.     // Second phase constructor
  3529.     function Init(&$aImg) {
  3530. $this->InitConstants($aImg);
  3531. // We want image to notify us when the margins changes so we 
  3532. // can recalculate the constants.
  3533. // PHP <= 4.04 BUGWARNING: IT IS IMPOSSIBLE TO DO THIS IN THE CONSTRUCTOR
  3534. // SINCE (FOR SOME REASON) IT IS IMPOSSIBLE TO PASS A REFERENCE
  3535. // TO 'this' INSTEAD IT WILL ADD AN ANONYMOUS COPY OF THIS OBJECT WHICH WILL
  3536. // GET ALL THE NOTIFICATIONS. (This took a while to track down...)
  3537. // Add us as an observer to class Image
  3538. $aImg->AddObserver("InitConstants",$this);
  3539.     }
  3540.     // Check if scale is set or if we should autoscale
  3541.     // We should do this is either scale or ticks has not been set
  3542.     function IsSpecified() {
  3543. if( $this->GetMinVal()==$this->GetMaxVal() ) { // Scale not set
  3544.     return false;
  3545. }
  3546. return true;
  3547.     }
  3548.     // Set the minimum data value when the autoscaling is used. 
  3549.     // Usefull if you want a fix minimum (like 0) but have an
  3550.     // automatic maximum
  3551.     function SetAutoMin($aMin) {
  3552. $this->autoscale_min=$aMin;
  3553.     }
  3554.     // Set the minimum data value when the autoscaling is used. 
  3555.     // Usefull if you want a fix minimum (like 0) but have an
  3556.     // automatic maximum
  3557.     function SetAutoMax($aMax) {