home *** CD-ROM | disk | FTP | other *** search
/ Chip 2004 April / CMCD0404.ISO / Software / Freeware / Programare / dotproject / lib / jpgraph / src / jpgraph.php < prev   
Encoding:
PHP Script  |  2004-01-29  |  218.9 KB  |  7,113 lines

  1. <?php 
  2. //=======================================================================
  3. // File:    JPGRAPH.PHP
  4. // Description:    PHP4 Graph Plotting library. Base module.
  5. // Created:     2001-01-08
  6. // Author:    Johan Persson (johanp@aditus.nu)
  7. // Ver:        $Id: jpgraph.php,v 1.6 2004/01/29 06:13:02 ajdonnison Exp $
  8. //
  9. // License:    This code is released under QPL 1.0 
  10. // Copyright (C) 2001,2002 Johan Persson 
  11. //========================================================================
  12.  
  13. //------------------------------------------------------------------------
  14. // Directories. Must be updated to reflect your installation
  15. //------------------------------------------------------------------------
  16.  
  17. // The full absolute name of the directory to be used to store the
  18. // cached image files. This directory will not be used if the USE_CACHE
  19. // define (furter down) is false. If you enable the cache please note that
  20. // this directory MUST be readable and writable for the process running PHP. 
  21. // Must end with '/'
  22. DEFINE("CACHE_DIR","/tmp/jpgraph_cache/");
  23.  
  24. // Directory for jpGraph TTF fonts. Must end with '/'
  25. // Note: The fonts must follow the naming conventions as
  26. // used by the supplied TTF fonts in JpGraph.
  27. DEFINE("TTF_DIR","/usr/X11R6/lib/X11/fonts/truetype/");
  28.  
  29. // Cache directory specification for use with CSIM graphs that are
  30. // using the cache.
  31. // The directory must be the filesysystem name as seen by PHP
  32. // and the 'http' version must be the same directory but as 
  33. // seen by the HTTP server relative to the 'htdocs' ddirectory. 
  34. // If a relative path is specified it is take to be relative from where
  35. // the image script is executed.
  36. // Note: The default setting is to create a subdirectory in the 
  37. // directory from where the image script is executed and store all files
  38. // there. As ususal this directory must be writeable by PHP.
  39. DEFINE("CSIMCACHE_DIR","csimcache/"); 
  40. DEFINE("CSIMCACHE_HTTP_DIR","csimcache/");
  41.  
  42. //------------------------------------------------------------------------
  43. // Various JpGraph Settings. PLEASE adjust accordingly to you
  44. // system setup. Note that cache functionlity is turned off by
  45. // default (Enable by setting USE_CACHE to true)
  46. //------------------------------------------------------------------------
  47.  
  48. // Should the image be a truecolor image? 
  49. // Note 1: Can only be used with GD 2.0.1 and above.
  50. // Note 2: GD 2.0.1 + PHP 4.0.6 on Win32 crashes when trying to use 
  51. // trucolor. Truecolor support is to be considered alpha since GD 2.x
  52. // is still not considered stable (especially on Win32). 
  53. // Note 3: MUST be enabled to get background images working with GD2
  54. // Note 4: If enabled then truetype fonts will look very ugly
  55. // => You can't have both background images and truetype fonts in the same
  56. // image until these bugs has been fixed in GD 2.01  
  57. DEFINE('USE_TRUECOLOR',true);
  58.  
  59. // Specify what version of the GD library is installed.
  60. // If this is set to 'auto' the version will be automatically 
  61. // determined.
  62. // However since determining the library takes ~1ms you can also 
  63. // manually specify the version if you know what version you have. 
  64. // This means that you should 
  65. // set this define to true if you have GD 2.x installed to save 1ms. 
  66. DEFINE("USE_LIBRARY_GD2",'auto');
  67.  
  68. // Should the cache be used at all? By setting this to false no
  69. // files will be generated in the cache directory.  
  70. // The difference from READ_CACHE being that setting READ_CACHE to
  71. // false will still create the image in the cache directory
  72. // just not use it. By setting USE_CACHE=false no files will even
  73. // be generated in the cache directory.
  74. DEFINE("USE_CACHE",false);
  75.  
  76. // Should we try to find an image in the cache before generating it? 
  77. // Set this define to false to bypass the reading of the cache and always
  78. // regenerate the image. Note that even if reading the cache is 
  79. // disabled the cached will still be updated with the newly generated
  80. // image. Set also "USE_CACHE" below.
  81. DEFINE("READ_CACHE",true);
  82.  
  83. // Deafult graphic format set to "auto" which will automatically
  84. // choose the best available format in the order png,gif,jpg
  85. // (The supported format depends on what your PHP installation supports)
  86. DEFINE("DEFAULT_GFORMAT","auto");
  87.  
  88. // Determine if the error handler should be image based or purely
  89. // text based. Image based makes it easier since the script will
  90. // always return an image even in case of errors.
  91. DEFINE("USE_IMAGE_ERROR_HANDLER",true);
  92.  
  93. // If the color palette is full should JpGraph try to allocate
  94. // the closest match? If you plan on using background image or
  95. // gradient fills it might be a good idea to enable this.
  96. // If not you will otherwise get an error saying that the color palette is 
  97. // exhausted. The drawback of using approximations is that the colors 
  98. // might not be exactly what you specified. 
  99. // Note1: This does only apply to paletted images, not truecolor 
  100. // images since they don't have the limitations of maximum number
  101. // of colors.
  102. DEFINE("USE_APPROX_COLORS",true);
  103.  
  104. // Special unicode language support
  105. DEFINE("LANGUAGE_CYRILLIC",false);
  106.  
  107. // If you are setting this config to true the conversion
  108. // will assume that the input text is windows 1251, if
  109. // false it will assume koi8-r
  110. DEFINE("CYRILLIC_FROM_WINDOWS",false);
  111.  
  112. // Should usage of deprecated functions and parameters give a fatal error?
  113. // (Useful to check if code is future proof.)
  114. DEFINE("ERR_DEPRECATED",false);
  115.  
  116. // Should the time taken to generate each picture be branded to the lower
  117. // left in corner in each generated image? Useful for performace measurements
  118. // generating graphs
  119. DEFINE("BRAND_TIMING",false);
  120.  
  121. // What format should be used for the timing string?
  122. DEFINE("BRAND_TIME_FORMAT","Generated in: %01.3fs");
  123.  
  124. //------------------------------------------------------------------------
  125. // The following constants should rarely have to be changed !
  126. //------------------------------------------------------------------------
  127.  
  128. // What group should the cached file belong to
  129. // (Set to "" will give the default group for the "PHP-user")
  130. // Please note that the Apache user must be a member of the
  131. // specified group since otherwise it is impossible for Apache
  132. // to set the specified group.
  133. DEFINE("CACHE_FILE_GROUP","wwwadmin");
  134.  
  135. // What permissions should the cached file have
  136. // (Set to "" will give the default persmissions for the "PHP-user")
  137. DEFINE("CACHE_FILE_MOD",0664);
  138.  
  139. // Decide if we should use the bresenham circle algorithm or the
  140. // built in Arc(). Bresenham gives better visual apperance of circles 
  141. // but is more CPU intensive and slower then the built in Arc() function
  142. // in GD. Turned off by default for speed
  143. DEFINE("USE_BRESENHAM",false);
  144.  
  145. // Enable some extra debug information for CSIM etc to be shown. 
  146. // (Should only be changed if your first name is Johan and you
  147. // happen to know what you are doing!!)
  148. DEFINE("JPG_DEBUG",false);
  149.  
  150. // Special file name to indicate that we only want to calc
  151. // the image map in the call to Graph::Stroke() used
  152. // internally from the GetHTMLCSIM() method.
  153. DEFINE("_CSIM_SPECIALFILE","_csim_special_");
  154.  
  155. // HTTP GET argument that is used with image map
  156. // to indicate to the script to just generate the image
  157. // and not the full CSIM HTML page.
  158. DEFINE("_CSIM_DISPLAY","_jpg_csimd");
  159.  
  160. // Special filename for Graph::Stroke(). If this filename is given
  161. // then the image will NOT be streamed to browser of file. Instead the
  162. // Stroke call will return the handler for the created GD image.
  163. DEFINE("_IMG_HANDLER","__handle");
  164.  
  165. //------------------------------------------------------------------
  166. // Constants which are used as parameters for the method calls
  167. //------------------------------------------------------------------
  168.  
  169. // TTF Font families
  170. DEFINE("FF_COURIER",10);
  171. DEFINE("FF_VERDANA",11);
  172. DEFINE("FF_TIMES",12);
  173. DEFINE("FF_COMIC",14);
  174. DEFINE("FF_ARIAL",15);
  175. DEFINE("FF_GEORGIA",16);
  176. DEFINE("FF_TREBUCHE",17);
  177. // 
  178. DEFINE("FF_BOOK",91);    // Deprecated fonts from 1.9
  179. DEFINE("FF_HANDWRT",92); // Deprecated fonts from 1.9
  180.  
  181. // TTF Font styles
  182. DEFINE("FS_NORMAL",1);
  183. DEFINE("FS_BOLD",2);
  184. DEFINE("FS_ITALIC",3);
  185. DEFINE("FS_BOLDIT",4);
  186. DEFINE("FS_BOLDITALIC",4);
  187.  
  188. //Definitions for internal font, new style
  189. DEFINE("FF_FONT0",1);
  190. DEFINE("FF_FONT1",2);
  191. DEFINE("FF_FONT2",4);
  192.  
  193. //Definitions for internal font, old style
  194. // (Only defined here to be able to generate an error mesage
  195. // when used)
  196. DEFINE("FONT0",99);        // Deprecated from 1.2
  197. DEFINE("FONT1",98);        // Deprecated from 1.2
  198. DEFINE("FONT1_BOLD",97);    // Deprecated from 1.2
  199. DEFINE("FONT2",96);        // Deprecated from 1.2
  200. DEFINE("FONT2_BOLD",95);     // Deprecated from 1.2
  201.  
  202. // Tick density
  203. DEFINE("TICKD_DENSE",1);
  204. DEFINE("TICKD_NORMAL",2);
  205. DEFINE("TICKD_SPARSE",3);
  206. DEFINE("TICKD_VERYSPARSE",4);
  207.  
  208. // Side for ticks and labels. 
  209. DEFINE("SIDE_LEFT",-1);
  210. DEFINE("SIDE_RIGHT",1);
  211. DEFINE("SIDE_DOWN",-1);
  212. DEFINE("SIDE_UP",1);
  213.  
  214. // Legend type stacked vertical or horizontal
  215. DEFINE("LEGEND_VERT",0);
  216. DEFINE("LEGEND_HOR",1);
  217.  
  218. // Mark types for plot marks
  219. DEFINE("MARK_SQUARE",1);
  220. DEFINE("MARK_UTRIANGLE",2);
  221. DEFINE("MARK_DTRIANGLE",3);
  222. DEFINE("MARK_DIAMOND",4);
  223. DEFINE("MARK_CIRCLE",5);
  224. DEFINE("MARK_FILLEDCIRCLE",6);
  225. DEFINE("MARK_CROSS",7);
  226. DEFINE("MARK_STAR",8);
  227. DEFINE("MARK_X",9);
  228. DEFINE("MARK_LEFTTRIANGLE",10);
  229. DEFINE("MARK_RIGHTTRIANGLE",11);
  230. DEFINE("MARK_FLASH",12);
  231.  
  232.  
  233. // Styles for gradient color fill
  234. DEFINE("GRAD_VER",1);
  235. DEFINE("GRAD_HOR",2);
  236. DEFINE("GRAD_MIDHOR",3);
  237. DEFINE("GRAD_MIDVER",4);
  238. DEFINE("GRAD_CENTER",5);
  239. DEFINE("GRAD_WIDE_MIDVER",6);
  240. DEFINE("GRAD_WIDE_MIDHOR",7);
  241.  
  242. // Inline defines
  243. DEFINE("INLINE_YES",1);
  244. DEFINE("INLINE_NO",0);
  245.  
  246. // Format for background images
  247. DEFINE("BGIMG_FILLPLOT",1);
  248. DEFINE("BGIMG_FILLFRAME",2);
  249. DEFINE("BGIMG_COPY",3);
  250. DEFINE("BGIMG_CENTER",4);
  251.  
  252. // Depth of objects
  253. DEFINE("DEPTH_BACK",0);
  254. DEFINE("DEPTH_FRONT",1);
  255.  
  256. // Direction
  257. DEFINE("VERTICAL",1);
  258. DEFINE("HORIZONTAL",0);
  259.  
  260. // Constants for types of static bands in plot area
  261. DEFINE("BAND_RDIAG",1);    // Right diagonal lines
  262. DEFINE("BAND_LDIAG",2); // Left diagonal lines
  263. DEFINE("BAND_SOLID",3); // Solid one color
  264. DEFINE("BAND_VLINE",4); // Vertical lines
  265. DEFINE("BAND_HLINE",5);  // Horizontal lines
  266. DEFINE("BAND_3DPLANE",6);  // "3D" Plane
  267. DEFINE("BAND_HVCROSS",7);  // Vertical/Hor crosses
  268. DEFINE("BAND_DIAGCROSS",8); // Diagonal crosses
  269.  
  270. // Axis styles for scientific style axis
  271. DEFINE('AXSTYLE_SIMPLE',1);
  272. DEFINE('AXSTYLE_BOXIN',2);
  273. DEFINE('AXSTYLE_BOXOUT',3);
  274.  
  275. //
  276. // First of all set up a default error handler
  277. //
  278.  
  279. //=============================================================
  280. // The default trivial text error handler.
  281. //=============================================================
  282. class JpGraphErrObject {
  283.     function JpGraphErrObject() {
  284.     // Empty. Reserved for future use
  285.     }
  286.  
  287.     // If aHalt is true then execution can't continue. Typical used for
  288.     // fatal errors
  289.     function Raise($aMsg,$aHalt=true) {
  290.     $aMsg = "<b>JpGraph Error:</b> ".$aMsg;
  291.     if( $aHalt )
  292.         die($aMsg);
  293.     else 
  294.         echo $aMsg."<p>";
  295.     }
  296. }
  297.  
  298. //==============================================================
  299. // An image based error handler
  300. //==============================================================
  301. class JpGraphErrObjectImg {
  302.  
  303.     function Raise($aMsg,$aHalt=true) {
  304.     if( headers_sent() ) {
  305.         // Special case for headers already sent error. Dont
  306.         // return an image since it can't be displayed
  307.         die("<b>JpGraph Error:</b> ".$aMsg);        
  308.     }
  309.  
  310.     // Create an image that contains the error text.
  311.     $w=450; $h=110;
  312.     $img = new Image($w,$h);
  313.     $img->SetColor("darkred");
  314.     $img->Rectangle(0,0,$w-1,$h-1);
  315.     $img->SetFont(FF_FONT1,FS_BOLD);
  316.     $img->StrokeText(10,20,"JpGraph Error:");
  317.     $img->SetColor("black");
  318.     $img->SetFont(FF_FONT1,FS_NORMAL);
  319.     $txt = new Text(wordwrap($aMsg,70),10,20);
  320.     $txt->Align("left","top");
  321.     $txt->Stroke($img);
  322.     $img->Headers();
  323.     $img->Stream();
  324.     die();
  325.     }
  326. }
  327.  
  328. //
  329. // A wrapper class that is used to access the specified error object
  330. // (to hide the global error parameter and avoid having a GLOBAL directive
  331. // in all methods.
  332. //
  333. class JpGraphError {
  334.     function Install($aErrObject) {
  335.     GLOBAL $__jpg_err;
  336.     $__jpg_err = $aErrObject;
  337.     }
  338.     function Raise($aMsg,$aHalt=true){
  339.     GLOBAL $__jpg_err;
  340.     $tmp = new $__jpg_err;
  341.     $tmp->Raise($aMsg,$aHalt);
  342.     }
  343. }
  344.  
  345. //
  346. // ... and install the default error handler
  347. //
  348. if( USE_IMAGE_ERROR_HANDLER ) {
  349.     JpGraphError::Install("JpGraphErrObjectImg");
  350. }
  351. else {
  352.     JpGraphError::Install("JpGraphErrObject");
  353. }
  354.  
  355.  
  356. //
  357. //Check if there were any warnings, perhaps some wrong includes by the
  358. //user
  359. //
  360. //if( isset($GLOBALS['php_errormsg']) ) {
  361. //    JpGraphError::Raise("<b>General PHP error:</b><br>".$GLOBALS['php_errormsg']);
  362. //}
  363.  
  364.  
  365. //
  366. // Routine to determine if GD1 or GD2 is installed
  367. // At the moment this is used to verify that the user
  368. // really has GD2 if he has set USE_GD2 to true. 
  369. //
  370. function CheckGDVersion() {
  371.     ob_start();
  372.     phpinfo(8); // Just get the modules loaded
  373.     $a = ob_get_contents();
  374.     ob_end_clean();
  375.     if( preg_match('/.*GD Version.*(1[0-9|\.]+).*/',$a,$m) ) {
  376.     $r=1;$v=$m[1];
  377.     }
  378.     elseif( preg_match('/.*GD Version.*(2[0-9|\.]+).*/',$a,$m) ) {
  379.     $r=2;$v=$m[1];
  380.     }
  381.     else {
  382.     $r=0;$v=$m[1];
  383.     }
  384.  
  385.     return $r;
  386. }
  387.  
  388. //
  389. // Check what version of the GD library is installed.
  390. //
  391. if( USE_LIBRARY_GD2 == 'auto' ) {
  392.     $gdversion = CheckGDVersion();
  393.     if( $gdversion == 2 ) {
  394.     $GLOBALS['gd2'] = true;
  395.     $GLOBALS['copyfunc'] = 'imagecopyresampled';
  396.     }
  397.     elseif( $gdversion == 1 ) {
  398.     $GLOBALS['gd2'] = false;
  399.     $GLOBALS['copyfunc'] = 'imagecopyresized';
  400.     }
  401.     else {
  402.     JpGraphError::Raise(" Your PHP installation does not seem to 
  403.     have the required GD library.
  404.     Please see the PHP documentation on how to install and enable the GD library.");
  405.     }
  406. }
  407. else {
  408.     if( USE_LIBRARY_GD2 ) {
  409.     $GLOBALS['gd2'] = true;
  410.     $GLOBALS['copyfunc'] = 'imagecopyresampled';
  411.     }
  412.     else {
  413.     $GLOBALS['gd2'] = false;
  414.     $GLOBALS['copyfunc'] = 'imagecopyresized';
  415.     }
  416. }
  417.  
  418. // Usefull mathematical function
  419. function sign($a) {if( $a>=0) return 1; else return -1;}
  420.  
  421. // Utility function to generate an image name based on the filename we
  422. // are running from and assuming we use auto detection of graphic format
  423. // (top level), i.e it is safe to call this function
  424. // from a script that uses JpGraph
  425. function GenImgName() {
  426.     global $HTTP_SERVER_VARS;
  427.     $supported = imagetypes();
  428.     if( $supported & IMG_PNG )
  429.     $img_format="png";
  430.     elseif( $supported & IMG_GIF )
  431.     $img_format="gif";
  432.     elseif( $supported & IMG_JPG )
  433.     $img_format="jpeg";
  434.     if( !isset($HTTP_SERVER_VARS['PHP_SELF']) )
  435.     JpGraphError::Raise(" Can't access PHP_SELF, PHP global variable. You can't run PHP from command line
  436.         if you want to use the 'auto' naming of cache or image files.");
  437.     $fname=basename($HTTP_SERVER_VARS['PHP_SELF']);
  438.     // Replace the ".php" extension with the image format extension
  439.     return substr($fname,0,strlen($fname)-4).".".$img_format;
  440. }
  441.  
  442.  
  443. class LanguageConv {
  444.  
  445. //  Translate iso encoding to unicode
  446.     function iso2uni ($isoline){
  447.     $uniline = "";
  448.     for ($i=0; $i < strlen($isoline); $i++){
  449.         $thischar=substr($isoline,$i,1);
  450.         $charcode=ord($thischar);
  451.         $uniline.=($charcode>175) ? "&#" . (1040+($charcode-176)). ";" : $thischar;
  452.     }
  453.     return $uniline;
  454.     }
  455.  
  456.     function ToCyrillic($aTxt) {
  457.     if( CYRILLIC_FROM_WINDOWS ) {
  458.         $aTxt = convert_cyr_string($aTxt, "w", "k"); 
  459.     }
  460.     $isostring = convert_cyr_string($aTxt, "k", "i");
  461.     $unistring = LanguageConv::iso2uni($isostring);
  462.     return $unistring;
  463.     }
  464. }
  465.  
  466. //===================================================
  467. // CLASS JpgTimer
  468. // Description: General timing utility class to handle
  469. // timne measurement of generating graphs. Multiple
  470. // timers can be started by pushing new on a stack.
  471. //===================================================
  472. class JpgTimer {
  473.     var $start;
  474.     var $idx;    
  475. //---------------
  476. // CONSTRUCTOR
  477.     function JpgTimer() {
  478.     $this->idx=0;
  479.     }
  480.  
  481. //---------------
  482. // PUBLIC METHODS    
  483.  
  484.     // Push a new timer start on stack
  485.     function Push() {
  486.     list($ms,$s)=explode(" ",microtime());    
  487.     $this->start[$this->idx++]=floor($ms*1000) + 1000*$s;    
  488.     }
  489.  
  490.     // Pop the latest timer start and return the diff with the
  491.     // current time
  492.     function Pop() {
  493.     assert($this->idx>0);
  494.     list($ms,$s)=explode(" ",microtime());    
  495.     $etime=floor($ms*1000) + (1000*$s);
  496.     $this->idx--;
  497.     return $etime-$this->start[$this->idx];
  498.     }
  499. } // Class
  500.  
  501. $gJpgBrandTiming = BRAND_TIMING;
  502. $gDateLocale = new DateLocale();
  503. $gJpgDateLocale = new DateLocale();
  504. //===================================================
  505. // CLASS DateLocale
  506. // Description: Hold localized text used in dates
  507. // ToDOo: Rewrite this to use the real local locale
  508. // instead.
  509. //===================================================
  510. class DateLocale {
  511.  
  512.     var $iLocale = 'C'; // environmental locale be used by default
  513.  
  514.     var $iDayAbb = null;
  515.     var $iShortDay = null;
  516.     var $iShortMonth = null;
  517.     var $iMonthName = null;
  518.  
  519. //---------------
  520. // CONSTRUCTOR    
  521.     function DateLocale() {
  522.     settype($this->iDayAbb, 'array');
  523.     settype($this->iShortDay, 'array');
  524.     settype($this->iShortMonth, 'array');
  525.     settype($this->iMonthName, 'array');
  526.  
  527.  
  528.     $this->Set('C');
  529.     }
  530.  
  531. //---------------
  532. // PUBLIC METHODS    
  533.     function Set($aLocale) {
  534.     if ( in_array($aLocale, array_keys($this->iDayAbb)) ){ 
  535.         $this->iLocale = $aLocale;
  536.         return TRUE;  // already cached nothing else to do!
  537.     }
  538.  
  539.     $pLocale = setlocale(LC_TIME, 0); // get current locale for LC_TIME
  540.     $res = setlocale(LC_TIME, $aLocale);
  541.     if ( ! $res ){
  542.         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.");
  543.         return FALSE;
  544.     }
  545.  
  546.     $this->iLocale = $aLocale;
  547.  
  548.     for ( $i = 0, $ofs = 0 - strftime('%w'); $i < 7; $i++, $ofs++ ){
  549.         $day = strftime('%a', strtotime("$ofs day"));
  550.         $day{0} = strtoupper($day{0});
  551.         $this->iDayAbb[$aLocale][]= $day{0};
  552.         $this->iShortDay[$aLocale][]= $day;
  553.     }
  554.  
  555.     for($i=1; $i<=12; ++$i) {
  556.         list($short ,$full) = explode('|', strftime("%b|%B",strtotime("2001-$i-01")));
  557.         $this->iShortMonth[$aLocale][] = ucfirst($short);
  558.         $this->iMonthName [$aLocale][] = ucfirst($full);
  559.     }
  560.     
  561.     
  562.     setlocale(LC_TIME, $pLocale);
  563.  
  564.     return TRUE;
  565.     }
  566.  
  567.  
  568.     function GetDayAbb() {
  569.     return $this->iDayAbb[$this->iLocale];
  570.     }
  571.     
  572.     function GetShortDay() {
  573.     return $this->iShortDay[$this->iLocale];
  574.     }
  575.  
  576.     function GetShortMonth() {
  577.     return $this->iShortMonth[$this->iLocale];
  578.     }
  579.     
  580.     function GetShortMonthName($aNbr) {
  581.     return $this->iShortMonth[$this->iLocale][$aNbr];
  582.     }
  583.  
  584.     function GetLongMonthName($aNbr) {
  585.     return $this->iMonthName[$this->iLocale][$aNbr];
  586.     }
  587.  
  588.     function GetMonth() {
  589.     return $this->iMonthName[$this->iLocale];
  590.     }
  591. }
  592.  
  593. //===================================================
  594. // CLASS FuncGenerator
  595. // Description: Utility class to help generate data for function plots. 
  596. // The class supports both parametric and regular functions.
  597. //===================================================
  598. class FuncGenerator {
  599.     var $iFunc='',$iXFunc='',$iMin,$iMax,$iStepSize;
  600.     
  601.     function FuncGenerator($aFunc,$aXFunc='') {
  602.     $this->iFunc = $aFunc;
  603.     $this->iXFunc = $aXFunc;
  604.     }
  605.     
  606.     function E($aXMin,$aXMax,$aSteps=50) {
  607.     $this->iMin = $aXMin;
  608.     $this->iMax = $aXMax;
  609.     $this->iStepSize = ($aXMax-$aXMin)/$aSteps;
  610.  
  611.     if( $this->iXFunc != '' )
  612.         $t = 'for($i='.$aXMin.'; $i<='.$aXMax.'; $i += '.$this->iStepSize.') {$ya[]='.$this->iFunc.';$xa[]='.$this->iXFunc.';}';
  613.     elseif( $this->iFunc != '' )
  614.         $t = 'for($x='.$aXMin.'; $x<='.$aXMax.'; $x += '.$this->iStepSize.') {$ya[]='.$this->iFunc.';$xa[]=$x;} $x='.$aXMax.';$ya[]='.$this->iFunc.';$xa[]=$x;';
  615.     else
  616.         JpGraphError::Raise('FuncGenerator : No function specified. ');
  617.             
  618.     @eval($t);
  619.         
  620.     // If there is an error in the function specifcation this is the only
  621.     // way we can discover that.
  622.     if( empty($xa) || empty($ya) )
  623.         JpGraphError::Raise('FuncGenerator : Syntax error in function specification ');
  624.                 
  625.     return array($xa,$ya);
  626.     }
  627. }
  628.  
  629.  
  630. //=======================================================
  631. // CLASS Footer
  632. // Description: Encapsulates the footer line in the Graph
  633. //
  634. //=======================================================
  635. class Footer {
  636.     var $left,$center,$right;
  637.     var $iLeftMargin = 3;
  638.     var $iRightMargin = 3;
  639.     var $iBottomMargin = 3;
  640.  
  641.     function Footer() {
  642.     $this->left = new Text();
  643.     $this->left->ParagraphAlign('left');
  644.     $this->center = new Text();
  645.     $this->center->ParagraphAlign('center');
  646.     $this->right = new Text();
  647.     $this->right->ParagraphAlign('right');
  648.     }
  649.  
  650.     function Stroke($aImg) {
  651.     $y = $aImg->height - $this->iBottomMargin;
  652.     $x = $this->iLeftMargin;
  653.     $this->left->Align('left','bottom');
  654.     $this->left->Stroke($aImg,$x,$y);
  655.  
  656.     $x = ($aImg->width - $this->iLeftMargin - $this->iRightMargin)/2;
  657.     $this->center->Align('center','bottom');
  658.     $this->center->Stroke($aImg,$x,$y);
  659.  
  660.     $x = $aImg->width - $this->iRightMargin;
  661.     $this->right->Align('right','bottom');
  662.     $this->right->Stroke($aImg,$x,$y);
  663.     }
  664. }
  665.  
  666. //===================================================
  667. // CLASS Graph
  668. // Description: Main class to handle graphs
  669. //===================================================
  670. class Graph {
  671.     var $cache=null;        // Cache object (singleton)
  672.     var $img=null;            // Img object (singleton)
  673.     var $plots=array();    // Array of all plot object in the graph (for Y 1 axis)
  674.     var $y2plots=array();// Array of all plot object in the graph (for Y 2 axis)
  675.     var $xscale=null;        // X Scale object (could be instance of LinearScale or LogScale
  676.     var $yscale=null,$y2scale=null;
  677.     var $cache_name;        // File name to be used for the current graph in the cache directory
  678.     var $xgrid=null;        // X Grid object (linear or logarithmic)
  679.     var $ygrid=null,$y2grid=null; //dito for Y
  680.     var $doframe=true,$frame_color=array(0,0,0), $frame_weight=1;    // Frame around graph
  681.     var $boxed=false, $box_color=array(0,0,0), $box_weight=1;        // Box around plot area
  682.     var $doshadow=false,$shadow_width=4,$shadow_color=array(102,102,102);    // Shadow for graph
  683.     var $xaxis=null;        // X-axis (instane of Axis class)
  684.     var $yaxis=null, $y2axis=null;    // Y axis (instance of Axis class)
  685.     var $margin_color=array(200,200,200);    // Margin color of graph
  686.     var $plotarea_color=array(255,255,255);    // Plot area color
  687.     var $title,$subtitle,$subsubtitle;     // Title and subtitle(s) text object
  688.     var $axtype="linlin";    // Type of axis
  689.     var $xtick_factor;    // Factot to determine the maximum number of ticks depending on the plot with
  690.     var $texts=null;        // Text object to ge shown in the graph
  691.     var $lines=null;
  692.     var $bands=null;
  693.     var $text_scale_off=0;    // Text scale offset in world coordinates
  694.     var $background_image="",$background_image_type=-1,$background_image_format="png";
  695.     var $background_image_bright=0,$background_image_contr=0,$background_image_sat=0;
  696.     var $image_bright=0, $image_contr=0, $image_sat=0;
  697.     var $inline;
  698.     var $showcsim=0,$csimcolor="red"; //debug stuff, draw the csim boundaris on the image if <>0
  699.     var $grid_depth=DEPTH_BACK;    // Draw grid under all plots as default
  700.     var $iAxisStyle = AXSTYLE_SIMPLE;
  701.     var $iCSIMdisplay=false,$iHasStroked = false;
  702.     var $footer;
  703.     var $csimcachename = '', $csimcachetimeout = 0;
  704.     var $iDoClipping = false;
  705.  
  706. //---------------
  707. // CONSTRUCTOR
  708.  
  709.     // aWIdth         Width in pixels of image
  710.     // aHeight      Height in pixels of image
  711.     // aCachedName    Name for image file in cache directory 
  712.     // aTimeOut        Timeout in minutes for image in cache
  713.     // aInline        If true the image is streamed back in the call to Stroke()
  714.     //            If false the image is just created in the cache
  715.     function Graph($aWidth=300,$aHeight=200,$aCachedName="",$aTimeOut=0,$aInline=true) {
  716.     GLOBAL $gJpgBrandTiming;
  717.     // If timing is used create a new timing object
  718.     if( $gJpgBrandTiming ) {
  719.         global $tim;
  720.         $tim = new JpgTimer();
  721.         $tim->Push();
  722.     }
  723.         
  724.     // Automatically generate the image file name based on the name of the script that
  725.     // generates the graph
  726.     if( $aCachedName=="auto" )
  727.         $aCachedName=GenImgName();
  728.             
  729.     // Should the image be streamed back to the browser or only to the cache?
  730.     $this->inline=$aInline;
  731.         
  732.     $this->img    =  new RotImage($aWidth,$aHeight);
  733.  
  734.     $this->cache     =  new ImgStreamCache($this->img);
  735.     $this->cache->SetTimeOut($aTimeOut);
  736.  
  737.     $this->title     =  new Text();
  738.     $this->title->ParagraphAlign('center');
  739.     $this->title->SetFont(FF_FONT2,FS_BOLD);
  740.     $this->title->SetMargin(3);
  741.  
  742.     $this->subtitle =  new Text();
  743.     $this->subtitle->ParagraphAlign('center');
  744.  
  745.     $this->subsubtitle =  new Text();
  746.     $this->subsubtitle->ParagraphAlign('center');
  747.  
  748.     $this->legend     =  new Legend();
  749.     $this->footer = new Footer();
  750.  
  751.     // If the cached version exist just read it directly from the
  752.     // cache, stream it back to browser and exit
  753.     if( $aCachedName!="" && READ_CACHE && $aInline )
  754.         if( $this->cache->GetAndStream($aCachedName) ) {
  755.         exit();
  756.         }
  757.                 
  758.     $this->cache_name = $aCachedName;
  759.     $this->SetTickDensity(); // Normal density
  760.     }
  761. //---------------
  762. // PUBLIC METHODS    
  763.  
  764.     // Should the grid be in front or back of the plot?
  765.     function SetGridDepth($aDepth) {
  766.     $this->grid_depth=$aDepth;
  767.     }
  768.     
  769.     // Specify graph angle 0-360 degrees.
  770.     function SetAngle($aAngle) {
  771.     $this->img->SetAngle($aAngle);
  772.     }
  773.  
  774.     // Shortcut to image margin
  775.     function SetMargin($lm,$rm,$tm,$bm) {
  776.     $this->img->SetMargin($lm,$rm,$tm,$bm);
  777.     }
  778.  
  779.     // Rotate the graph 90 degrees and set the margin 
  780.     // when we have done a 90 degree rotation
  781.     function Set90AndMargin($lm,$rm,$tm,$bm) {
  782.     $adj = ($this->img->height - $this->img->width)/2;
  783.     $this->img->SetMargin($tm-$adj,$bm-$adj,$rm+$adj,$lm+$adj);
  784.     $this->img->SetCenter(floor($this->img->width/2),floor($this->img->height/2));
  785.     $this->SetAngle(90);
  786.     }
  787.     
  788.     function SetClipping($aFlg=true) {
  789.     $this->iDoClipping = $aFlg ;
  790.     }
  791.  
  792.     // Add a plot object to the graph
  793.     function Add(&$aPlot) {
  794.     if( $aPlot == null )
  795.         JpGraphError::Raise("<b></b> Graph::Add() You tried to add a null plot to the graph.");
  796.     if( is_array($aPlot) && count($aPlot) > 0 )
  797.         $cl = get_class($aPlot[0]);
  798.     else
  799.         $cl = get_class($aPlot);
  800.  
  801.     if( $cl == 'text' ) 
  802.         $this->AddText($aPlot);
  803.     elseif( $cl == 'plotline' )
  804.         $this->AddLine($aPlot);
  805.     elseif( $cl == 'plotband' )
  806.         $this->AddBand($aPlot);
  807.     else
  808.         $this->plots[] = &$aPlot;
  809.     }
  810.  
  811.     // Add plot to second Y-scale
  812.     function AddY2(&$aPlot) {
  813.     if( $aPlot == null )
  814.         JpGraphError::Raise("<b></b> Graph::AddY2() You tried to add a null plot to the graph.");    
  815.     $this->y2plots[] = &$aPlot;
  816.     }
  817.     
  818.     // Add text object to the graph
  819.     function AddText(&$aTxt) {
  820.     if( $aTxt == null )
  821.         JpGraphError::Raise("<b></b> Graph::AddText() You tried to add a null text to the graph.");        
  822.     if( is_array($aTxt) ) {
  823.         for($i=0; $i < count($aTxt); ++$i )
  824.         $this->texts[]=&$aTxt[$i];
  825.     }
  826.     else
  827.         $this->texts[] = &$aTxt;
  828.     }
  829.     
  830.     // Add a line object (class PlotLine) to the graph
  831.     function AddLine(&$aLine) {
  832.     if( $aLine == null )
  833.         JpGraphError::Raise("<b></b> Graph::AddLine() You tried to add a null line to the graph.");        
  834.     if( is_array($aLine) ) {
  835.         for($i=0; $i<count($aLine); ++$i )
  836.         $this->lines[]=&$aLine[$i];
  837.     }
  838.     else
  839.         $this->lines[] = &$aLine;
  840.     }
  841.  
  842.     // Add vertical or horizontal band
  843.     function AddBand(&$aBand) {
  844.     if( $aBand == null )
  845.         JpGraphError::Raise(" Graph::AddBand() You tried to add a null band to the graph.");
  846.     if( is_array($aBand) ) {
  847.         for($i=0; $i<count($aBand); ++$i )
  848.         $this->bands[] = &$aBand[$i];
  849.     }
  850.     else
  851.         $this->bands[] = &$aBand;
  852.     }
  853.  
  854.     
  855.     // Specify a background image
  856.     function SetBackgroundImage($aFileName,$aBgType=BGIMG_FILLPLOT,$aImgFormat="auto") {
  857.  
  858.     if( $GLOBALS['gd2'] && !USE_TRUECOLOR ) {
  859.         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.");
  860.     }
  861.  
  862.     // Get extension to determine image type
  863.     if( $aImgFormat == "auto" ) {
  864.         $e = explode('.',$aFileName);
  865.         if( !$e ) {
  866.         JpGraphError::Raise('Incorrect file name for Graph::SetBackgroundImage() : '.$aFileName);
  867.         exit();
  868.         }
  869.  
  870.         if( strtolower($e[1]) == 'png' ) 
  871.         $aImgFormat = 'png';
  872.         elseif( strtolower($e[1]) == 'jpg' || strtolower($e[1]) == 'jpeg') 
  873.         $aImgFormat = 'jpg';
  874.         elseif( strtolower($e[1]) == 'gif' )     
  875.         $aImgFormat = 'gif';
  876.         else {
  877.         JpGraphError::Raise('Unknown file extension in Graph::SetBackgroundImage : '.$aFileName);
  878.         exit();
  879.         }
  880.     }
  881.  
  882.     $this->background_image = $aFileName;
  883.     $this->background_image_type=$aBgType;
  884.     $this->background_image_format=$aImgFormat;
  885.     }
  886.     
  887.     // Adjust brightness and constrast for background image
  888.     function AdjBackgroundImage($aBright,$aContr=0,$aSat=0) {
  889.     $this->background_image_bright=$aBright;
  890.     $this->background_image_contr=$aContr;
  891.     $this->background_image_sat=$aSat;
  892.     }
  893.     
  894.     // Adjust brightness and constrast for image
  895.     function AdjImage($aBright,$aContr=0,$aSat=0) {
  896.     $this->image_bright=$aBright;
  897.     $this->image_contr=$aContr;
  898.     $this->image_sat=$aSat;
  899.     }
  900.  
  901.     // Specify axis style (boxed or single)
  902.     function SetAxisStyle($aStyle) {
  903.         $this->iAxisStyle = $aStyle ;
  904.     }
  905.     
  906.     // Set a frame around the plot area
  907.     function SetBox($aDrawPlotFrame=true,$aPlotFrameColor=array(0,0,0),$aPlotFrameWeight=1) {
  908.     $this->boxed = $aDrawPlotFrame;
  909.     $this->box_weight = $aPlotFrameWeight;
  910.     $this->box_color = $aPlotFrameColor;
  911.     }
  912.     
  913.     // Specify color for the plotarea (not the margins)
  914.     function SetColor($aColor) {
  915.     $this->plotarea_color=$aColor;
  916.     }
  917.     
  918.     // Specify color for the margins (all areas outside the plotarea)
  919.     function SetMarginColor($aColor) {
  920.     $this->margin_color=$aColor;
  921.     }
  922.     
  923.     // Set a frame around the entire image
  924.     function SetFrame($aDrawImgFrame=true,$aImgFrameColor=array(0,0,0),$aImgFrameWeight=1) {
  925.     $this->doframe = $aDrawImgFrame;
  926.     $this->frame_color = $aImgFrameColor;
  927.     $this->frame_weight = $aImgFrameWeight;
  928.     }
  929.         
  930.     // Set the shadow around the whole image
  931.     function SetShadow($aShowShadow=true,$aShadowWidth=5,$aShadowColor=array(102,102,102)) {
  932.     $this->doshadow = $aShowShadow;
  933.     $this->shadow_color = $aShadowColor;
  934.     $this->shadow_width = $aShadowWidth;
  935.     $this->footer->iBottomMargin += $aShadowWidth;
  936.     $this->footer->iRightMargin += $aShadowWidth;
  937.     }
  938.  
  939.     // Specify x,y scale. Note that if you manually specify the scale
  940.     // you must also specify the tick distance with a call to Ticks::Set()
  941.     function SetScale($aAxisType,$aYMin=1,$aYMax=1,$aXMin=1,$aXMax=1) {
  942.     $this->axtype = $aAxisType;
  943.  
  944.     $yt=substr($aAxisType,-3,3);
  945.     if( $yt=="lin" )
  946.         $this->yscale = new LinearScale($aYMin,$aYMax);
  947.     elseif( $yt == "int" ) {
  948.         $this->yscale = new LinearScale($aYMin,$aYMax);
  949.         $this->yscale->SetIntScale();
  950.     }
  951.     elseif( $yt=="log" )
  952.         $this->yscale = new LogScale($aYMin,$aYMax);
  953.     else
  954.         JpGraphError::Raise("Unknown scale specification for Y-scale. ($aAxisType)");
  955.             
  956.     $xt=substr($aAxisType,0,3);
  957.     if( $xt == "lin" || $xt == "tex" ) {
  958.         $this->xscale = new LinearScale($aXMin,$aXMax,"x");
  959.         $this->xscale->textscale = ($xt == "tex");
  960.     }
  961.     elseif( $xt == "int" ) {
  962.         $this->xscale = new LinearScale($aXMin,$aXMax,"x");
  963.         $this->xscale->SetIntScale();
  964.     }
  965.     elseif( $xt == "log" )
  966.         $this->xscale = new LogScale($aXMin,$aXMax,"x");
  967.     else
  968.         JpGraphError::Raise(" Unknown scale specification for X-scale. ($aAxisType)");
  969.  
  970.     $this->xscale->Init($this->img);
  971.     $this->yscale->Init($this->img);                        
  972.                     
  973.     $this->xaxis = new Axis($this->img,$this->xscale);
  974.     $this->yaxis = new Axis($this->img,$this->yscale);
  975.     $this->xgrid = new Grid($this->xaxis);
  976.     $this->ygrid = new Grid($this->yaxis);    
  977.     $this->ygrid->Show();            
  978.     }
  979.     
  980.     // Specify secondary Y scale
  981.     function SetY2Scale($aAxisType="lin",$aY2Min=1,$aY2Max=1) {
  982.     if( $aAxisType=="lin" ) 
  983.         $this->y2scale = new LinearScale($aY2Min,$aY2Max);
  984.     elseif( $aAxisType == "int" ) {
  985.         $this->y2scale = new LinearScale($aY2Min,$aY2Max);
  986.         $this->y2scale->SetIntScale();
  987.     }
  988.     elseif( $aAxisType=="log" ) {
  989.         $this->y2scale = new LogScale($aY2Min,$aY2Max);
  990.     }
  991.     else JpGraphError::Raise("JpGraph: Unsupported Y2 axis type: $axtype<br>");
  992.             
  993.     $this->y2scale->Init($this->img);    
  994.     $this->y2axis = new Axis($this->img,$this->y2scale);
  995.     $this->y2axis->scale->ticks->SetDirection(SIDE_LEFT); 
  996.     $this->y2axis->SetLabelSide(SIDE_RIGHT); 
  997.         
  998.     // Deafult position is the max x-value
  999.     $this->y2grid = new Grid($this->y2axis);                            
  1000.     }
  1001.     
  1002.     // Specify density of ticks when autoscaling 'normal', 'dense', 'sparse', 'verysparse'
  1003.     // The dividing factor have been determined heuristically according to my aesthetic 
  1004.     // sense (or lack off) y.m.m.v !
  1005.     function SetTickDensity($aYDensity=TICKD_NORMAL,$aXDensity=TICKD_NORMAL) {
  1006.     $this->xtick_factor=30;
  1007.     $this->ytick_factor=25;        
  1008.     switch( $aYDensity ) {
  1009.         case TICKD_DENSE:
  1010.         $this->ytick_factor=12;            
  1011.         break;
  1012.         case TICKD_NORMAL:
  1013.         $this->ytick_factor=25;            
  1014.         break;
  1015.         case TICKD_SPARSE:
  1016.         $this->ytick_factor=40;            
  1017.         break;
  1018.         case TICKD_VERYSPARSE:
  1019.         $this->ytick_factor=100;            
  1020.         break;        
  1021.         default:
  1022.         JpGraphError::Raise("JpGraph: Unsupported Tick density: $densy");
  1023.     }
  1024.     switch( $aXDensity ) {
  1025.         case TICKD_DENSE:
  1026.         $this->xtick_factor=15;                            
  1027.         break;
  1028.         case TICKD_NORMAL:
  1029.         $this->xtick_factor=30;            
  1030.         break;
  1031.         case TICKD_SPARSE:
  1032.         $this->xtick_factor=45;                    
  1033.         break;
  1034.         case TICKD_VERYSPARSE:
  1035.         $this->xtick_factor=60;                                
  1036.         break;        
  1037.         default:
  1038.         JpGraphError::Raise("JpGraph: Unsupported Tick density: $densx");
  1039.     }        
  1040.     }
  1041.     
  1042.  
  1043.     // Get a string of all image map areas    
  1044.     function GetCSIMareas() {
  1045.     if( !$this->iHasStroked )
  1046.         $this->Stroke(_CSIM_SPECIALFILE);
  1047.     $csim='';
  1048.     $n = count($this->plots);
  1049.     for( $i=0; $i<$n; ++$i ) 
  1050.         $csim .= $this->plots[$i]->GetCSIMareas();
  1051.  
  1052.     $n = count($this->y2plots);
  1053.     for( $i=0; $i<$n; ++$i ) 
  1054.         $csim .= $this->y2plots[$i]->GetCSIMareas();
  1055.  
  1056.     $csim .= $this->legend->GetCSIMAreas();
  1057.  
  1058.     return $csim;
  1059.     }
  1060.     
  1061.     // Get a complete <MAP>..</MAP> tag for the final image map
  1062.     function GetHTMLImageMap($aMapName) {
  1063.     $im = "<MAP NAME=\"$aMapName\">\n";
  1064.     $im .= $this->GetCSIMareas();
  1065.     $im .= "</MAP>"; 
  1066.     return $im;
  1067.     }
  1068.  
  1069.     function CheckCSIMCache($aCacheName,$aTimeOut=60) {
  1070.     
  1071.     
  1072.  
  1073.     $this->csimcachename = CSIMCACHE_DIR.$aCacheName;
  1074.     $this->csimcachetimeout = $aTimeOut;
  1075.  
  1076.     // First determine if we need to check for a cached version
  1077.     // This differs from the standard cache in the sense that the
  1078.     // image and CSIM map HTML file is written relative to the directory
  1079.     // the script executes in and not the specified cache directory.
  1080.     // The reason for this is that the cache directory is not necessarily
  1081.     // accessible from the HTTP server.
  1082.     if( $this->csimcachename != '' ) {
  1083.         $dir = dirname($this->csimcachename);
  1084.         $base = basename($this->csimcachename);
  1085.         $base = strtok($base,'.');
  1086.         $suffix = strtok('.');
  1087.         $basecsim = $dir.'/'.$base.'_csim_.html';
  1088.         $baseimg = $dir.'/'.$base.'.'.$this->img->img_format;
  1089.  
  1090.         $timedout=false;
  1091.         
  1092.         // Does it exist at all ?
  1093.         
  1094.         if( file_exists($basecsim) && file_exists($baseimg) ) {
  1095.         // Check that it hasn't timed out
  1096.         $diff=time()-filemtime($basecsim);
  1097.         if( $this->csimcachetimeout>0 && ($diff > $this->csimcachetimeout*60) ) {
  1098.             $timedout=true;
  1099.             @unlink($basecsim);
  1100.             @unlink($baseimg);
  1101.         }
  1102.         else {
  1103.             if ($fh = @fopen($basecsim, "r")) {
  1104.             fpassthru($fh);
  1105.             exit();
  1106.             }
  1107.             else
  1108.             JpGraphError::Raise(" Can't open cached CSIM \"$basecsim\" for reading.");
  1109.         }
  1110.         }
  1111.     }
  1112.     return false;
  1113.     }
  1114.  
  1115.     function StrokeCSIM($aScriptName='',$aCSIMName='',$aBorder=0) {
  1116.     GLOBAL $HTTP_GET_VARS;
  1117.  
  1118.     if( $aCSIMName=='' ) {
  1119.         // create a random map name
  1120.         srand ((double) microtime() * 1000000);
  1121.         $r = rand(0,100000);
  1122.         $aCSIMName='__mapname'.$r.'__';
  1123.     }
  1124.  
  1125.     if( empty($HTTP_GET_VARS[_CSIM_DISPLAY]) ) {
  1126.  
  1127.         // First determine if we need to check for a cached version
  1128.         // This differs from the standard cache in the sense that the
  1129.         // image and CSIM map HTML file is written relative to the directory
  1130.         // the script executes in and not the specified cache directory.
  1131.         // The reason for this is that the cache directory is not necessarily
  1132.         // accessible from the HTTP server.
  1133.         if( $this->csimcachename != '' ) {
  1134.         $dir = dirname($this->csimcachename);
  1135.         $base = basename($this->csimcachename);
  1136.         $base = strtok($base,'.');
  1137.         $suffix = strtok('.');
  1138.         $basecsim = $dir.'/'.$base.'_csim_.html';
  1139.         $baseimg = $base.'.'.$this->img->img_format;
  1140.  
  1141.         // Check that apache can write to directory specified
  1142.  
  1143.         if( file_exists($dir) && !is_writeable($dir) ) {
  1144.             JpgraphError::Raise('Apache/PHP does not have permission to write to the CSIM cache directory ('.$dir.'). Check permissions.');
  1145.         }
  1146.         
  1147.         // Make sure directory exists
  1148.         $this->cache->MakeDirs($dir);
  1149.  
  1150.         // Write the image file
  1151.         $this->Stroke(CSIMCACHE_DIR.$baseimg);
  1152.  
  1153.         // Construct wrapper HTML and write to file and send it back to browser
  1154.         $htmlwrap = $this->GetHTMLImageMap($aCSIMName)."\n".
  1155.             '<img src="'.CSIMCACHE_HTTP_DIR.$baseimg.'" ISMAP USEMAP="#'.$aCSIMName.'" border=$aBorder>'."\n";
  1156.         if($fh =  @fopen($basecsim,'w') ) {
  1157.             fwrite($fh,$htmlwrap);
  1158.             fclose($fh);
  1159.             echo $htmlwrap;
  1160.         }
  1161.         else
  1162.             JpGraphError::Raise(" Can't write CSIM \"$basecsim\" for writing. Check free space and permissions.");
  1163.         }
  1164.         else {
  1165.         if( $aScriptName=='' ) {
  1166.             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().');
  1167.             exit();
  1168.         }
  1169.  
  1170.         // Construct the HTML wrapper page
  1171.         // Get all user defined URL arguments
  1172.         reset($HTTP_GET_VARS);
  1173.         
  1174.         // This is a JPGRAPH internal defined that prevents
  1175.         // us from recursively coming here again
  1176.         $urlarg='?'._CSIM_DISPLAY.'=1';
  1177.  
  1178.         while( list($key,$value) = each($HTTP_GET_VARS) ) {
  1179.             $urlarg .= '&'.$key.'='.$value;
  1180.         }
  1181.  
  1182.         echo $this->GetHTMLImageMap($aCSIMName);
  1183.         echo "<img src='".$aScriptName.$urlarg."' ISMAP USEMAP='#".$aCSIMName."' border=$aBorder>";
  1184.         }
  1185.     }
  1186.     else {
  1187.         $this->Stroke();
  1188.     }
  1189.     }
  1190.  
  1191.     // Stroke the graph
  1192.     // $aStrokeFileName    If != "" the image will be written to this file and NOT
  1193.     // streamed back to the browser
  1194.     function Stroke($aStrokeFileName="") {        
  1195.     // If the filename is the predefined value = '_csim_special_'
  1196.     // we assume that the call to stroke only needs to do enough
  1197.     // to correctly generate the CSIM maps.
  1198.     // We use this variable to skip things we don't strictly need
  1199.     // to do to generate the image map to improve performance
  1200.     // a best we can. Therefor you will see a lot of tests !$_csim in the
  1201.     // code below.
  1202.     $_csim = ($aStrokeFileName===_CSIM_SPECIALFILE);
  1203.  
  1204.     // We need to know if we have stroked the plot in the
  1205.     // GetCSIMareas. Otherwise the CSIM hasn't been generated
  1206.     // and in the case of GetCSIM called before stroke to generate
  1207.     // CSIM without storing an image to disk GetCSIM must call Stroke.
  1208.     $this->iHasStroked = true;
  1209.  
  1210.     // Do any pre-stroke adjustment that is needed by the different plot types
  1211.     // (i.e bar plots want's to add an offset to the x-labels etc)
  1212.     for($i=0; $i<count($this->plots) ; ++$i ) {
  1213.         $this->plots[$i]->PreStrokeAdjust($this);
  1214.         $this->plots[$i]->Legend($this);
  1215.     }
  1216.         
  1217.     // Any plots on the second Y scale?
  1218.     if( $this->y2scale != null ) {
  1219.         for($i=0; $i<count($this->y2plots)    ; ++$i ) {
  1220.         $this->y2plots[$i]->PreStrokeAdjust($this);
  1221.         $this->y2plots[$i]->Legend($this);
  1222.         }
  1223.     }
  1224.         
  1225.     // Bail out if any of the Y-axis not been specified and
  1226.     // has no plots. (This means it is impossible to do autoscaling and
  1227.     // no other scale was given so we can't possible draw anything). If you use manual
  1228.     // scaling you also have to supply the tick steps as well.
  1229.     if( (!$this->yscale->IsSpecified() && count($this->plots)==0) ||
  1230.         ($this->y2scale!=null && !$this->y2scale->IsSpecified() && count($this->y2plots)==0) ) {
  1231.         $e = "Can't draw unspecified Y-scale.<br>\nYou have either:<br>\n";
  1232.         $e .= "1. Specified an Y axis for autoscaling but have not supplied any plots<br>\n";
  1233.         $e .= "2. Specified a scale manually but have forgot to specify the tick steps";
  1234.         JpGraphError::Raise($e);
  1235.     }
  1236.         
  1237.     // Bail out if no plots and no specified X-scale
  1238.     if( (!$this->xscale->IsSpecified() && count($this->plots)==0 && count($this->y2plots)==0) )
  1239.         JpGraphError::Raise("<strong>JpGraph: Can't draw unspecified X-scale.</strong><br>No plots.<br>");
  1240.  
  1241.     //Check if we should autoscale y-axis
  1242.     if( !$this->yscale->IsSpecified() && count($this->plots)>0 ) {
  1243.         list($min,$max) = $this->GetPlotsYMinMax($this->plots);
  1244.         $this->yscale->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor);
  1245.     }
  1246.     elseif( $this->yscale->IsSpecified() && $this->yscale->auto_ticks ) {
  1247.         // If the user has specified a min/max value for the scale we still use the
  1248.         // autoscaling to get a suitable tick distance. This might adjust the specified
  1249.         // min max values so they end up on a tick mark.
  1250.         $min = $this->yscale->scale[0];
  1251.         $max = $this->yscale->scale[1];
  1252.         $this->yscale->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor);
  1253.     }
  1254.  
  1255.     if( $this->y2scale != null) {
  1256.         if( !$this->y2scale->IsSpecified() && count($this->y2plots)>0 ) {
  1257.         list($min,$max) = $this->GetPlotsYMinMax($this->y2plots);
  1258.         $this->y2scale->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor);
  1259.         }            
  1260.         elseif( $this->y2scale->IsSpecified() && $this->y2scale->auto_ticks ) {
  1261.         // If the user has specified a min/max value for the scale we still use the
  1262.         // autoscaling to get a suitable tick distance. This might adjust the specified
  1263.         // min max values so they end up on a tick mark.
  1264.         $min = $this->y2scale->scale[0];
  1265.         $max = $this->y2scale->scale[1];
  1266.         $this->y2scale->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor);
  1267.         }
  1268.     }
  1269.                 
  1270.     //Check if we should autoscale x-axis
  1271.     if( !$this->xscale->IsSpecified() ) {
  1272.         if( substr($this->axtype,0,4) == "text" ) {
  1273.         $max=0;
  1274.         foreach( $this->plots as $p ) {
  1275.             $max=max($max,$p->numpoints-1);
  1276.         }
  1277.         $min=0;
  1278.         if( $this->y2axis != null ) {
  1279.             foreach( $this->y2plots as $p ) {
  1280.             $max=max($max,$p->numpoints-1);
  1281.             }            
  1282.         }
  1283.         $this->xscale->Update($this->img,$min,$max);
  1284.         $this->xscale->ticks->Set($this->xaxis->tick_step,1);
  1285.         $this->xscale->ticks->SupressMinorTickMarks();
  1286.         }
  1287.         else {
  1288.         list($min,$ymin) = $this->plots[0]->Min();
  1289.         list($max,$ymax) = $this->plots[0]->Max();
  1290.         foreach( $this->plots as $p ) {
  1291.             list($xmin,$ymin) = $p->Min();
  1292.             list($xmax,$ymax) = $p->Max();            
  1293.             $min = Min($xmin,$min);
  1294.             $max = Max($xmax,$max);
  1295.         }
  1296.         if( $this->y2axis != null ) {
  1297.             foreach( $this->y2plots as $p ) {
  1298.             list($xmin,$ymin) = $p->Min();
  1299.             list($xmax,$ymax) = $p->Max();            
  1300.             $min = Min($xmin,$min);
  1301.             $max = Max($xmax,$max);
  1302.             }            
  1303.         }
  1304.         $this->xscale->AutoScale($this->img,$min,$max,$this->img->plotwidth/$this->xtick_factor);
  1305.         }
  1306.             
  1307.         //Adjust position of y-axis and y2-axis to minimum/maximum of x-scale
  1308.         if( !is_numeric($this->yaxis->pos) && !is_string($this->yaxis->pos) )
  1309.             $this->yaxis->SetPos($this->xscale->GetMinVal());
  1310.         if( $this->y2axis != null ) {
  1311.         if( !is_numeric($this->y2axis->pos) && !is_string($this->y2axis->pos) )
  1312.             $this->y2axis->SetPos($this->xscale->GetMaxVal());
  1313.         $this->y2axis->SetTitleSide(SIDE_RIGHT);
  1314.         }
  1315.     }        
  1316.         
  1317.     // If we have a negative values and x-axis position is at 0
  1318.     // we need to supress the first and possible the last tick since
  1319.     // they will be drawn on top of the y-axis (and possible y2 axis)
  1320.     // The test below might seem strange the reasone being that if
  1321.     // the user hasn't specified a value for position this will not
  1322.     // be set until we do the stroke for the axis so as of now it
  1323.     // is undefined.
  1324.     // For X-text scale we ignore all this since the tick are usually
  1325.     // much further in and not close to the Y-axis. Hence the test 
  1326.     // for 'text'    
  1327.  
  1328.     if( ($this->yaxis->pos==$this->xscale->GetMinVal() || 
  1329.          (is_string($this->yaxis->pos) && $this->yaxis->pos=='min')) &&  
  1330.         !is_numeric($this->xaxis->pos) && $this->yscale->GetMinVal() < 0 && 
  1331.         substr($this->axtype,0,4) != 'text' && $this->xaxis->pos!="min" ) {
  1332.  
  1333.         //$this->yscale->ticks->SupressZeroLabel(false);
  1334.         $this->xscale->ticks->SupressFirst();
  1335.         if( $this->y2axis != null ) {
  1336.         $this->xscale->ticks->SupressLast();
  1337.         }
  1338.     }
  1339.     elseif( !is_numeric($this->yaxis->pos) && $this->yaxis->pos=='max' ) {
  1340.         $this->xscale->ticks->SupressLast();
  1341.     }
  1342.     
  1343.  
  1344.     if( !$_csim ) {
  1345.         $this->StrokePlotArea();
  1346.         $this->StrokeAxis();
  1347.     }
  1348.  
  1349.     // Stroke bands
  1350.     if( $this->bands != null && !$_csim) 
  1351.         for($i=0; $i<count($this->bands); ++$i) {
  1352.         // Stroke all bands that asks to be in the background
  1353.         if( $this->bands[$i]->depth == DEPTH_BACK )
  1354.             $this->bands[$i]->Stroke($this->img,$this->xscale,$this->yscale);
  1355.         }
  1356.  
  1357.     if( $this->grid_depth == DEPTH_BACK && !$_csim) {
  1358.         $this->ygrid->Stroke();
  1359.         $this->xgrid->Stroke();
  1360.     }
  1361.                 
  1362.     // Stroke Y2-axis
  1363.     if( $this->y2axis != null && !$_csim) {        
  1364.         $this->y2axis->Stroke($this->xscale);                 
  1365.         $this->y2grid->Stroke();
  1366.     }
  1367.         
  1368.     $oldoff=$this->xscale->off;
  1369.     if(substr($this->axtype,0,4)=="text") {
  1370.         $this->xscale->off += 
  1371.         ceil($this->xscale->scale_factor*$this->text_scale_off*$this->xscale->ticks->minor_step);
  1372.     }
  1373.  
  1374.     if( $this->iDoClipping ) {
  1375.         $oldimage = $this->img->CloneCanvasH();
  1376.     }
  1377.  
  1378.     // Stroke all plots for Y1 axis
  1379.     for($i=0; $i < count($this->plots); ++$i) {
  1380.         $this->plots[$i]->Stroke($this->img,$this->xscale,$this->yscale);
  1381.         $this->plots[$i]->StrokeMargin($this->img);
  1382.     }                        
  1383.  
  1384.     // Stroke all plots for Y2 axis
  1385.     if( $this->y2scale != null )
  1386.         for($i=0; $i< count($this->y2plots); ++$i ) {    
  1387.         $this->y2plots[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
  1388.         }        
  1389.  
  1390.     if( $this->iDoClipping ) {
  1391.         // Clipping only supports graphs at 0 and 90 degrees
  1392.         if( $this->img->a == 0 ) {
  1393.         $this->img->CopyCanvasH($oldimage,$this->img->img,
  1394.                     $this->img->left_margin,$this->img->top_margin,
  1395.                     $this->img->left_margin,$this->img->top_margin,
  1396.                     $this->img->plotwidth+1,$this->img->plotheight);
  1397.         }
  1398.         elseif( $this->img->a == 90 ) {
  1399.         $adj = ($this->img->height - $this->img->width)/2;
  1400.         $this->img->CopyCanvasH($oldimage,$this->img->img,
  1401.                     $this->img->bottom_margin-$adj,$this->img->left_margin+$adj,
  1402.                     $this->img->bottom_margin-$adj,$this->img->left_margin+$adj,
  1403.                     $this->img->plotheight+1,$this->img->plotwidth);
  1404.         }
  1405.         else {
  1406.         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.');
  1407.         }
  1408.         $this->img->Destroy();
  1409.         $this->img->SetCanvasH($oldimage);
  1410.     }
  1411.  
  1412.     $this->xscale->off=$oldoff;
  1413.         
  1414.     if( $this->grid_depth == DEPTH_FRONT && !$_csim ) {
  1415.         $this->ygrid->Stroke();
  1416.         $this->xgrid->Stroke();
  1417.     }
  1418.  
  1419.     // Stroke bands
  1420.     if( $this->bands!= null )
  1421.         for($i=0; $i<count($this->bands); ++$i) {
  1422.         // Stroke all bands that asks to be in the foreground
  1423.         if( $this->bands[$i]->depth == DEPTH_FRONT )
  1424.             $this->bands[$i]->Stroke($this->img,$this->xscale,$this->yscale);
  1425.         }
  1426.  
  1427.     // Stroke any lines added
  1428.     if( $this->lines != null ) {
  1429.         for($i=0; $i<count($this->lines); ++$i) {
  1430.         $this->lines[$i]->Stroke($this->img,$this->xscale,$this->yscale);
  1431.         }
  1432.     }
  1433.         
  1434.     // Finally draw the axis again since some plots may have nagged
  1435.     // the axis in the edges.
  1436.     if( !$_csim )
  1437.         $this->StrokeAxis();
  1438.  
  1439.     if( $this->y2scale != null && !$_csim ) 
  1440.         $this->y2axis->Stroke($this->xscale);     
  1441.         
  1442.     if( !$_csim ) {
  1443.         $this->StrokePlotBox();
  1444.         $this->footer->Stroke($this->img);
  1445.     }
  1446.         
  1447.     if( !$_csim ) {
  1448.         // The titles and legends never gets rotated so make sure
  1449.         // that the angle is 0 before stroking them                
  1450.         $aa = $this->img->SetAngle(0);
  1451.         $this->StrokeTitles();
  1452.     }
  1453.  
  1454.     $this->legend->Stroke($this->img);        
  1455.  
  1456.     if( !$_csim ) {
  1457.  
  1458.         $this->StrokeTexts();    
  1459.         $this->img->SetAngle($aa);    
  1460.             
  1461.         // Draw an outline around the image map    
  1462.         if(JPG_DEBUG)
  1463.         $this->DisplayClientSideaImageMapAreas();        
  1464.         
  1465.         // Adjust the appearance of the image
  1466.         $this->AdjustSaturationBrightnessContrast();
  1467.  
  1468.         // If the filename is given as the special "__handle"
  1469.         // then the image handler is returned and the image is NOT
  1470.         // streamed back
  1471.         if( $aStrokeFileName == _IMG_HANDLER ) {
  1472.         return $this->img->img;
  1473.         }
  1474.         else {
  1475.         // Finally stream the generated picture                    
  1476.         $this->cache->PutAndStream($this->img,$this->cache_name,$this->inline,
  1477.                        $aStrokeFileName);        
  1478.         }
  1479.     }
  1480.     }
  1481.  
  1482. //---------------
  1483. // PRIVATE METHODS    
  1484.     function StrokeAxis() {
  1485.         
  1486.     // Stroke axis
  1487.     if( $this->iAxisStyle != AXSTYLE_SIMPLE ) {
  1488.         switch( $this->iAxisStyle ) {
  1489.             case AXSTYLE_BOXIN :
  1490.                 $toppos = SIDE_DOWN;
  1491.             $bottompos = SIDE_UP;
  1492.                 $leftpos = SIDE_RIGHT;
  1493.                 $rightpos = SIDE_LEFT;
  1494.                 break;
  1495.         case AXSTYLE_BOXOUT :
  1496.             $toppos = SIDE_UP;
  1497.                 $bottompos = SIDE_DOWN;        
  1498.                 $leftpos = SIDE_LEFT;
  1499.             $rightpos = SIDE_RIGHT;
  1500.                 break;            
  1501.         default:
  1502.                 JpGRaphError::Raise('Unknown AxisStyle() : '.$this->iAxisStyle);
  1503.                 break;
  1504.         }
  1505.         $this->xaxis->SetPos('min');
  1506.         
  1507.         // By default we hide the first label so it doesn't cross the
  1508.         // Y-axis in case the positon hasn't been set by the user.
  1509.         // However, if we use a box we always want the first value
  1510.         // displayed so we make sure it will be displayed.
  1511.         $this->xscale->ticks->SupressFirst(false);
  1512.         
  1513.         $this->xaxis->SetLabelSide(SIDE_DOWN);
  1514.         $this->xaxis->scale->ticks->SetSide($bottompos);
  1515.         $this->xaxis->Stroke($this->yscale);
  1516.  
  1517.         // To avoid side effects we work on a new copy
  1518.         $maxis = $this->xaxis;
  1519.         $maxis->SetPos('max');
  1520.         $maxis->SetLabelSide(SIDE_UP);
  1521.         $maxis->SetLabelMargin(7);
  1522.         $this->xaxis->scale->ticks->SetSide($toppos);
  1523.         $maxis->Stroke($this->yscale);
  1524.  
  1525.         $this->yaxis->SetPos('min');
  1526.         $this->yaxis->SetLabelMargin(10);
  1527.         $this->yaxis->SetLabelSide(SIDE_LEFT);
  1528.         $this->yaxis->scale->ticks->SetSide($leftpos);
  1529.         $this->yaxis->Stroke($this->xscale);
  1530.  
  1531.         $myaxis = $this->yaxis;
  1532.         $myaxis->SetPos('max');
  1533.         $myaxis->SetLabelMargin(10);
  1534.         $myaxis->SetLabelSide(SIDE_RIGHT);
  1535.         $myaxis->scale->ticks->SetSide($rightpos);
  1536.         $myaxis->Stroke($this->xscale);
  1537.         
  1538.     }
  1539.     else {
  1540.         $this->xaxis->Stroke($this->yscale);
  1541.         $this->yaxis->Stroke($this->xscale);        
  1542.     }
  1543.     }
  1544.  
  1545.  
  1546.     // Private helper function for backgound image
  1547.     function LoadBkgImage($aImgFormat="png",$aBright=0,$aContr=0) {        
  1548.     if( $aImgFormat == "jpg" )
  1549.             $f = "imagecreatefromjpeg";
  1550.     else
  1551.         $f = "imagecreatefrom".$aImgFormat;
  1552.     $imgtag = $aImgFormat;
  1553.     if( $aImgFormat == "jpeg" )
  1554.         $imgtag = "jpg";
  1555.     if( !strstr($this->background_image,$imgtag) && strstr($this->background_image,".") ) {
  1556.         $t = " Background image seems to be of different type (has different file extension)".
  1557.          " than specified imagetype. <br>Specified: '".
  1558.         $aImgFormat."'<br>File: '".$this->background_image."'";
  1559.         JpGraphError::Raise($t);
  1560.     }
  1561.     $img = $f($this->background_image);
  1562.     if( !$img ) {
  1563.         JpGraphError::Raise(" Can't read background image: '".$this->background_image."'");   
  1564.     }
  1565.     return $img;
  1566.     }    
  1567.  
  1568.     function StrokeFrameBackground() {
  1569.     if( $this->background_image == "" ) 
  1570.         return;
  1571.  
  1572.     $bkgimg = $this->LoadBkgImage($this->background_image_format);
  1573.     $this->img->_AdjBrightContrast($bkgimg,$this->background_image_bright,
  1574.                        $this->background_image_contr);
  1575.     $this->img->_AdjSat($bkgimg,$this->background_image_sat);
  1576.     $bw = ImageSX($bkgimg);
  1577.     $bh = ImageSY($bkgimg);
  1578.  
  1579.     // No matter what the angle is we always stroke the image and frame
  1580.     // assuming it is 0 degree
  1581.     $aa = $this->img->SetAngle(0);
  1582.         
  1583.     switch( $this->background_image_type ) {
  1584.         case BGIMG_FILLPLOT: // Resize to just fill the plotarea
  1585.         $this->StrokeFrame();
  1586.         $GLOBALS['copyfunc']($this->img->img,$bkgimg,
  1587.                      $this->img->left_margin,$this->img->top_margin,
  1588.                      0,0,$this->img->plotwidth,$this->img->plotheight,
  1589.                      $bw,$bh);
  1590.         break;
  1591.         case BGIMG_FILLFRAME: // Fill the whole area from upper left corner, resize to just fit
  1592.         $GLOBALS['copyfunc']($this->img->img,$bkgimg,
  1593.                      0,0,0,0,
  1594.                      $this->img->width,$this->img->height,
  1595.                      $bw,$bh);
  1596.         $this->StrokeFrame();
  1597.         break;
  1598.         case BGIMG_COPY: // Just copy the image from left corner, no resizing
  1599.         $GLOBALS['copyfunc']($this->img->img,$bkgimg,
  1600.                      0,0,0,0,
  1601.                      $bw,$bh,
  1602.                      $bw,$bh);
  1603.         $this->StrokeFrame();
  1604.         break;
  1605.         case BGIMG_CENTER: // Center original image in the plot area
  1606.         $centerx = round($this->img->plotwidth/2+$this->img->left_margin-$bw/2);
  1607.         $centery = round($this->img->plotheight/2+$this->img->top_margin-$bh/2);
  1608.         $GLOBALS['copyfunc']($this->img->img,$bkgimg,
  1609.                      $centerx,$centery,
  1610.                      0,0,
  1611.                      $bw,$bh,
  1612.                      $bw,$bh);
  1613.         $this->StrokeFrame();
  1614.         break;
  1615.         default:
  1616.         JpGraphError::Raise(" Unknown background image layout");
  1617.     }            
  1618.     $this->img->SetAngle($aa);        
  1619.     }
  1620.  
  1621.     // Private
  1622.     // Stroke the plot area with either a solid color or a background image
  1623.     function StrokePlotArea() {
  1624.     if( $this->background_image != "" ) {
  1625.         $this->StrokeFrameBackground();
  1626.     }
  1627.     else {                
  1628.         $aa = $this->img->SetAngle(0);
  1629.         $this->StrokeFrame();
  1630.         $this->img->SetAngle($aa);            
  1631.         $this->img->PushColor($this->plotarea_color);
  1632.  
  1633.         // Note: To be consistent we really should take a possible shadow
  1634.         // into account. However, that causes some problem for the LinearScale class
  1635.         // since in the current design it does not have any links to class Graph which
  1636.         // means it has no way of compensating for the adjusted plotarea in case of a 
  1637.         // shadow. So, until I redesign LinearScale we can't compensate for this.
  1638.         // So just set the two adjustment parameters to zero for now.
  1639.         $boxadj = 0; //$this->doframe ? $this->frame_weight : 0 ;
  1640.         $adj = 0; //$this->doshadow ? $this->shadow_width : 0 ;
  1641.  
  1642.         $this->img->FilledRectangle($this->img->left_margin+$boxadj,
  1643.                     $this->img->top_margin+$boxadj,
  1644.                     $this->img->width-$this->img->right_margin-$adj-2*$boxadj,
  1645.                     $this->img->height-$this->img->bottom_margin-$adj-2*$boxadj);    
  1646.         $this->img->PopColor();
  1647.     }    
  1648.     }    
  1649.     
  1650.     
  1651.     function StrokePlotBox() {
  1652.     // Should we draw a box around the plot area?
  1653.     if( $this->boxed ) {
  1654.         $this->img->SetLineWeight($this->box_weight);
  1655.         $this->img->SetColor($this->box_color);
  1656.         $this->img->Rectangle(
  1657.         $this->img->left_margin,$this->img->top_margin,
  1658.         $this->img->width-$this->img->right_margin,
  1659.         $this->img->height-$this->img->bottom_margin);
  1660.     }                        
  1661.     }        
  1662.  
  1663.     function StrokeTitles() {
  1664.     // Stroke title
  1665.     $y = $this->title->margin; 
  1666.     $margin=2;
  1667.     $this->title->Center(0,$this->img->width,$y);
  1668.     $this->title->Stroke($this->img);
  1669.         
  1670.     // ... and subtitle
  1671.     $y += $this->title->GetTextHeight($this->img) + $margin + $this->subtitle->margin;
  1672.     $this->subtitle->Center(0,$this->img->width,$y);    
  1673.     $this->subtitle->Stroke($this->img);
  1674.  
  1675.     // ... and subsubtitle
  1676.     $y += $this->subtitle->GetTextHeight($this->img) + $margin + $this->subsubtitle->margin;
  1677.     $this->subsubtitle->Center(0,$this->img->width,$y);
  1678.     $this->subsubtitle->Stroke($this->img);
  1679.  
  1680.     }
  1681.  
  1682.     function StrokeTexts() {
  1683.     // Stroke any user added text objects
  1684.     if( $this->texts != null ) {
  1685.         for($i=0; $i<count($this->texts); ++$i) {
  1686.         $this->texts[$i]->Stroke($this->img);
  1687.         }
  1688.     }
  1689.     }
  1690.  
  1691.     function DisplayClientSideaImageMapAreas() {
  1692.     // Debug stuff - display the outline of the image map areas
  1693.     foreach ($this->plots as $p) {
  1694.         $csim.= $p->GetCSIMareas();
  1695.     }
  1696.     $csim .= $this->legend->GetCSIMareas();
  1697.     if (preg_match_all("/area shape=\"(\w+)\" coords=\"([0-9\, ]+)\"/", $csim, $coords)) {
  1698.         $this->img->SetColor($this->csimcolor);
  1699.         for ($i=0; $i<count($coords[0]); $i++) {
  1700.         if ($coords[1][$i]=="poly") {
  1701.             preg_match_all('/\s*([0-9]+)\s*,\s*([0-9]+)\s*,*/',$coords[2][$i],$pts);
  1702.             $this->img->SetStartPoint($pts[1][count($pts[0])-1],$pts[2][count($pts[0])-1]);
  1703.             for ($j=0; $j<count($pts[0]); $j++) {
  1704.             $this->img->LineTo($pts[1][$j],$pts[2][$j]);
  1705.             }
  1706.         } else if ($coords[1][$i]=="rect") {
  1707.             $pts = preg_split('/,/', $coords[2][$i]);
  1708.             $this->img->SetStartPoint($pts[0],$pts[1]);
  1709.             $this->img->LineTo($pts[2],$pts[1]);
  1710.             $this->img->LineTo($pts[2],$pts[3]);
  1711.             $this->img->LineTo($pts[0],$pts[3]);
  1712.             $this->img->LineTo($pts[0],$pts[1]);                    
  1713.         }
  1714.         }
  1715.     }
  1716.     }
  1717.  
  1718.     function AdjustSaturationBrightnessContrast() {
  1719.     // Adjust the brightness and contrast of the image
  1720.     if( $this->image_contr || $this->image_bright )
  1721.         $this->img->AdjBrightContrast($this->image_bright,$this->image_contr);
  1722.     if( $this->image_sat )                                             
  1723.         $this->img->AdjSat($this->image_sat);
  1724.     }
  1725.  
  1726.     // Text scale offset in world coordinates
  1727.     function SetTextScaleOff($aOff) {
  1728.     $this->text_scale_off = $aOff;
  1729.     $this->xscale->text_scale_off = $aOff;
  1730.     }
  1731.     
  1732.     // Get min and max values for all included plots
  1733.     function GetPlotsYMinMax(&$aPlots) {
  1734.     list($xmax,$max) = $aPlots[0]->Max();
  1735.     list($xmin,$min) = $aPlots[0]->Min();
  1736.     for($i=0; $i<count($aPlots); ++$i ) {
  1737.         list($xmax,$ymax)=$aPlots[$i]->Max();
  1738.         list($xmin,$ymin)=$aPlots[$i]->Min();
  1739.         if (!is_string($ymax) || $ymax != "") $max=max($max,$ymax);
  1740.         if (!is_string($ymin) || $ymin != "") $min=min($min,$ymin);
  1741.     }
  1742.     if( $min == "" ) $min = 0;
  1743.     if( $max == "" ) $max = 0;
  1744.     if( $min == 0 && $max == 0 ) {
  1745.         // Special case if all values are 0
  1746.         $min=0;$max=1;            
  1747.     }
  1748.     return array($min,$max);
  1749.     }
  1750.  
  1751.     // Draw a frame around the image
  1752.     function StrokeFrame() {
  1753.     if( !$this->doframe ) return;
  1754.     if( $this->doshadow ) {
  1755.         $this->img->SetColor($this->frame_color);            
  1756.         if( $this->background_image_type <= 1 ) 
  1757.         $c = $this->margin_color;
  1758.         else
  1759.         $c = false;
  1760.         $this->img->ShadowRectangle(0,0,$this->img->width,$this->img->height,
  1761.                     $c,$this->shadow_width);
  1762.     }
  1763.     else {
  1764.         $this->img->SetLineWeight($this->frame_weight);
  1765.         if( $this->background_image_type <= 1 ) {
  1766.         $this->img->SetColor($this->margin_color);
  1767.         $this->img->FilledRectangle(0,0,$this->img->width-1,$this->img->height-1);        
  1768.         }
  1769.         $this->img->SetColor($this->frame_color);
  1770.         $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);        
  1771.     }
  1772.     }
  1773. } // Class
  1774.  
  1775.  
  1776. //===================================================
  1777. // CLASS TTF
  1778. // Description: Handle TTF font names
  1779. //===================================================
  1780. class TTF {
  1781.     var $font_files,$style_names;
  1782. //---------------
  1783. // CONSTRUCTOR
  1784.     function TTF() {
  1785.     $this->style_names=array(FS_NORMAL=>'normal',FS_BOLD=>'bold',FS_ITALIC=>'italic',FS_BOLDITALIC=>'bolditalic');
  1786.     // File names for available fonts
  1787.     $this->font_files=array(
  1788.         FF_COURIER => array(FS_NORMAL=>'cour', FS_BOLD=>'courbd', FS_ITALIC=>'couri', FS_BOLDITALIC=>'courbi' ),
  1789.         FF_GEORGIA => array(FS_NORMAL=>'georgia', FS_BOLD=>'georgiab', FS_ITALIC=>'georgiai', FS_BOLDITALIC=>'' ),
  1790.         FF_TREBUCHE =>array(FS_NORMAL=>'trebuc', FS_BOLD=>'trebucbd',   FS_ITALIC=>'trebucit', FS_BOLDITALIC=>'trebucbi' ),
  1791.         FF_VERDANA => array(FS_NORMAL=>'verdana', FS_BOLD=>'verdanab',  FS_ITALIC=>'verdanai', FS_BOLDITALIC=>'' ),
  1792.         FF_TIMES =>   array(FS_NORMAL=>'times',   FS_BOLD=>'timesbd',   FS_ITALIC=>'timesi',   FS_BOLDITALIC=>'timesbi' ),
  1793.         FF_COMIC =>   array(FS_NORMAL=>'comic',   FS_BOLD=>'comicbd',   FS_ITALIC=>'',         FS_BOLDITALIC=>'' ),
  1794.         FF_ARIAL =>   array(FS_NORMAL=>'arial',   FS_BOLD=>'arialbd',   FS_ITALIC=>'ariali',   FS_BOLDITALIC=>'arialbi' ) );
  1795.     }
  1796.  
  1797. //---------------
  1798. // PUBLIC METHODS    
  1799.     // Create the TTF file from the font specification
  1800.     function File($family,$style=FS_NORMAL) {
  1801.     
  1802.     if( $family == FF_HANDWRT || $family==FF_BOOK )
  1803.         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. Please download fonts from http://corefonts.sourceforge.net/');
  1804.  
  1805.     $fam = @$this->font_files[$family];
  1806.     if( !$fam ) JpGraphError::Raise("Specified TTF font family (id=$family) is unknown or does not exist. ".
  1807.                     "Please note that TTF fonts are not distributed with JpGraph for copyright reasons.". 
  1808.                     " You can find the MS TTF WEB-fonts (arial, courier etc) for download at ".
  1809.                     " http://corefonts.sourceforge.net/");
  1810.     $f = @$fam[$style];
  1811.  
  1812.     if( $f==='' )
  1813.         JpGraphError::Raise('Style "'.$this->style_names[$style].'" is not available for font family '.$this->font_files[$family][FS_NORMAL].'.');
  1814.     if( !$f )
  1815.         JpGraphError::Raise("Unknown font style specification [$fam].");
  1816.     $f = TTF_DIR.$f.'.ttf';
  1817.     if( !is_readable($f) ) {
  1818.         JpGraphError::Raise("Font file \"$f\" is not readable or does not exist.");
  1819.     }
  1820.     return $f;
  1821.     }
  1822. } // Class
  1823.  
  1824. //===================================================
  1825. // CLASS LineProperty
  1826. // Description: Holds properties for a line
  1827. //===================================================
  1828. class LineProperty {
  1829.     var $iWeight=1, $iColor="black",$iStyle="solid";
  1830.     var $iShow=true;
  1831.     
  1832. //---------------
  1833. // PUBLIC METHODS    
  1834.     function SetColor($aColor) {
  1835.     $this->iColor = $aColor;
  1836.     }
  1837.     
  1838.     function SetWeight($aWeight) {
  1839.     $this->iWeight = $aWeight;
  1840.     }
  1841.     
  1842.     function SetStyle($aStyle) {
  1843.     $this->iStyle = $aStyle;
  1844.     }
  1845.         
  1846.     function Show($aShow=true) {
  1847.     $this->iShow=$aShow;
  1848.     }
  1849.     
  1850.     function Stroke($aImg,$aX1,$aY1,$aX2,$aY2) {
  1851.     if( $this->iShow ) {
  1852.         $aImg->SetColor($this->iColor);
  1853.         $aImg->SetLineWeight($this->iWeight);
  1854.         $aImg->SetLineStyle($this->iStyle);            
  1855.         $aImg->StyleLine($aX1,$aY1,$aX2,$aY2);
  1856.     }
  1857.     }
  1858. }
  1859.  
  1860. //===================================================
  1861. // CLASS SuperScriptText
  1862. // Description: Format a superscript text
  1863. //===================================================
  1864. class SuperScriptText extends Text {
  1865.     var $iSuper="";
  1866.     var $sfont_family="",$sfont_style="",$sfont_size=8;
  1867.     var $iSuperMargin=2,$iVertOverlap=4,$iSuperScale=0.65;
  1868.     var $iSDir=0;
  1869.     var $iSimple=false;
  1870.  
  1871.     function SuperScriptText($aTxt="",$aSuper="",$aXAbsPos=0,$aYAbsPos=0) {
  1872.     parent::Text($aTxt,$aXAbsPos,$aYAbsPos);
  1873.     $this->iSuper = $aSuper;
  1874.     }
  1875.  
  1876.     function FromReal($aVal,$aPrecision=2) {
  1877.     // Convert a floating point number to scientific notation
  1878.     $neg=1.0;
  1879.     if( $aVal < 0 ) {
  1880.         $neg = -1.0;
  1881.         $aVal = -$aVal;
  1882.     }
  1883.         
  1884.     $l = floor(log10($aVal));
  1885.     $a = sprintf("%0.".$aPrecision."f",round($aVal / pow(10,$l),$aPrecision));
  1886.     $a *= $neg;
  1887.     if( $this->iSimple && ($a == 1 || $a==-1) ) $a = '';
  1888.     
  1889.     if( $a != '' )
  1890.         $this->t = $a.' * 10';
  1891.     else {
  1892.         if( $neg == 1 )
  1893.         $this->t = '10';
  1894.         else
  1895.         $this->t = '-10';
  1896.     }
  1897.     $this->iSuper = $l;
  1898.     }
  1899.  
  1900.     function Set($aTxt,$aSuper="") {
  1901.     $this->t = $aTxt;
  1902.     $this->iSuper = $aSuper;
  1903.     }
  1904.  
  1905.     function SetSuperFont($aFontFam,$aFontStyle=FS_NORMAL,$aFontSize=8) {
  1906.     $this->sfont_family = $aFontFam;
  1907.     $this->sfont_style = $aFontStyle;
  1908.     $this->sfont_size = $aFontSize;
  1909.     }
  1910.  
  1911.     // Total width of text
  1912.     function GetWidth(&$aImg) {
  1913.     $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  1914.     $w = $aImg->GetTextWidth($this->t);
  1915.     $aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size);
  1916.     $w += $aImg->GetTextWidth($this->iSuper);
  1917.     $w += $this->iSuperMargin;
  1918.     return $w;
  1919.     }
  1920.     
  1921.     // Hight of font (approximate the height of the text)
  1922.     function GetFontHeight(&$aImg) {
  1923.     $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);    
  1924.     $h = $aImg->GetFontHeight();
  1925.     $aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size);
  1926.     $h += $aImg->GetFontHeight();
  1927.     return $h;
  1928.     }
  1929.  
  1930.     // Hight of text
  1931.     function GetTextHeight(&$aImg) {
  1932.     $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  1933.     $h = $aImg->GetTextHeight($this->t);
  1934.     $aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size);
  1935.     $h += $aImg->GetTextHeight($this->iSuper);
  1936.     return $h;
  1937.     }
  1938.  
  1939.     function Stroke($aImg,$ax=-1,$ay=-1) {
  1940.     
  1941.         // To position the super script correctly we need different
  1942.     // cases to handle the alignmewnt specified since that will
  1943.     // determine how we can interpret the x,y coordinates
  1944.     
  1945.     $w = parent::GetWidth($aImg);
  1946.     $h = parent::GetTextHeight($aImg);
  1947.     switch( $this->valign ) {
  1948.         case 'top':
  1949.         $sy = $this->y;
  1950.         break;
  1951.         case 'center':
  1952.         $sy = $this->y - $h/2;
  1953.         break;
  1954.         case 'bottom':
  1955.         $sy = $this->y - $h;
  1956.         break;
  1957.         default:
  1958.         JpGraphError::Raise('PANIC: Internal error in SuperScript::Stroke(). Unknown vertical alignment for text');
  1959.         exit();
  1960.     }
  1961.  
  1962.     switch( $this->halign ) {
  1963.         case 'left':
  1964.         $sx = $this->x + $w;
  1965.         break;
  1966.         case 'center':
  1967.         $sx = $this->x + $w/2;
  1968.         break;
  1969.         case 'right':
  1970.         $sx = $this->x;
  1971.         break;
  1972.         default:
  1973.         JpGraphError::Raise('PANIC: Internal error in SuperScript::Stroke(). Unknown horizontal alignment for text');
  1974.         exit();
  1975.     }
  1976.  
  1977.     $sx += $this->iSuperMargin;
  1978.     $sy += $this->iVertOverlap;
  1979.  
  1980.     // Should we automatically determine the font or
  1981.     // has the user specified it explicetly?
  1982.     if( $this->sfont_family == "" ) {
  1983.         if( $this->font_family <= FF_FONT2 ) {
  1984.         if( $this->font_family == FF_FONT0 ) {
  1985.             $sff = FF_FONT0;
  1986.         }
  1987.         elseif( $this->font_family == FF_FONT1 ) {
  1988.             if( $this->font_style == FS_NORMAL )
  1989.             $sff = FF_FONT0;
  1990.             else
  1991.             $sff = FF_FONT1;
  1992.         }
  1993.         else {
  1994.             $sff = FF_FONT1;
  1995.         }
  1996.         $sfs = $this->font_style;
  1997.         $sfz = $this->font_size;
  1998.         }
  1999.         else {
  2000.         // TTF fonts
  2001.         $sff = $this->font_family;
  2002.         $sfs = $this->font_style;
  2003.         $sfz = floor($this->font_size*$this->iSuperScale);        
  2004.         if( $sfz < 8 ) $sfz = 8;
  2005.         }        
  2006.         $this->sfont_family = $sff;
  2007.         $this->sfont_style = $sfs;
  2008.         $this->sfont_size = $sfz;        
  2009.     } 
  2010.     else {
  2011.         $sff = $this->sfont_family;
  2012.         $sfs = $this->sfont_style;
  2013.         $sfz = $this->sfont_size;        
  2014.     }
  2015.  
  2016.     parent::Stroke($aImg,$ax,$ay);
  2017.  
  2018.  
  2019.     // For the builtin fonts we need to reduce the margins
  2020.     // since the bounding bx reported for the builtin fonts
  2021.     // are much larger than for the TTF fonts.
  2022.     if( $sff <= FF_FONT2 ) {
  2023.         $sx -= 2;
  2024.         $sy += 3;
  2025.     }
  2026.  
  2027.     $aImg->SetTextAlign('left','bottom');    
  2028.     $aImg->SetFont($sff,$sfs,$sfz);
  2029.     $aImg->PushColor($this->color);    
  2030.     $aImg->StrokeText($sx,$sy,$this->iSuper,$this->iSDir,'left');
  2031.     $aImg->PopColor();    
  2032.     }
  2033. }
  2034.  
  2035.  
  2036.  
  2037. //===================================================
  2038. // CLASS Text
  2039. // Description: Arbitrary text object that can be added to the graph
  2040. //===================================================
  2041. class Text {
  2042.     var $t,$x=0,$y=0,$halign="left",$valign="top",$color=array(0,0,0);
  2043.     var $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12;
  2044.     var $hide=false, $dir=0;
  2045.     var $boxed=false;    // Should the text be boxed
  2046.     var $paragraph_align="left";
  2047.     var $margin;
  2048.     var $icornerradius=0,$ishadowwidth=3;
  2049.  
  2050. //---------------
  2051. // CONSTRUCTOR
  2052.  
  2053.     // Create new text at absolute pixel coordinates
  2054.     function Text($aTxt="",$aXAbsPos=0,$aYAbsPos=0) {
  2055.     $this->t = $aTxt;
  2056.     $this->x = $aXAbsPos;
  2057.     $this->y = $aYAbsPos;
  2058.     $this->margin = 0;
  2059.     }
  2060. //---------------
  2061. // PUBLIC METHODS    
  2062.     // Set the string in the text object
  2063.     function Set($aTxt) {
  2064.     $this->t = $aTxt;
  2065.     }
  2066.     
  2067.     // Alias for Pos()
  2068.     function SetPos($aXAbsPos=0,$aYAbsPos=0,$aHAlign="left",$aVAlign="top") {
  2069.     $this->Pos($aXAbsPos,$aYAbsPos,$aHAlign,$aVAlign);
  2070.     }
  2071.     
  2072.     // Specify the position and alignment for the text object
  2073.     function Pos($aXAbsPos=0,$aYAbsPos=0,$aHAlign="left",$aVAlign="top") {
  2074.     $this->x = $aXAbsPos;
  2075.     $this->y = $aYAbsPos;
  2076.     $this->halign = $aHAlign;
  2077.     $this->valign = $aVAlign;
  2078.     }
  2079.     
  2080.     // Specify alignment for the text
  2081.     function Align($aHAlign,$aVAlign="top",$aParagraphAlign="") {
  2082.     $this->halign = $aHAlign;
  2083.     $this->valign = $aVAlign;
  2084.     if( $aParagraphAlign != "" )
  2085.         $this->paragraph_align = $aParagraphAlign;
  2086.     }        
  2087.  
  2088.     // Specifies the alignment for a multi line text
  2089.     function ParagraphAlign($aAlign) {
  2090.     $this->paragraph_align = $aAlign;
  2091.     }
  2092.  
  2093.     function SetShadow($aShadowColor='gray',$aShadowWidth=3) {
  2094.     $this->ishadowwidth=$aShadowWidth;
  2095.     $this->shadow=$aShadowColor;
  2096.     $this->boxed=true;
  2097.     }
  2098.     
  2099.     // Specify that the text should be boxed. fcolor=frame color, bcolor=border color,
  2100.     // $shadow=drop shadow should be added around the text.
  2101.     function SetBox($aFrameColor=array(255,255,255),$aBorderColor=array(0,0,0),$aShadowColor=false,$aCornerRadius=4,$aShadowWidth=3) {
  2102.     if( $aFrameColor==false )
  2103.         $this->boxed=false;
  2104.     else
  2105.         $this->boxed=true;
  2106.     $this->fcolor=$aFrameColor;
  2107.     $this->bcolor=$aBorderColor;
  2108.     // For backwards compatibility when shadow was just true or false
  2109.     if( $aShadowColor === true )
  2110.         $aShadowColor = 'gray';
  2111.     $this->shadow=$aShadowColor;
  2112.     $this->icornerradius=$aCornerRadius;
  2113.     $this->ishadowwidth=$aShadowWidth;
  2114.     }
  2115.     
  2116.     // Hide the text
  2117.     function Hide($aHide=true) {
  2118.     $this->hide=$aHide;
  2119.     }
  2120.     
  2121.     // This looks ugly since it's not a very orthogonal design 
  2122.     // but I added this "inverse" of Hide() to harmonize
  2123.     // with some classes which I designed more recently (especially) 
  2124.     // jpgraph_gantt
  2125.     function Show($aShow=true) {
  2126.     $this->hide=!$aShow;
  2127.     }
  2128.     
  2129.     // Specify font
  2130.     function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
  2131.     $this->font_family=$aFamily;
  2132.     $this->font_style=$aStyle;
  2133.     $this->font_size=$aSize;
  2134.     }
  2135.             
  2136.     // Center the text between $left and $right coordinates
  2137.     function Center($aLeft,$aRight,$aYAbsPos=false) {
  2138.     $this->x = $aLeft + ($aRight-$aLeft    )/2;
  2139.     $this->halign = "center";
  2140.     if( is_numeric($aYAbsPos) )
  2141.         $this->y = $aYAbsPos;        
  2142.     }
  2143.     
  2144.     // Set text color
  2145.     function SetColor($aColor) {
  2146.     $this->color = $aColor;
  2147.     }
  2148.     
  2149.     function SetAngle($aAngle) {
  2150.     $this->SetOrientation($aAngle);
  2151.     }
  2152.     
  2153.     // Orientation of text. Note only TTF fonts can have an arbitrary angle
  2154.     function SetOrientation($aDirection=0) {
  2155.     if( is_numeric($aDirection) )
  2156.         $this->dir=$aDirection;    
  2157.     elseif( $aDirection=="h" )
  2158.         $this->dir = 0;
  2159.     elseif( $aDirection=="v" )
  2160.         $this->dir = 90;
  2161.     else JpGraphError::Raise(" Invalid direction specified for text.");
  2162.     }
  2163.     
  2164.     // Total width of text
  2165.     function GetWidth(&$aImg) {
  2166.     $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  2167.     $w = $aImg->GetTextWidth($this->t);
  2168.     return $w;    
  2169.     }
  2170.     
  2171.     // Hight of font
  2172.     function GetFontHeight(&$aImg) {
  2173.     $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  2174.     $h = $aImg->GetFontHeight();
  2175.     return $h;
  2176.  
  2177.     }
  2178.  
  2179.     function GetTextHeight(&$aImg) {
  2180.     $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);    
  2181.     $h = $aImg->GetTextHeight($this->t);
  2182.     return $h;
  2183.     }
  2184.  
  2185.     // Set the margin which will be interpretaed differently depending
  2186.     // on the context.
  2187.     function SetMargin($aMarg) {
  2188.     $this->margin = $aMarg;
  2189.     }
  2190.     
  2191.     // Display text in image
  2192.     function Stroke($aImg,$x=null,$y=null) {
  2193.  
  2194.     if( !empty($x) ) $this->x = $x;
  2195.     if( !empty($y) ) $this->y = $y;
  2196.  
  2197.     // If position been given as a fraction of the image size
  2198.     // calculate the absolute position
  2199.     if( $this->x < 1 && $this->x > 0 ) $this->x *= $aImg->width;
  2200.     if( $this->y < 1 && $this->y > 0 ) $this->y *= $aImg->height;
  2201.  
  2202.     $aImg->PushColor($this->color);    
  2203.     $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  2204.     $aImg->SetTextAlign($this->halign,$this->valign);
  2205.     if( $this->boxed ) {
  2206.         if( $this->fcolor=="nofill" ) 
  2207.         $this->fcolor=false;        
  2208.         $aImg->StrokeBoxedText($this->x,$this->y,$this->t,
  2209.                    $this->dir,$this->fcolor,$this->bcolor,$this->shadow,
  2210.                    $this->paragraph_align,6,2,$this->icornerradius,
  2211.                    $this->ishadowwidth);
  2212.     }
  2213.     else {
  2214.         $aImg->StrokeText($this->x,$this->y,$this->t,$this->dir,
  2215.                   $this->paragraph_align);
  2216.     }
  2217.     $aImg->PopColor($this->color);    
  2218.     }
  2219. } // Class
  2220.  
  2221.  
  2222. //===================================================
  2223. // CLASS Grid
  2224. // Description: responsible for drawing grid lines in graph
  2225. //===================================================
  2226. class Grid {
  2227.     var $img;
  2228.     var $scale;
  2229.     var $grid_color=array(196,196,196);
  2230.     var $type="solid";
  2231.     var $show=false, $showMinor=false,$weight=1;
  2232. //---------------
  2233. // CONSTRUCTOR
  2234.     function Grid(&$aAxis) {
  2235.     $this->scale = &$aAxis->scale;
  2236.     $this->img = &$aAxis->img;
  2237.     }
  2238. //---------------
  2239. // PUBLIC METHODS
  2240.     function SetColor($aColor) {
  2241.     $this->grid_color=$aColor;
  2242.     }
  2243.     
  2244.     function SetWeight($aWeight) {
  2245.     $this->weight=$aWeight;
  2246.     }
  2247.     
  2248.     // Specify if grid should be dashed, dotted or solid
  2249.     function SetLineStyle($aType) {
  2250.     $this->type = $aType;
  2251.     }
  2252.     
  2253.     // Decide if both major and minor grid should be displayed
  2254.     function Show($aShowMajor=true,$aShowMinor=false) {
  2255.     $this->show=$aShowMajor;
  2256.     $this->showMinor=$aShowMinor;
  2257.     }
  2258.     
  2259.     // Display the grid
  2260.     function Stroke() {
  2261.     if( $this->showMinor ) 
  2262.         $this->DoStroke($this->scale->ticks->ticks_pos);
  2263.     else
  2264.         $this->DoStroke($this->scale->ticks->maj_ticks_pos);
  2265.     }
  2266.     
  2267. //--------------
  2268. // Private methods    
  2269.     // Draw the grid
  2270.     function DoStroke(&$aTicksPos) {
  2271.     if( !$this->show )
  2272.         return;    
  2273.     $this->img->SetColor($this->grid_color);
  2274.     $this->img->SetLineWeight($this->weight);
  2275.     $nbrgrids = count($aTicksPos);                    
  2276.     if( $this->scale->type=="y" ) {
  2277.         $xl=$this->img->left_margin;
  2278.         $xr=$this->img->width-$this->img->right_margin;
  2279.         for($i=0; $i<$nbrgrids; ++$i) {
  2280.         $y=$aTicksPos[$i];
  2281.         if( $this->type == "solid" )
  2282.             $this->img->Line($xl,$y,$xr,$y);
  2283.         elseif( $this->type == "dotted" )
  2284.             $this->img->DashedLine($xl,$y,$xr,$y,1,6);
  2285.         elseif( $this->type == "dashed" )
  2286.             $this->img->DashedLine($xl,$y,$xr,$y,2,4);
  2287.         elseif( $this->type == "longdashed" )
  2288.             $this->img->DashedLine($xl,$y,$xr,$y,8,6);
  2289.         }
  2290.     }
  2291.                 
  2292.     if( $this->scale->type=="x" ) {    
  2293.         $yu=$this->img->top_margin;
  2294.         $yl=$this->img->height-$this->img->bottom_margin;
  2295.         $x=$aTicksPos[0];
  2296.         $limit=$this->img->width-$this->img->right_margin;
  2297.         $i=0;
  2298.         // We must also test for limit since we might have
  2299.         // an offset and the number of ticks is calculated with
  2300.         // assumption offset==0 so we might end up drawing one
  2301.         // to many gridlines
  2302.         while( $x<=$limit && $i<count($aTicksPos) ) {
  2303.         $x=$aTicksPos[$i];
  2304.         if( $this->type == "solid" )                
  2305.             $this->img->Line($x,$yl,$x,$yu);
  2306.         elseif( $this->type == "dotted" )
  2307.             $this->img->DashedLine($x,$yl,$x,$yu,1,6);
  2308.         elseif( $this->type == "dashed" )
  2309.             $this->img->DashedLine($x,$yl,$x,$yu,2,4);
  2310.         elseif( $this->type == "longdashed" )
  2311.             $this->img->DashedLine($x,$yl,$x,$yu,8,6);    
  2312.         ++$i;                                    
  2313.         }
  2314.     }        
  2315.     return true;
  2316.     }
  2317. } // Class
  2318.  
  2319. //===================================================
  2320. // CLASS Axis
  2321. // Description: Defines X and Y axis. Notes that at the
  2322. // moment the code is not really good since the axis on
  2323. // several occasion must know wheter it's an X or Y axis.
  2324. // This was a design decision to make the code easier to
  2325. // follow. 
  2326. //===================================================
  2327. class Axis {
  2328.     var $pos = false;
  2329.     var $weight=1;
  2330.     var $color=array(0,0,0),$label_color=array(0,0,0);
  2331.     var $img=null,$scale=null; 
  2332.     var $hide=false;
  2333.     var $ticks_label=false;
  2334.     var $show_first_label=true,$show_last_label=true;
  2335.     var $label_step=1; // Used by a text axis to specify what multiple of major steps
  2336.     // should be labeled.
  2337.     var $tick_step=1;
  2338.     var $labelPos=0;   // Which side of the axis should the labels be?
  2339.     var $title=null,$title_adjust,$title_margin,$title_side=SIDE_LEFT;
  2340.     var $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12,$label_angle=0;
  2341.     var $tick_label_margin=5;
  2342.     var $label_halign = '',$label_valign = '', $label_para_align='left';
  2343.     var $hide_line=false;
  2344.     //var $hide_zero_label=false;
  2345.  
  2346. //---------------
  2347. // CONSTRUCTOR
  2348.     function Axis(&$img,&$aScale,$color=array(0,0,0)) {
  2349.     $this->img = &$img;
  2350.     $this->scale = &$aScale;
  2351.     $this->color = $color;
  2352.     $this->title=new Text("");
  2353.         
  2354.     if( $aScale->type=="y" ) {
  2355.         $this->title_margin = 25;
  2356.         $this->title_adjust="middle";
  2357.         $this->title->SetOrientation(90);
  2358.         $this->tick_label_margin=7;
  2359.         $this->labelPos=SIDE_LEFT;
  2360.         //$this->SetLabelFormat('%.1f');
  2361.     }
  2362.     else {
  2363.         $this->title_margin = 5;
  2364.         $this->title_adjust="high";
  2365.         $this->title->SetOrientation(0);            
  2366.         $this->tick_label_margin=3;
  2367.         $this->labelPos=SIDE_DOWN;
  2368.         //$this->SetLabelFormat('%.0f');
  2369.     }
  2370.     }
  2371. //---------------
  2372. // PUBLIC METHODS    
  2373.     
  2374.     function SetLabelFormat($aFormStr) {
  2375.     $this->scale->ticks->SetLabelFormat($aFormStr);
  2376.     }
  2377.     
  2378.     function SetLabelFormatString($aFormStr) {
  2379.     $this->scale->ticks->SetLabelFormat($aFormStr);
  2380.     }
  2381.     
  2382.     function SetLabelFormatCallback($aFuncName) {
  2383.     $this->scale->ticks->SetFormatCallback($aFuncName);
  2384.     }
  2385.  
  2386.     function SetLabelAlign($aHAlign,$aVAlign="top",$aParagraphAlign='left') {
  2387.     $this->label_halign = $aHAlign;
  2388.     $this->label_valign = $aVAlign;
  2389.     $this->label_para_align = $aParagraphAlign;
  2390.     }        
  2391.  
  2392.     // Don't display the first label
  2393.     function HideFirstTickLabel($aShow=false) {
  2394.     $this->show_first_label=$aHide;
  2395.     }
  2396.  
  2397.     function HideLastTickLabel($aShow=false) {
  2398.     $this->show_last_label=$aHide;
  2399.     }
  2400.  
  2401.     function HideTicks($aHideMinor=true,$aHideMajor=true) {
  2402.     $this->scale->ticks->SupressMinorTickMarks($aHideMinor);
  2403.     $this->scale->ticks->SupressTickMarks($aHideMajor);
  2404.     }
  2405.  
  2406.     // Hide zero label
  2407.     function HideZeroLabel($aFlag=true) {
  2408.     $this->scale->ticks->SupressZeroLabel();
  2409.     //$this->hide_zero_label = $aFlag;
  2410.     }
  2411.     
  2412.     function HideFirstLastLabel() {
  2413.     // The two first calls to ticks method will supress 
  2414.     // automatically generated scale values. However, that
  2415.     // will not affect manually specified value, e.g text-scales.
  2416.     // therefor we also make a kludge here to supress manually
  2417.     // specified scale labels.
  2418.     $this->scale->ticks->SupressLast();
  2419.     $this->scale->ticks->SupressFirst();
  2420.     $this->show_first_label    = false;
  2421.     $this->show_last_label = false;
  2422.     }
  2423.     
  2424.     // Hide the axis
  2425.     function Hide($aHide=true) {
  2426.     $this->hide=$aHide;
  2427.     }
  2428.  
  2429.     // Hide the actual axis-line, but still print the labels
  2430.     function HideLine($aHide=true) {
  2431.     $this->hide_line = $aHide;
  2432.     }
  2433.  
  2434.     // Weight of axis
  2435.     function SetWeight($aWeight) {
  2436.     $this->weight = $aWeight;
  2437.     }
  2438.  
  2439.     // Axis color
  2440.     function SetColor($aColor,$aLabelColor=false) {
  2441.     $this->color = $aColor;
  2442.     if( !$aLabelColor ) $this->label_color = $aColor;
  2443.     else $this->label_color = $aLabelColor;
  2444.     }
  2445.     
  2446.     // Title on axis
  2447.     function SetTitle($aTitle,$aAdjustAlign="high") {
  2448.     $this->title->Set($aTitle);
  2449.     $this->title_adjust=$aAdjustAlign;
  2450.     }
  2451.     
  2452.     // Specify distance from the axis
  2453.     function SetTitleMargin($aMargin) {
  2454.     $this->title_margin=$aMargin;
  2455.     }
  2456.     
  2457.     // Which side of the axis should the axis title be?
  2458.     function SetTitleSide($aSideOfAxis) {
  2459.     $this->title_side = $aSideOfAxis;
  2460.     }
  2461.  
  2462.     // Utility function to set the direction for tick marks
  2463.     function SetTickDirection($aDir) {
  2464.         // Will be deprecated from 1.7        
  2465.         if( ERR_DEPRECATED )
  2466.         JpGraphError::Raise('Axis::SetTickDirection() is deprecated. Use Axis::SetTickSide() instead');
  2467.     $this->scale->ticks->SetSide($aDir);
  2468.     }
  2469.     
  2470.     function SetTickSide($aDir) {
  2471.     $this->scale->ticks->SetSide($aDir);
  2472.     }
  2473.     
  2474.     // Specify text labels for the ticks. One label for each data point
  2475.     function SetTickLabels($aLabelArray) {
  2476.     $this->ticks_label = $aLabelArray;
  2477.     }
  2478.     
  2479.     // How far from the axis should the labels be drawn
  2480.     function SetTickLabelMargin($aMargin) {
  2481.     if( ERR_DEPRECATED )        
  2482.         JpGraphError::Raise('SetTickLabelMargin() is deprecated. Use Axis::SetLabelMargin() instead.');
  2483.           $this->tick_label_margin=$aMargin;
  2484.     }
  2485.  
  2486.     function SetLabelMargin($aMargin) {
  2487.     $this->tick_label_margin=$aMargin;
  2488.     }
  2489.     
  2490.     // Specify that every $step of the ticks should be displayed starting
  2491.     // at $start
  2492.     // DEPRECATED FUNCTION: USE SetTextTickInterval() INSTEAD
  2493.     function SetTextTicks($step,$start=0) {
  2494.     JpGraphError::Raise(" SetTextTicks() is deprecated. Use SetTextTickInterval() instead.");        
  2495.     }
  2496.  
  2497.     // Specify that every $step of the ticks should be displayed starting
  2498.     // at $start    
  2499.     function SetTextTickInterval($aStep,$aStart=0) {
  2500.     $this->scale->ticks->SetTextLabelStart($aStart);
  2501.     $this->tick_step=$aStep;
  2502.     }
  2503.      
  2504.     // Specify that every $step tick mark should have a label 
  2505.     // should be displayed starting
  2506.     function SetTextLabelInterval($aStep) {
  2507.     if( $aStep < 1 )
  2508.         JpGraphError::Raise(" Text label interval must be specified >= 1.");
  2509.     $this->label_step=$aStep;
  2510.     }
  2511.     
  2512.     // Which side of the axis should the labels be on?
  2513.     function SetLabelPos($aSidePos) {
  2514.         // This will be deprecated from 1.7
  2515.     if( ERR_DEPRECATED )        
  2516.         JpGraphError::Raise('SetLabelPos() is deprecated. Use Axis::SetLabelSide() instead.');
  2517.     $this->labelPos=$aSidePos;
  2518.     }
  2519.     
  2520.     function SetLabelSide($aSidePos) {
  2521.     $this->labelPos=$aSidePos;
  2522.     }
  2523.  
  2524.     // Set the font
  2525.     function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
  2526.     $this->font_family = $aFamily;
  2527.     $this->font_style = $aStyle;
  2528.     $this->font_size = $aSize;
  2529.     }
  2530.  
  2531.     // Position for axis line on the "other" scale
  2532.     function SetPos($aPosOnOtherScale) {
  2533.     $this->pos=$aPosOnOtherScale;
  2534.     }
  2535.     
  2536.     // Specify the angle for the tick labels
  2537.     function SetLabelAngle($aAngle) {
  2538.     $this->label_angle = $aAngle;
  2539.     }    
  2540.     
  2541.     // Stroke the axis.
  2542.     function Stroke($aOtherAxisScale) {        
  2543.     if( $this->hide ) return;        
  2544.     if( is_numeric($this->pos) ) {
  2545.         $pos=$aOtherAxisScale->Translate($this->pos);
  2546.     }
  2547.     else {    // Default to minimum of other scale if pos not set        
  2548.         if( ($aOtherAxisScale->GetMinVal() >= 0 && $this->pos==false) || $this->pos=="min" ) {
  2549.         $pos = $aOtherAxisScale->scale_abs[0];
  2550.         }
  2551.         elseif($this->pos == "max") {
  2552.         $pos = $aOtherAxisScale->scale_abs[1];
  2553.         }
  2554.         else { // If negative set x-axis at 0
  2555.         $this->pos=0;
  2556.         $pos=$aOtherAxisScale->Translate(0);
  2557.         }
  2558.     }    
  2559.     $this->img->SetLineWeight($this->weight);
  2560.     $this->img->SetColor($this->color);        
  2561.     $this->img->SetFont($this->font_family,$this->font_style,$this->font_size);
  2562.     if( $this->scale->type == "x" ) {
  2563.         if( !$this->hide_line ) 
  2564.         $this->img->FilledRectangle($this->img->left_margin,$pos,
  2565.                         $this->img->width-$this->img->right_margin,$pos+$this->weight-1);
  2566.         $y=$pos+$this->img->GetFontHeight()+$this->title_margin+$this->title->margin;
  2567.         if( $this->title_adjust=="high" )
  2568.         $this->title->Pos($this->img->width-$this->img->right_margin,$y,"right","top");
  2569.         elseif( $this->title_adjust=="middle" || $this->title_adjust=="center" ) 
  2570.         $this->title->Pos(($this->img->width-$this->img->left_margin-$this->img->right_margin)/2+$this->img->left_margin,$y,"center","top");
  2571.         elseif($this->title_adjust=="low")
  2572.         $this->title->Pos($this->img->left_margin,$y,"left","top");
  2573.         else {    
  2574.         JpGraphError::Raise('Unknown alignment specified for X-axis title. ('.$this->title_adjust.')');
  2575.         }
  2576.     }
  2577.     elseif( $this->scale->type == "y" ) {
  2578.         // Add line weight to the height of the axis since
  2579.         // the x-axis could have a width>1 and we want the axis to fit nicely together.
  2580.         if( !$this->hide_line ) 
  2581.         $this->img->FilledRectangle($pos-$this->weight+1,$this->img->top_margin,
  2582.                         $pos,$this->img->height-$this->img->bottom_margin+$this->weight-1);
  2583.         $x=$pos ;
  2584.         if( $this->title_side == SIDE_LEFT ) {
  2585.         $x -= $this->title_margin;
  2586.         $x -= $this->title->margin;
  2587.         $halign="right";
  2588.         }
  2589.         else {
  2590.         $x += $this->title_margin;
  2591.         $x += $this->title->margin;
  2592.         $halign="left";
  2593.         }
  2594.         // If the user has manually specified an hor. align
  2595.         // then we override the automatic settings with this
  2596.         // specifed setting. Since default is 'left' we compare
  2597.         // with that. (This means a manually set 'left' align
  2598.         // will have no effect.)
  2599.         if( $this->title->halign != 'left' ) 
  2600.         $halign = $this->title->halign;
  2601.         if( $this->title_adjust=="high" ) 
  2602.         $this->title->Pos($x,$this->img->top_margin,$halign,"top"); 
  2603.         elseif($this->title_adjust=="middle" || $this->title_adjust=="center")  
  2604.         $this->title->Pos($x,($this->img->height-$this->img->top_margin-$this->img->bottom_margin)/2+$this->img->top_margin,$halign,"center");
  2605.         elseif($this->title_adjust=="low")
  2606.         $this->title->Pos($x,$this->img->height-$this->img->bottom_margin,$halign,"bottom");
  2607.         else    
  2608.         JpGraphError::Raise('Unknown alignment specified for Y-axis title. ('.$this->title_adjust.')');
  2609.         
  2610.     }
  2611.     $this->scale->ticks->Stroke($this->img,$this->scale,$pos);
  2612.     $this->StrokeLabels($pos);
  2613.     $this->title->Stroke($this->img);
  2614.     }
  2615.  
  2616. //---------------
  2617. // PRIVATE METHODS    
  2618.     // Draw all the tick labels on major tick marks
  2619.     function StrokeLabels($aPos,$aMinor=false) {
  2620.  
  2621.     $this->img->SetColor($this->label_color);
  2622.     $this->img->SetFont($this->font_family,$this->font_style,$this->font_size);
  2623.     $yoff=$this->img->GetFontHeight()/2;
  2624.  
  2625.     // Only draw labels at major tick marks
  2626.     $nbr = count($this->scale->ticks->maj_ticks_label);
  2627.  
  2628.     // We have the option to not-display the very first mark
  2629.     // (Usefull when the first label might interfere with another
  2630.     // axis.)
  2631.     $i = $this->show_first_label ? 0 : 1 ;
  2632.     if( !$this->show_last_label ) --$nbr;
  2633.     // Now run through all labels making sure we don't overshoot the end
  2634.     // of the scale.    
  2635.     while( $i<$nbr ) {
  2636.         // $tpos holds the absolute text position for the label
  2637.         $tpos=$this->scale->ticks->maj_ticklabels_pos[$i];
  2638.  
  2639.         // Note. the $limit is only used for the x axis since we
  2640.         // might otherwise overshoot if the scale has been centered
  2641.         // This is due to us "loosing" the last tick mark if we center.
  2642.         if( $this->scale->type=="x" && $tpos > $this->img->width-$this->img->right_margin+1 ) {
  2643.             return; 
  2644.         }
  2645.         // we only draw every $label_step label
  2646.         if( ($i % $this->label_step)==0 ) {
  2647.         // If the label has been specified use that and in other case
  2648.         // just label the mark with the actual scale value 
  2649.         $m=$this->scale->ticks->GetMajor();
  2650.                 
  2651.         // ticks_label has an entry for each data point and is the array
  2652.         // that holds the labels set by the user. If the user hasn't 
  2653.         // specified any values we use whats in the automatically asigned
  2654.         // labels in the maj_ticks_label
  2655.         if( isset($this->ticks_label[$i*$m]) )
  2656.             $label=$this->ticks_label[$i*$m];
  2657.         else {
  2658.             $label=$this->scale->ticks->maj_ticks_label[$i];
  2659.             if( $this->scale->textscale ) {
  2660.             ++$label;
  2661.             
  2662.             }
  2663.         }
  2664.                     
  2665.         //if( $this->hide_zero_label && $label==0.0 ) {
  2666.         //    ++$i;
  2667.         //    continue;
  2668.         //}                    
  2669.                     
  2670.         if( $this->scale->type == "x" ) {
  2671.             if( $this->labelPos == SIDE_DOWN ) {
  2672.             if( $this->label_angle==0 || $this->label_angle==90 ) {
  2673.                 if( $this->label_halign=='' && $this->label_valign=='')
  2674.                 $this->img->SetTextAlign('center','top');
  2675.                 else
  2676.                     $this->img->SetTextAlign($this->label_halign,$this->label_valign);
  2677.                 
  2678.             }
  2679.             else {
  2680.                 if( $this->label_halign=='' && $this->label_valign=='')
  2681.                 $this->img->SetTextAlign("right","top");
  2682.                 else
  2683.                 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
  2684.             }
  2685.  
  2686.             $this->img->StrokeText($tpos,$aPos+$this->tick_label_margin,$label,
  2687.                            $this->label_angle,$this->label_para_align);
  2688.             }
  2689.             else {
  2690.             if( $this->label_angle==0 || $this->label_angle==90 ) {
  2691.                 if( $this->label_halign=='' && $this->label_valign=='')
  2692.                 $this->img->SetTextAlign("center","bottom");
  2693.                 else
  2694.                     $this->img->SetTextAlign($this->label_halign,$this->label_valign);
  2695.             }
  2696.             else {
  2697.                 if( $this->label_halign=='' && $this->label_valign=='')
  2698.                 $this->img->SetTextAlign("right","bottom");
  2699.                 else
  2700.                     $this->img->SetTextAlign($this->label_halign,$this->label_valign);
  2701.             }
  2702.             $this->img->StrokeText($tpos,$aPos-$this->tick_label_margin,$label,
  2703.                            $this->label_angle,$this->label_para_align);
  2704.             }
  2705.         }
  2706.         else {
  2707.             // scale->type == "y"
  2708.             //if( $this->label_angle!=0 ) 
  2709.             //JpGraphError::Raise(" Labels at an angle are not supported on Y-axis");
  2710.             if( $this->labelPos == SIDE_LEFT ) { // To the left of y-axis                    
  2711.             if( $this->label_halign=='' && $this->label_valign=='')    
  2712.                 $this->img->SetTextAlign("right","center");
  2713.             else
  2714.                 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
  2715.             $this->img->StrokeText($aPos-$this->tick_label_margin,$tpos,$label,$this->label_angle,$this->label_para_align);    
  2716.             }
  2717.             else { // To the right of the y-axis
  2718.             if( $this->label_halign=='' && $this->label_valign=='')    
  2719.                 $this->img->SetTextAlign("left","center");
  2720.             else
  2721.                 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
  2722.             $this->img->StrokeText($aPos+$this->tick_label_margin,$tpos,$label,$this->label_angle,$this->label_para_align);    
  2723.             }
  2724.         }
  2725.         }
  2726.         ++$i;    
  2727.     }                                
  2728.     }            
  2729.  
  2730. } // Class
  2731.  
  2732. //===================================================
  2733. // CLASS Ticks
  2734. // Description: Abstract base class for drawing linear and logarithmic
  2735. // tick marks on axis
  2736. //===================================================
  2737. class Ticks {
  2738.     var $minor_abs_size=3, $major_abs_size=5;
  2739.     var $direction=1; // Should ticks be in(=1) the plot area or outside (=-1)?
  2740.     var $scale;
  2741.     var $is_set=false;
  2742.     var $precision=-1;
  2743.     var $supress_zerolabel=false,$supress_first=false;
  2744.     var $supress_last=false,$supress_tickmarks=false,$supress_minor_tickmarks=false;
  2745.     var $mincolor="",$majcolor="";
  2746.     var $weight=1;
  2747.     var $label_formatstr='';   // C-style format string to use for labels
  2748.     var $label_formfunc='';
  2749.  
  2750.  
  2751. //---------------
  2752. // CONSTRUCTOR
  2753.     function Ticks(&$aScale) {
  2754.     $this->scale=&$aScale;
  2755.     }
  2756.  
  2757. //---------------
  2758. // PUBLIC METHODS    
  2759.     // Set format string for automatic labels
  2760.     function SetLabelFormat($aFormatString) {
  2761.     $this->label_formatstr=$aFormatString;
  2762.     }
  2763.     
  2764.     function SetFormatCallback($aCallbackFuncName) {
  2765.     $this->label_formfunc = $aCallbackFuncName;
  2766.     }
  2767.     
  2768.     // Don't display the first zero label
  2769.     function SupressZeroLabel($aFlag=true) {
  2770.     $this->supress_zerolabel=$aFlag;
  2771.     }
  2772.     
  2773.     // Don't display minor tick marks
  2774.     function SupressMinorTickMarks($aHide=true) {
  2775.     $this->supress_minor_tickmarks=$aHide;
  2776.     }
  2777.     
  2778.     // Don't display major tick marks
  2779.     function SupressTickMarks($aHide=true) {
  2780.     $this->supress_tickmarks=$aHide;
  2781.     }
  2782.     
  2783.     // Hide the first tick mark
  2784.     function SupressFirst($aHide=true) {
  2785.     $this->supress_first=$aHide;
  2786.     }
  2787.     
  2788.     // Hide the last tick mark
  2789.     function SupressLast($aHide=true) {
  2790.     $this->supress_last=$aHide;
  2791.     }
  2792.  
  2793.     // Size (in pixels) of minor tick marks
  2794.     function GetMinTickAbsSize() {
  2795.     return $this->minor_abs_size;
  2796.     }
  2797.     
  2798.     // Size (in pixels) of major tick marks
  2799.     function GetMajTickAbsSize() {
  2800.     return $this->major_abs_size;        
  2801.     }
  2802.     
  2803.     // Have the ticks been specified
  2804.     function IsSpecified() {
  2805.     return $this->is_set;
  2806.     }
  2807.     
  2808.     // Set the distance between major and minor tick marks
  2809.     function Set($aMaj,$aMin) {
  2810.     // "Virtual method"
  2811.     // Should be implemented by the concrete subclass
  2812.     // if any action is wanted.
  2813.     }
  2814.     
  2815.     // Specify number of decimals in automatic labels
  2816.     // Deprecated from 1.4. Use SetFormatString() instead
  2817.     function SetPrecision($aPrecision) {     
  2818.         if( ERR_DEPRECATED )
  2819.         JpGraphError::Raise('Ticks::SetPrecision() is deprecated. Use Ticks::SetLabelFormat() (or Ticks::SetFormatCallback()) instead');
  2820.     $this->precision=$aPrecision;
  2821.     }
  2822.  
  2823.     function SetSide($aSide) {
  2824.     $this->direction=$aSide;
  2825.     }
  2826.     
  2827.     // Which side of the axis should the ticks be on
  2828.     function SetDirection($aSide=SIDE_RIGHT) {
  2829.     $this->direction=$aSide;
  2830.     }
  2831.     
  2832.     // Set colors for major and minor tick marks
  2833.     function SetMarkColor($aMajorColor,$aMinorColor="") {
  2834.     $this->SetColor($aMajorColor,$aMinorColor);
  2835.     }
  2836.     
  2837.     function SetColor($aMajorColor,$aMinorColor="") {
  2838.     $this->majcolor=$aMajorColor;
  2839.         
  2840.     // If not specified use same as major
  2841.     if( $aMinorColor=="" ) 
  2842.         $this->mincolor=$aMajorColor;
  2843.     else
  2844.         $this->mincolor=$aMinorColor;
  2845.     }
  2846.     
  2847.     function SetWeight($aWeight) {
  2848.     $this->weight=$aWeight;
  2849.     }
  2850.     
  2851. } // Class
  2852.  
  2853. //===================================================
  2854. // CLASS LinearTicks
  2855. // Description: Draw linear ticks on axis
  2856. //===================================================
  2857. class LinearTicks extends Ticks {
  2858.     var $minor_step=1, $major_step=2;
  2859.     var $xlabel_offset=0,$xtick_offset=0;
  2860.     var $label_offset=0; // What offset should the displayed label have
  2861.     // i.e should we display 0,1,2 or 1,2,3,4 or 2,3,4 etc
  2862.     var $text_label_start=0;
  2863. //---------------
  2864. // CONSTRUCTOR
  2865.     function LinearTicks() {
  2866.     // Empty
  2867.     }
  2868.  
  2869. //---------------
  2870. // PUBLIC METHODS    
  2871.     
  2872.     
  2873.     // Return major step size in world coordinates
  2874.     function GetMajor() {
  2875.     return $this->major_step;
  2876.     }
  2877.     
  2878.     // Return minor step size in world coordinates
  2879.     function GetMinor() {
  2880.     return $this->minor_step;
  2881.     }
  2882.     
  2883.     // Set Minor and Major ticks (in world coordinates)
  2884.     function Set($aMajStep,$aMinStep=false) {
  2885.     if( $aMinStep==false ) 
  2886.         $aMinStep=$aMajStep;
  2887.         
  2888.     if( $aMajStep <= 0 || $aMinStep <= 0 ) {
  2889.         JpGraphError::Raise(" Minor or major step size is 0. Check that you haven't
  2890.                 got an accidental SetTextTicks(0) in your code.<p>
  2891.                 If this is not the case you might have stumbled upon a bug in JpGraph.
  2892.                 Please report this and if possible include the data that caused the
  2893.                 problem.");
  2894.     }
  2895.         
  2896.     $this->major_step=$aMajStep;
  2897.     $this->minor_step=$aMinStep;
  2898.     $this->is_set = true;
  2899.     }
  2900.  
  2901.     // Draw linear ticks
  2902.     function Stroke(&$img,&$scale,$pos) {
  2903.     $maj_step_abs = $scale->scale_factor*$this->major_step;        
  2904.     $min_step_abs = $scale->scale_factor*$this->minor_step;        
  2905.  
  2906.     if( $min_step_abs==0 || $maj_step_abs==0 ) 
  2907.         JpGraphError::Raise(" A plot has an illegal scale. This could for example be 
  2908.             that you are trying to use text autoscaling to draw a line plot with only one point 
  2909.             or similair abnormality (a line needs two points!).");
  2910.     $limit = $scale->scale_abs[1];    
  2911.     $nbrmajticks=floor(1.000001*(($scale->GetMaxVal()-$scale->GetMinVal())/$this->major_step))+1;
  2912.     $first=0;
  2913.         
  2914.     // If precision hasn't been specified set it to a sensible value
  2915.     if( $this->precision==-1 ) { 
  2916.         $t = log10($this->minor_step);
  2917.         if( $t > 0 )
  2918.         $precision = 0;
  2919.         else
  2920.         $precision = -floor($t);
  2921.     }
  2922.     else
  2923.         $precision = $this->precision;
  2924.             
  2925.     $img->SetLineWeight($this->weight);            
  2926.         
  2927.     // Handle ticks on X-axis
  2928.     if( $scale->type == "x" ) {
  2929.         // Draw the major tick marks
  2930.             
  2931.         $yu = $pos - $this->direction*$this->GetMajTickAbsSize();
  2932.             
  2933.         // TODO: Add logic to set label_offset for text labels
  2934.         $label = (float)$scale->GetMinVal()+$this->text_label_start+$this->label_offset;    
  2935.             
  2936.         $start_abs=$scale->scale_factor*$this->text_label_start;
  2937.             
  2938.         $nbrmajticks=ceil(($scale->GetMaxVal()-$scale->GetMinVal()-$this->text_label_start )/$this->major_step)+1;    
  2939.         for( $i=0; $label<=$scale->GetMaxVal()+$this->label_offset; ++$i ) {
  2940.         $x=$scale->scale_abs[0]+$start_abs+$i*$maj_step_abs+$this->xlabel_offset*$min_step_abs;    
  2941.         $this->maj_ticklabels_pos[$i]=ceil($x);                
  2942.  
  2943.         // Apply format
  2944.         if( $this->label_formfunc != "" ) {
  2945.             $f=$this->label_formfunc;
  2946.             $l = $f($label);
  2947.         }    
  2948.         elseif( $this->label_formatstr != "" ) 
  2949.             $l = sprintf($this->label_formatstr,$label);
  2950.         else {
  2951.             $v = round($label,$precision);
  2952.             $l = sprintf("%01.".$precision."f",$v);
  2953.         }
  2954.                     
  2955.         if( ($this->supress_zerolabel && ($l + 0)==0) || ($this->supress_first && $i==0) ||
  2956.             ($this->supress_last  && $i==$nbrmajticks-1) ) {
  2957.             $l="";
  2958.         }
  2959.  
  2960.         $this->maj_ticks_label[$i]=$l;
  2961.         $label+=$this->major_step;
  2962.                 
  2963.         // The x-position of the tick marks can be different from the labels.
  2964.         // Note that we record the tick position (not the label) so that the grid
  2965.         // happen upon tick marks and not labels.
  2966.         $xtick=$scale->scale_abs[0]+$start_abs+$i*$maj_step_abs+$this->xtick_offset*$min_step_abs;
  2967.         $this->maj_ticks_pos[$i]=ceil($xtick);                
  2968.         if(!($this->xtick_offset > 0 && $i==$nbrmajticks-1) && !$this->supress_tickmarks) {
  2969.             if( $this->majcolor!="" ) $img->PushColor($this->majcolor);
  2970.             $img->Line($xtick,$pos,$xtick,$yu);
  2971.             if( $this->majcolor!="" ) $img->PopColor();
  2972.         }
  2973.         }
  2974.         // Draw the minor tick marks
  2975.             
  2976.         $yu = $pos - $this->direction*$this->GetMinTickAbsSize();
  2977.         $label = $scale->GetMinVal();        
  2978.         $x=$scale->scale_abs[0];
  2979.         while( $x < $limit ) {        
  2980.         $this->ticks_pos[]=$x;
  2981.         $this->ticks_label[]=$label;
  2982.         $label+=$this->minor_step;
  2983.         if( !$this->supress_tickmarks && !$this->supress_minor_tickmarks) {
  2984.             if( $this->mincolor!="" ) $img->PushColor($this->mincolor);
  2985.             $img->Line($x,$pos,$x,$yu); 
  2986.             if( $this->mincolor!="" ) $img->PopColor();
  2987.         }
  2988.         $x += $min_step_abs;
  2989.         }
  2990.     }
  2991.     elseif( $scale->type == "y" ) {
  2992.  
  2993.         // Draw the major tick marks
  2994.         $xr = $pos + $this->direction*$this->GetMajTickAbsSize();
  2995.         $label = $scale->GetMinVal();
  2996.         
  2997.         $tmpmaj=array();
  2998.         $tmpmin=array();
  2999.  
  3000.         for( $i=0; $i<$nbrmajticks; ++$i) {
  3001.         $y=$scale->scale_abs[0]+$i*$maj_step_abs;                
  3002.  
  3003.         $tmpmaj[]=$y;
  3004.  
  3005.     
  3006.         // THe following two lines might seem to be unecessary but they are not!
  3007.         // The reason being that for X-axis we separate the position of the labels
  3008.         // and the tick marks which we don't do for the Y-axis.
  3009.         // We therefore need to make sure both arrays are corcectly filled
  3010.         // since Axis::StrokeLabels() uses the label positions and Grid::Stroke() uses
  3011.         // the tick positions.
  3012.         $this->maj_ticklabels_pos[$i]=$y;
  3013.         $this->maj_ticks_pos[$i]=$y;
  3014.         
  3015.         if( $this->label_formfunc != "" ) {
  3016.             $f=$this->label_formfunc;
  3017.             $l = $f($label);
  3018.         }    
  3019.         elseif( $this->label_formatstr != "" ) 
  3020.             $l = sprintf($this->label_formatstr,$label);
  3021.         else
  3022.             $l = sprintf("%01.".$precision."f",round($label,$precision));
  3023.                                 
  3024.         if( ($this->supress_zerolabel && ($l + 0)==0) ||  ($this->supress_first && $i==0) ||
  3025.             ($this->supress_last  && $i==$nbrmajticks-1) ) {
  3026.             $l="";
  3027.         }
  3028.         
  3029.         $this->maj_ticks_label[$i]=$l; 
  3030.         $label+=$this->major_step;    
  3031.         if( !$this->supress_tickmarks ) {
  3032.             if( $this->majcolor!="" ) $img->PushColor($this->majcolor);
  3033.             $img->Line($pos,$y,$xr,$y);    
  3034.             if( $this->majcolor!="" ) $img->PopColor();
  3035.         }
  3036.         }
  3037.  
  3038.         // Draw the minor tick marks
  3039.         $xr = $pos + $this->direction*$this->GetMinTickAbsSize();
  3040.         $label = $scale->GetMinVal();    
  3041.         for( $i=0,$y=$scale->scale_abs[0]; $y>=$limit; ) {
  3042.  
  3043.         $tmpmin[]=$y;
  3044.  
  3045.         $this->ticks_pos[$i]=$y;
  3046.         $this->ticks_label[$i]=$label;                
  3047.         $label+=$this->minor_step;                
  3048.         if( !$this->supress_tickmarks && !$this->supress_minor_tickmarks)    {
  3049.             if( $this->mincolor!="" ) $img->PushColor($this->mincolor);
  3050.             $img->Line($pos,$y,$xr,$y);
  3051.             if( $this->mincolor!="" ) $img->PopColor();
  3052.         }
  3053.         ++$i;
  3054.         $y=$scale->scale_abs[0]+$i*$min_step_abs;
  3055.         }    
  3056.     }    
  3057.     }
  3058. //---------------
  3059. // PRIVATE METHODS
  3060.     // Spoecify the offset of the displayed tick mark with the tick "space"
  3061.     // Legal values for $o is [0,1] used to adjust where the tick marks and label 
  3062.     // should be positioned within the major tick-size
  3063.     // $lo specifies the label offset and $to specifies the tick offset
  3064.     // this comes in handy for example in bar graphs where we wont no offset for the
  3065.     // tick but have the labels displayed halfway under the bars.
  3066.     function SetXLabelOffset($aLabelOff,$aTickOff=-1) {
  3067.     $this->xlabel_offset=$aLabelOff;
  3068.     if( $aTickOff==-1 )    // Same as label offset
  3069.         $this->xtick_offset=$aLabelOff;
  3070.     else
  3071.         $this->xtick_offset=$aTickOff;
  3072.     if( $aLabelOff>0 )
  3073.         $this->SupressLast();    // The last tick wont fit
  3074.     }
  3075.  
  3076.     // Which tick label should we start with?
  3077.     function SetTextLabelStart($aTextLabelOff) {
  3078.     $this->text_label_start=$aTextLabelOff;
  3079.     }
  3080.     
  3081. } // Class
  3082.  
  3083. //===================================================
  3084. // CLASS LinearScale
  3085. // Description: Handle linear scaling between screen and world 
  3086. //===================================================
  3087. class LinearScale {
  3088.     var $scale=array(0,0);
  3089.     var $scale_abs=array(0,0);
  3090.     var $scale_factor; // Scale factor between world and screen
  3091.     var $world_size;    // Plot area size in world coordinates
  3092.     var $world_abs_size; // Plot area size in pixels
  3093.     var $off; // Offset between image edge and plot area
  3094.     var $type; // is this x or y scale ?
  3095.     var $ticks=null; // Store ticks
  3096.     var $autoscale_min=false; // Forced minimum value, auto determine max
  3097.     var $autoscale_max=false; // Forced maximum value, auto determine min
  3098.     var $gracetop=0,$gracebottom=0;
  3099.     var $intscale=false; // Restrict autoscale to integers
  3100.     var $textscale=false; // Just a flag to let the Plot class find out if
  3101.     // we are a textscale or not. This is a cludge since
  3102.     // this ionformatyion is availabale in Graph::axtype but
  3103.     // we don't have access to the graph object in the Plots
  3104.     // stroke method. So we let graph store the status here
  3105.     // when the linear scale is created. A real cludge...
  3106.     var $text_scale_off = 0;
  3107.     var $auto_ticks=false; // When using manual scale should the ticks be automatically set?
  3108. //---------------
  3109. // CONSTRUCTOR
  3110.     function LinearScale($aMin=0,$aMax=0,$aType="y") {
  3111.     assert($aType=="x" || $aType=="y" );
  3112.     assert($aMin<=$aMax);
  3113.         
  3114.     $this->type=$aType;
  3115.     $this->scale=array($aMin,$aMax);        
  3116.     $this->world_size=$aMax-$aMin;    
  3117.     $this->ticks = new LinearTicks();
  3118.     }
  3119.  
  3120. //---------------
  3121. // PUBLIC METHODS    
  3122.     // Second phase constructor
  3123.     function Init(&$aImg) {
  3124.     $this->InitConstants($aImg);    
  3125.     // We want image to notify us when the margins changes so we 
  3126.     // can recalculate the constants.
  3127.     // PHP <= 4.04 BUGWARNING: IT IS IMPOSSIBLE TO DO THIS IN THE CONSTRUCTOR
  3128.     // SINCE (FOR SOME REASON) IT IS IMPOSSIBLE TO PASS A REFERENCE
  3129.     // TO 'this' INSTEAD IT WILL ADD AN ANONYMOUS COPY OF THIS OBJECT WHICH WILL
  3130.     // GET ALL THE NOTIFICATIONS. (This took a while to track down...)
  3131.         
  3132.     // Add us as an observer to class Image
  3133.     $aImg->AddObserver("InitConstants",$this);
  3134.     }
  3135.     
  3136.     // Check if scale is set or if we should autoscale
  3137.     // We should do this is either scale or ticks has not been set
  3138.     function IsSpecified() {
  3139.     if( $this->GetMinVal()==$this->GetMaxVal() ) {        // Scale not set
  3140.         return false;
  3141.     }
  3142.     return true;
  3143.     }
  3144.     
  3145.     // Set the minimum data value when the autoscaling is used. 
  3146.     // Usefull if you want a fix minimum (like 0) but have an
  3147.     // automatic maximum
  3148.     function SetAutoMin($aMin) {
  3149.     $this->autoscale_min=$aMin;
  3150.     }
  3151.  
  3152.     // Set the minimum data value when the autoscaling is used. 
  3153.     // Usefull if you want a fix minimum (like 0) but have an
  3154.     // automatic maximum
  3155.     function SetAutoMax($aMax) {
  3156.     $this->autoscale_max=$aMax;
  3157.     }
  3158.  
  3159.     // If the user manually specifies a scale should the ticks
  3160.     // still be set automatically?
  3161.     function SetAutoTicks($aFlag=true) {
  3162.     $this->auto_ticks = $aFlag;
  3163.     }
  3164.  
  3165.  
  3166.     
  3167.     // Specify scale "grace" value (top and bottom)
  3168.     function SetGrace($aGraceTop,$aGraceBottom=0) {
  3169.     if( $aGraceTop<0 || $aGraceBottom < 0  )
  3170.         JpGraphError::Raise(" Grace must be larger then 0");
  3171.     $this->gracetop=$aGraceTop;
  3172.     $this->gracebottom=$aGraceBottom;
  3173.     }
  3174.     
  3175.     // Get the minimum value in the scale
  3176.     function GetMinVal() {
  3177.     return $this->scale[0];
  3178.     }
  3179.     
  3180.     // get maximum value for scale
  3181.     function GetMaxVal() {
  3182.     return $this->scale[1];
  3183.     }
  3184.         
  3185.     // Specify a new min/max value for sclae    
  3186.     function Update(&$aImg,$aMin,$aMax) {
  3187.     $this->scale=array($aMin,$aMax);        
  3188.     $this->world_size=$aMax-$aMin;        
  3189.     $this->InitConstants($aImg);                    
  3190.     }
  3191.     
  3192.     // Translate between world and screen
  3193.     function Translate($aCoord) {
  3194.     return $this->off+($aCoord - $this->GetMinVal()) * $this->scale_factor; 
  3195.     }
  3196.     
  3197.     // Relative translate (don't include offset) usefull when we just want
  3198.     // to know the relative position (in pixels) on the axis
  3199.     function RelTranslate($aCoord) {
  3200.     return ($aCoord - $this->GetMinVal()) * $this->scale_factor; 
  3201.     }
  3202.     
  3203.     // Restrict autoscaling to only use integers
  3204.     function SetIntScale($aIntScale=true) {
  3205.     $this->intscale=$aIntScale;
  3206.     }
  3207.     
  3208.     // Calculate an integer autoscale
  3209.     function IntAutoScale(&$img,$min,$max,$maxsteps,$majend=true) {
  3210.     // Make sure limits are integers
  3211.     $min=floor($min);
  3212.     $max=ceil($max);
  3213.     if( abs($min-$max)==0 ) {
  3214.         --$min; ++$max;
  3215.     }
  3216.     $maxsteps = floor($maxsteps);
  3217.         
  3218.     $gracetop=round(($this->gracetop/100.0)*abs($max-$min));
  3219.     $gracebottom=round(($this->gracebottom/100.0)*abs($max-$min));
  3220.     if( is_numeric($this->autoscale_min) ) {
  3221.         $min = ceil($this->autoscale_min);
  3222.         if( $min >= $max ) {
  3223.         JpGraphError::Raise('You have specified a min value with SetAutoMin() which is larger than the maximum value used for the scale. This is not possible.');
  3224.         die();
  3225.         }
  3226.     }
  3227.  
  3228.     if( is_numeric($this->autoscale_max) ) {
  3229.         $max = ceil($this->autoscale_max);
  3230.         if( $min >= $max ) {
  3231.         JpGraphError::Raise('You have specified a max value with SetAutoMax() which is smaller than the miminum value used for the scale. This is not possible.');
  3232.         die();
  3233.         }
  3234.     }
  3235.  
  3236.     if( abs($min-$max ) == 0 ) {
  3237.         ++$max;
  3238.         --$min;
  3239.     }
  3240.             
  3241.     $min -= $gracebottom;
  3242.     $max += $gracetop;        
  3243.  
  3244.     // First get tickmarks as multiples of 1, 10, ...    
  3245.     list($num1steps,$adj1min,$adj1max,$maj1step) = 
  3246.         $this->IntCalcTicks($maxsteps,$min,$max,1);
  3247.  
  3248.     if( abs($min-$max) > 2 ) {
  3249.         // Then get tick marks as 2:s 2, 20, ...
  3250.         list($num2steps,$adj2min,$adj2max,$maj2step) = 
  3251.             $this->IntCalcTicks($maxsteps,$min,$max,5);
  3252.     }
  3253.     else {
  3254.         $num2steps = 10000;    // Dummy high value so we don't choose this
  3255.     }
  3256.     
  3257.     if( abs($min-$max) > 5 ) {    
  3258.         // Then get tickmarks as 5:s 5, 50, 500, ...
  3259.         list($num5steps,$adj5min,$adj5max,$maj5step) = 
  3260.             $this->IntCalcTicks($maxsteps,$min,$max,2);        
  3261.     }
  3262.     else {
  3263.         $num5steps = 10000;    // Dummy high value so we don't choose this        
  3264.     }
  3265.     
  3266.     // Check to see whichof 1:s, 2:s or 5:s fit better with
  3267.     // the requested number of major ticks        
  3268.     $match1=abs($num1steps-$maxsteps);        
  3269.     $match2=abs($num2steps-$maxsteps);
  3270.     if( !empty($maj5step) && $maj5step > 1 )
  3271.         $match5=abs($num5steps-$maxsteps);
  3272.     else
  3273.         $match5=10000;     // Dummy high value 
  3274.         
  3275.     // Compare these three values and see which is the closest match
  3276.     // We use a 0.6 weight to gravitate towards multiple of 5:s 
  3277.     if( $match1 < $match2 ) {
  3278.         if( $match1 < $match5 )
  3279.         $r=1;            
  3280.         else 
  3281.         $r=3;
  3282.     }
  3283.     else {
  3284.         if( $match2 < $match5 )
  3285.         $r=2;            
  3286.         else 
  3287.         $r=3;        
  3288.     }    
  3289.     // Minsteps are always the same as maxsteps for integer scale
  3290.     switch( $r ) {
  3291.         case 1:
  3292.         $this->Update($img,$adj1min,$adj1max);
  3293.         $this->ticks->Set($maj1step,$maj1step);
  3294.         break;            
  3295.         case 2:
  3296.         $this->Update($img,$adj2min,$adj2max);        
  3297.         $this->ticks->Set($maj2step,$maj2step);
  3298.         break;                                    
  3299.         case 3:
  3300.         $this->Update($img,$adj5min,$adj5max);
  3301.         $this->ticks->Set($maj5step,$maj2step);        
  3302.         break;            
  3303.     }        
  3304.     }
  3305.     
  3306.     
  3307.     // Calculate autoscale. Used if user hasn't given a scale and ticks
  3308.     // $maxsteps is the maximum number of major tickmarks allowed.
  3309.     function AutoScale(&$img,$min,$max,$maxsteps,$majend=true) {
  3310.     if( $this->intscale ) {    
  3311.         $this->IntAutoScale($img,$min,$max,$maxsteps,$majend);
  3312.         return;
  3313.     }
  3314.     if( abs($min-$max) < 0.00001 ) {
  3315.         // We need some difference to be able to autoscale
  3316.         // make it 5% above and 5% below value
  3317.         if( $min==0 && $max==0 ) {        // Special case
  3318.         $min=-1; $max=1;
  3319.         }
  3320.         else {
  3321.         $delta = (abs($max)+abs($min))*0.005;
  3322.         $min -= $delta;
  3323.         $max += $delta;
  3324.         }
  3325.     }
  3326.         
  3327.     $gracetop=($this->gracetop/100.0)*abs($max-$min);
  3328.     $gracebottom=($this->gracebottom/100.0)*abs($max-$min);
  3329.     if( is_numeric($this->autoscale_min) ) {
  3330.         $min = $this->autoscale_min;
  3331.         if( $min >= $max ) {
  3332.         JpGraphError::Raise('You have specified a min value with SetAutoMin() which is larger than the maximum value used for the scale. This is not possible.');
  3333.         die();
  3334.         }
  3335.         if( abs($min-$max ) < 0.00001 )
  3336.         $max *= 1.2;
  3337.     }
  3338.  
  3339.     if( is_numeric($this->autoscale_max) ) {
  3340.         $max = $this->autoscale_max;
  3341.         if( $min >= $max ) {
  3342.         JpGraphError::Raise('You have specified a max value with SetAutoMax() which is smaller than the miminum value used for the scale. This is not possible.');
  3343.         die();
  3344.         }
  3345.         if( abs($min-$max ) < 0.00001 )
  3346.         $min *= 0.8;
  3347.     }
  3348.  
  3349.     
  3350.     $min -= $gracebottom;
  3351.     $max += $gracetop;
  3352.  
  3353.     // First get tickmarks as multiples of 0.1, 1, 10, ...    
  3354.     list($num1steps,$adj1min,$adj1max,$min1step,$maj1step) = 
  3355.         $this->CalcTicks($maxsteps,$min,$max,1,2);
  3356.         
  3357.     // Then get tick marks as 2:s 0.2, 2, 20, ...
  3358.     list($num2steps,$adj2min,$adj2max,$min2step,$maj2step) = 
  3359.         $this->CalcTicks($maxsteps,$min,$max,5,2);
  3360.         
  3361.     // Then get tickmarks as 5:s 0.05, 0.5, 5, 50, ...
  3362.     list($num5steps,$adj5min,$adj5max,$min5step,$maj5step) = 
  3363.         $this->CalcTicks($maxsteps,$min,$max,2,5);        
  3364.  
  3365.     // Check to see whichof 1:s, 2:s or 5:s fit better with
  3366.     // the requested number of major ticks        
  3367.     $match1=abs($num1steps-$maxsteps);        
  3368.     $match2=abs($num2steps-$maxsteps);
  3369.     $match5=abs($num5steps-$maxsteps);
  3370.     // Compare these three values and see which is the closest match
  3371.     // We use a 0.8 weight to gravitate towards multiple of 5:s 
  3372.     $r=$this->MatchMin3($match1,$match2,$match5,0.8);
  3373.     switch( $r ) {
  3374.         case 1:
  3375.         $this->Update($img,$adj1min,$adj1max);
  3376.         $this->ticks->Set($maj1step,$min1step);
  3377.         break;            
  3378.         case 2:
  3379.         $this->Update($img,$adj2min,$adj2max);        
  3380.         $this->ticks->Set($maj2step,$min2step);
  3381.         break;                                    
  3382.         case 3:
  3383.         $this->Update($img,$adj5min,$adj5max);
  3384.         $this->ticks->Set($maj5step,$min5step);        
  3385.         break;            
  3386.     }
  3387.     }
  3388.  
  3389. //---------------
  3390. // PRIVATE METHODS    
  3391.  
  3392.     // This method recalculates all constants that are depending on the
  3393.     // margins in the image. If the margins in the image are changed
  3394.     // this method should be called for every scale that is registred with
  3395.     // that image. Should really be installed as an observer of that image.
  3396.     function InitConstants(&$img) {
  3397.     if( $this->type=="x" ) {
  3398.         $this->world_abs_size=$img->width - $img->left_margin - $img->right_margin;
  3399.         $this->off=$img->left_margin;
  3400.         $this->scale_factor = 0;
  3401.         if( $this->world_size > 0 )
  3402.         $this->scale_factor=$this->world_abs_size/($this->world_size*1.0);
  3403.     }
  3404.     else { // y scale
  3405.         $this->world_abs_size=$img->height - $img->top_margin - $img->bottom_margin; 
  3406.         $this->off=$img->top_margin+$this->world_abs_size;            
  3407.         $this->scale_factor = 0;            
  3408.         if( $this->world_size > 0 )            
  3409.         $this->scale_factor=-$this->world_abs_size/($this->world_size*1.0);    
  3410.     }
  3411.     $size = $this->world_size * $this->scale_factor;
  3412.     $this->scale_abs=array($this->off,$this->off + $size);    
  3413.     }
  3414.     
  3415.     // Initialize the conversion constants for this scale
  3416.     // This tries to pre-calculate as much as possible to speed up the
  3417.     // actual conversion (with Translate()) later on
  3418.     // $start    =scale start in absolute pixels (for x-scale this is an y-position
  3419.     //                 and for an y-scale this is an x-position
  3420.     // $len         =absolute length in pixels of scale             
  3421.     function SetConstants($aStart,$aLen) {
  3422.     $this->world_abs_size=$aLen;
  3423.     $this->off=$aStart;
  3424.         
  3425.     if( $this->world_size<=0 ) {
  3426.         JpGraphError::Raise("<b>JpGraph Fatal Error</b>:<br>
  3427.          You have unfortunately stumbled upon a bug in JpGraph. <br>
  3428.          It seems like the scale range is ".$this->world_size." [for ".
  3429.                 $this->type." scale] <br>
  3430.              Please report Bug #01 to jpgraph@aditus.nu and include the script
  3431.          that gave this error. <br>
  3432.          This problem could potentially be caused by trying to use \"illegal\"
  3433.          values in the input data arrays (like trying to send in strings or
  3434.          only NULL values) which causes the autoscaling to fail.");
  3435.     }
  3436.         
  3437.     // scale_factor = number of pixels per world unit
  3438.     $this->scale_factor=$this->world_abs_size/($this->world_size*1.0);
  3439.         
  3440.     // scale_abs = start and end points of scale in absolute pixels
  3441.     $this->scale_abs=array($this->off,$this->off+$this->world_size*$this->scale_factor);        
  3442.     }
  3443.     
  3444.     
  3445.     // Calculate number of ticks steps with a specific division
  3446.     // $a is the divisor of 10**x to generate the first maj tick intervall
  3447.     // $a=1, $b=2 give major ticks with multiple of 10, ...,0.1,1,10,...
  3448.     // $a=5, $b=2 give major ticks with multiple of 2:s ...,0.2,2,20,...
  3449.     // $a=2, $b=5 give major ticks with multiple of 5:s ...,0.5,5,50,...
  3450.     // We return a vector of
  3451.     //     [$numsteps,$adjmin,$adjmax,$minstep,$majstep]
  3452.     // If $majend==true then the first and last marks on the axis will be major
  3453.     // labeled tick marks otherwise it will be adjusted to the closest min tick mark
  3454.     function CalcTicks($maxsteps,$min,$max,$a,$b,$majend=true) {
  3455.     $diff=$max-$min; 
  3456.     if( $diff==0 )
  3457.         $ld=0;
  3458.     else
  3459.         $ld=floor(log10($diff));
  3460.  
  3461.     // Gravitate min towards zero if we are close        
  3462.     if( $min>0 && $min < pow(10,$ld) ) $min=0;
  3463.         
  3464.     //$majstep=pow(10,$ld-1)/$a; 
  3465.     $majstep=pow(10,$ld)/$a; 
  3466.     $minstep=$majstep/$b;
  3467.     
  3468.     $adjmax=ceil($max/$minstep)*$minstep;
  3469.     $adjmin=floor($min/$minstep)*$minstep;    
  3470.     $adjdiff = $adjmax-$adjmin;
  3471.     $numsteps=$adjdiff/$majstep; 
  3472.     
  3473.     while( $numsteps>$maxsteps ) {
  3474.         $majstep=pow(10,$ld)/$a; 
  3475.         $numsteps=$adjdiff/$majstep;
  3476.         ++$ld;
  3477.     }
  3478.  
  3479.     $minstep=$majstep/$b;
  3480.     $adjmin=floor($min/$minstep)*$minstep;    
  3481.     $adjdiff = $adjmax-$adjmin;        
  3482.     if( $majend ) {
  3483.         $adjmin = floor($min/$majstep)*$majstep;    
  3484.         $adjdiff = $adjmax-$adjmin;        
  3485.         $adjmax = ceil($adjdiff/$majstep)*$majstep+$adjmin;
  3486.     }
  3487.     else
  3488.         $adjmax=ceil($max/$minstep)*$minstep;
  3489.  
  3490.     return array($numsteps,$adjmin,$adjmax,$minstep,$majstep);
  3491.     }
  3492.     
  3493.     function IntCalcTicks($maxsteps,$min,$max,$a,$majend=true) {
  3494.     $diff=$max-$min; 
  3495.     if( $diff==0 )
  3496.         JpGraphError::Raise('Can\'t automatically determine ticks since min==max.');
  3497.     else
  3498.         $ld=floor(log10($diff));
  3499.         
  3500.     // Gravitate min towards zero if we are close        
  3501.     if( $min>0 && $min < pow(10,$ld) ) $min=0;
  3502.         
  3503.     if( $ld == 0 ) $ld=1;
  3504.     
  3505.     if( $a == 1 ) 
  3506.         $majstep = 1;
  3507.     else
  3508.         $majstep=pow(10,$ld)/$a; 
  3509.     $adjmax=ceil($max/$majstep)*$majstep;
  3510.  
  3511.     $adjmin=floor($min/$majstep)*$majstep;    
  3512.     $adjdiff = $adjmax-$adjmin;
  3513.     $numsteps=$adjdiff/$majstep; 
  3514.     while( $numsteps>$maxsteps ) {
  3515.         $majstep=pow(10,$ld)/$a; 
  3516.         $numsteps=$adjdiff/$majstep;
  3517.         ++$ld;
  3518.     }
  3519.         
  3520.     $adjmin=floor($min/$majstep)*$majstep;    
  3521.     $adjdiff = $adjmax-$adjmin;        
  3522.     if( $majend ) {
  3523.         $adjmin = floor($min/$majstep)*$majstep;    
  3524.         $adjdiff = $adjmax-$adjmin;        
  3525.         $adjmax = ceil($adjdiff/$majstep)*$majstep+$adjmin;
  3526.     }
  3527.     else
  3528.         $adjmax=ceil($max/$majstep)*$majstep;
  3529.             
  3530.     return array($numsteps,$adjmin,$adjmax,$majstep);        
  3531.     }
  3532.  
  3533.  
  3534.     
  3535.     // Determine the minimum of three values witha  weight for last value
  3536.     function MatchMin3($a,$b,$c,$weight) {
  3537.     if( $a < $b ) {
  3538.         if( $a < ($c*$weight) ) 
  3539.         return 1; // $a smallest
  3540.         else 
  3541.         return 3; // $c smallest
  3542.     }
  3543.     elseif( $b < ($c*$weight) ) 
  3544.         return 2; // $b smallest
  3545.     return 3; // $c smallest
  3546.     }
  3547. } // Class
  3548.  
  3549. //===================================================
  3550. // CLASS RGB
  3551. // Description: Color definitions as RGB triples
  3552. //===================================================
  3553. class RGB {
  3554.     var $rgb_table;
  3555.     var $img;
  3556.     function RGB($aImg=null) {
  3557.     $this->img = $aImg;
  3558.         
  3559.     // Conversion array between color names and RGB
  3560.     $this->rgb_table = array(
  3561.         "aqua"=> array(0,255,255),        
  3562.         "lime"=> array(0,255,0),        
  3563.         "teal"=> array(0,128,128),
  3564.         "whitesmoke"=>array(245,245,245),
  3565.         "gainsboro"=>array(220,220,220),
  3566.         "oldlace"=>array(253,245,230),
  3567.         "linen"=>array(250,240,230),
  3568.         "antiquewhite"=>array(250,235,215),
  3569.         "papayawhip"=>array(255,239,213),
  3570.         "blanchedalmond"=>array(255,235,205),
  3571.         "bisque"=>array(255,228,196),
  3572.         "peachpuff"=>array(255,218,185),
  3573.         "navajowhite"=>array(255,222,173),
  3574.         "moccasin"=>array(255,228,181),
  3575.         "cornsilk"=>array(255,248,220),
  3576.         "ivory"=>array(255,255,240),
  3577.         "lemonchiffon"=>array(255,250,205),
  3578.         "seashell"=>array(255,245,238),
  3579.         "mintcream"=>array(245,255,250),
  3580.         "azure"=>array(240,255,255),
  3581.         "aliceblue"=>array(240,248,255),
  3582.         "lavender"=>array(230,230,250),
  3583.         "lavenderblush"=>array(255,240,245),
  3584.         "mistyrose"=>array(255,228,225),
  3585.         "white"=>array(255,255,255),
  3586.         "black"=>array(0,0,0),
  3587.         "darkslategray"=>array(47,79,79),
  3588.         "dimgray"=>array(105,105,105),
  3589.         "slategray"=>array(112,128,144),
  3590.         "lightslategray"=>array(119,136,153),
  3591.         "gray"=>array(190,190,190),
  3592.         "lightgray"=>array(211,211,211),
  3593.         "midnightblue"=>array(25,25,112),
  3594.         "navy"=>array(0,0,128),
  3595.         "cornflowerblue"=>array(100,149,237),
  3596.         "darkslateblue"=>array(72,61,139),
  3597.         "slateblue"=>array(106,90,205),
  3598.         "mediumslateblue"=>array(123,104,238),
  3599.         "lightslateblue"=>array(132,112,255),
  3600.         "mediumblue"=>array(0,0,205),
  3601.         "royalblue"=>array(65,105,225),
  3602.         "blue"=>array(0,0,255),
  3603.         "dodgerblue"=>array(30,144,255),
  3604.         "deepskyblue"=>array(0,191,255),
  3605.         "skyblue"=>array(135,206,235),
  3606.         "lightskyblue"=>array(135,206,250),
  3607.         "steelblue"=>array(70,130,180),
  3608.         "lightred"=>array(211,167,168),
  3609.         "lightsteelblue"=>array(176,196,222),
  3610.         "lightblue"=>array(173,216,230),
  3611.         "powderblue"=>array(176,224,230),
  3612.         "paleturquoise"=>array(175,238,238),
  3613.         "darkturquoise"=>array(0,206,209),
  3614.         "mediumturquoise"=>array(72,209,204),
  3615.         "turquoise"=>array(64,224,208),
  3616.         "cyan"=>array(0,255,255),
  3617.         "lightcyan"=>array(224,255,255),
  3618.         "cadetblue"=>array(95,158,160),
  3619.         "mediumaquamarine"=>array(102,205,170),
  3620.         "aquamarine"=>array(127,255,212),
  3621.         "darkgreen"=>array(0,100,0),
  3622.         "darkolivegreen"=>array(85,107,47),
  3623.         "darkseagreen"=>array(143,188,143),
  3624.         "seagreen"=>array(46,139,87),
  3625.         "mediumseagreen"=>array(60,179,113),
  3626.         "lightseagreen"=>array(32,178,170),
  3627.         "palegreen"=>array(152,251,152),
  3628.         "springgreen"=>array(0,255,127),
  3629.         "lawngreen"=>array(124,252,0),
  3630.         "green"=>array(0,255,0),
  3631.         "chartreuse"=>array(127,255,0),
  3632.         "mediumspringgreen"=>array(0,250,154),
  3633.         "greenyellow"=>array(173,255,47),
  3634.         "limegreen"=>array(50,205,50),
  3635.         "yellowgreen"=>array(154,205,50),
  3636.         "forestgreen"=>array(34,139,34),
  3637.         "olivedrab"=>array(107,142,35),
  3638.         "darkkhaki"=>array(189,183,107),
  3639.         "khaki"=>array(240,230,140),
  3640.         "palegoldenrod"=>array(238,232,170),
  3641.         "lightgoldenrodyellow"=>array(250,250,210),
  3642.         "lightyellow"=>array(255,255,200),
  3643.         "yellow"=>array(255,255,0),
  3644.         "gold"=>array(255,215,0),
  3645.         "lightgoldenrod"=>array(238,221,130),
  3646.         "goldenrod"=>array(218,165,32),
  3647.         "darkgoldenrod"=>array(184,134,11),
  3648.         "rosybrown"=>array(188,143,143),
  3649.         "indianred"=>array(205,92,92),
  3650.         "saddlebrown"=>array(139,69,19),
  3651.         "sienna"=>array(160,82,45),
  3652.         "peru"=>array(205,133,63),
  3653.         "burlywood"=>array(222,184,135),
  3654.         "beige"=>array(245,245,220),
  3655.         "wheat"=>array(245,222,179),
  3656.         "sandybrown"=>array(244,164,96),
  3657.         "tan"=>array(210,180,140),
  3658.         "chocolate"=>array(210,105,30),
  3659.         "firebrick"=>array(178,34,34),
  3660.         "brown"=>array(165,42,42),
  3661.         "darksalmon"=>array(233,150,122),
  3662.         "salmon"=>array(250,128,114),
  3663.         "lightsalmon"=>array(255,160,122),
  3664.         "orange"=>array(255,165,0),
  3665.         "darkorange"=>array(255,140,0),
  3666.         "coral"=>array(255,127,80),
  3667.         "lightcoral"=>array(240,128,128),
  3668.         "tomato"=>array(255,99,71),
  3669.         "orangered"=>array(255,69,0),
  3670.         "red"=>array(255,0,0),
  3671.         "hotpink"=>array(255,105,180),
  3672.         "deeppink"=>array(255,20,147),
  3673.         "pink"=>array(255,192,203),
  3674.         "lightpink"=>array(255,182,193),
  3675.         "palevioletred"=>array(219,112,147),
  3676.         "maroon"=>array(176,48,96),
  3677.         "mediumvioletred"=>array(199,21,133),
  3678.         "violetred"=>array(208,32,144),
  3679.         "magenta"=>array(255,0,255),
  3680.         "violet"=>array(238,130,238),
  3681.         "plum"=>array(221,160,221),
  3682.         "orchid"=>array(218,112,214),
  3683.         "mediumorchid"=>array(186,85,211),
  3684.         "darkorchid"=>array(153,50,204),
  3685.         "darkviolet"=>array(148,0,211),
  3686.         "blueviolet"=>array(138,43,226),
  3687.         "purple"=>array(160,32,240),
  3688.         "mediumpurple"=>array(147,112,219),
  3689.         "thistle"=>array(216,191,216),
  3690.         "snow1"=>array(255,250,250),
  3691.         "snow2"=>array(238,233,233),
  3692.         "snow3"=>array(205,201,201),
  3693.         "snow4"=>array(139,137,137),
  3694.         "seashell1"=>array(255,245,238),
  3695.         "seashell2"=>array(238,229,222),
  3696.         "seashell3"=>array(205,197,191),
  3697.         "seashell4"=>array(139,134,130),
  3698.         "AntiqueWhite1"=>array(255,239,219),
  3699.         "AntiqueWhite2"=>array(238,223,204),
  3700.         "AntiqueWhite3"=>array(205,192,176),
  3701.         "AntiqueWhite4"=>array(139,131,120),
  3702.         "bisque1"=>array(255,228,196),
  3703.         "bisque2"=>array(238,213,183),
  3704.         "bisque3"=>array(205,183,158),
  3705.         "bisque4"=>array(139,125,107),
  3706.         "peachPuff1"=>array(255,218,185),
  3707.         "peachpuff2"=>array(238,203,173),
  3708.         "peachpuff3"=>array(205,175,149),
  3709.         "peachpuff4"=>array(139,119,101),
  3710.         "navajowhite1"=>array(255,222,173),
  3711.         "navajowhite2"=>array(238,207,161),
  3712.         "navajowhite3"=>array(205,179,139),
  3713.         "navajowhite4"=>array(139,121,94),
  3714.         "lemonchiffon1"=>array(255,250,205),
  3715.         "lemonchiffon2"=>array(238,233,191),
  3716.         "lemonchiffon3"=>array(205,201,165),
  3717.         "lemonchiffon4"=>array(139,137,112),
  3718.         "ivory1"=>array(255,255,240),
  3719.         "ivory2"=>array(238,238,224),
  3720.         "ivory3"=>array(205,205,193),
  3721.         "ivory4"=>array(139,139,131),
  3722.         "honeydew"=>array(193,205,193),
  3723.         "lavenderblush1"=>array(255,240,245),
  3724.         "lavenderblush2"=>array(238,224,229),
  3725.         "lavenderblush3"=>array(205,193,197),
  3726.         "lavenderblush4"=>array(139,131,134),
  3727.         "mistyrose1"=>array(255,228,225),
  3728.         "mistyrose2"=>array(238,213,210),
  3729.         "mistyrose3"=>array(205,183,181),
  3730.         "mistyrose4"=>array(139,125,123),
  3731.         "azure1"=>array(240,255,255),
  3732.         "azure2"=>array(224,238,238),
  3733.         "azure3"=>array(193,205,205),
  3734.         "azure4"=>array(131,139,139),
  3735.         "slateblue1"=>array(131,111,255),
  3736.         "slateblue2"=>array(122,103,238),
  3737.         "slateblue3"=>array(105,89,205),
  3738.         "slateblue4"=>array(71,60,139),
  3739.         "royalblue1"=>array(72,118,255),
  3740.         "royalblue2"=>array(67,110,238),
  3741.         "royalblue3"=>array(58,95,205),
  3742.         "royalblue4"=>array(39,64,139),
  3743.         "dodgerblue1"=>array(30,144,255),
  3744.         "dodgerblue2"=>array(28,134,238),
  3745.         "dodgerblue3"=>array(24,116,205),
  3746.         "dodgerblue4"=>array(16,78,139),
  3747.         "steelblue1"=>array(99,184,255),
  3748.         "steelblue2"=>array(92,172,238),
  3749.         "steelblue3"=>array(79,148,205),
  3750.         "steelblue4"=>array(54,100,139),
  3751.         "deepskyblue1"=>array(0,191,255),
  3752.         "deepskyblue2"=>array(0,178,238),
  3753.         "deepskyblue3"=>array(0,154,205),
  3754.         "deepskyblue4"=>array(0,104,139),
  3755.         "skyblue1"=>array(135,206,255),
  3756.         "skyblue2"=>array(126,192,238),
  3757.         "skyblue3"=>array(108,166,205),
  3758.         "skyblue4"=>array(74,112,139),
  3759.         "lightskyblue1"=>array(176,226,255),
  3760.         "lightskyblue2"=>array(164,211,238),
  3761.         "lightskyblue3"=>array(141,182,205),
  3762.         "lightskyblue4"=>array(96,123,139),
  3763.         "slategray1"=>array(198,226,255),
  3764.         "slategray2"=>array(185,211,238),
  3765.         "slategray3"=>array(159,182,205),
  3766.         "slategray4"=>array(108,123,139),
  3767.         "lightsteelblue1"=>array(202,225,255),
  3768.         "lightsteelblue2"=>array(188,210,238),
  3769.         "lightsteelblue3"=>array(162,181,205),
  3770.         "lightsteelblue4"=>array(110,123,139),
  3771.         "lightblue1"=>array(191,239,255),
  3772.         "lightblue2"=>array(178,223,238),
  3773.         "lightblue3"=>array(154,192,205),
  3774.         "lightblue4"=>array(104,131,139),
  3775.         "lightcyan1"=>array(224,255,255),
  3776.         "lightcyan2"=>array(209,238,238),
  3777.         "lightcyan3"=>array(180,205,205),
  3778.         "lightcyan4"=>array(122,139,139),
  3779.         "paleturquoise1"=>array(187,255,255),
  3780.         "paleturquoise2"=>array(174,238,238),
  3781.         "paleturquoise3"=>array(150,205,205),
  3782.         "paleturquoise4"=>array(102,139,139),
  3783.         "cadetblue1"=>array(152,245,255),
  3784.         "cadetblue2"=>array(142,229,238),
  3785.         "cadetblue3"=>array(122,197,205),
  3786.         "cadetblue4"=>array(83,134,139),
  3787.         "turquoise1"=>array(0,245,255),
  3788.         "turquoise2"=>array(0,229,238),
  3789.         "turquoise3"=>array(0,197,205),
  3790.         "turquoise4"=>array(0,134,139),
  3791.         "cyan1"=>array(0,255,255),
  3792.         "cyan2"=>array(0,238,238),
  3793.         "cyan3"=>array(0,205,205),
  3794.         "cyan4"=>array(0,139,139),
  3795.         "darkslategray1"=>array(151,255,255),
  3796.         "darkslategray2"=>array(141,238,238),
  3797.         "darkslategray3"=>array(121,205,205),
  3798.         "darkslategray4"=>array(82,139,139),
  3799.         "aquamarine1"=>array(127,255,212),
  3800.         "aquamarine2"=>array(118,238,198),
  3801.         "aquamarine3"=>array(102,205,170),
  3802.         "aquamarine4"=>array(69,139,116),
  3803.         "darkseagreen1"=>array(193,255,193),
  3804.         "darkseagreen2"=>array(180,238,180),
  3805.         "darkseagreen3"=>array(155,205,155),
  3806.         "darkseagreen4"=>array(105,139,105),
  3807.         "seagreen1"=>array(84,255,159),
  3808.         "seagreen2"=>array(78,238,148),
  3809.         "seagreen3"=>array(67,205,128),
  3810.         "seagreen4"=>array(46,139,87),
  3811.         "palegreen1"=>array(154,255,154),
  3812.         "palegreen2"=>array(144,238,144),
  3813.         "palegreen3"=>array(124,205,124),
  3814.         "palegreen4"=>array(84,139,84),
  3815.         "springgreen1"=>array(0,255,127),
  3816.         "springgreen2"=>array(0,238,118),
  3817.         "springgreen3"=>array(0,205,102),
  3818.         "springgreen4"=>array(0,139,69),
  3819.         "chartreuse1"=>array(127,255,0),
  3820.         "chartreuse2"=>array(118,238,0),
  3821.         "chartreuse3"=>array(102,205,0),
  3822.         "chartreuse4"=>array(69,139,0),
  3823.         "olivedrab1"=>array(192,255,62),
  3824.         "olivedrab2"=>array(179,238,58),
  3825.         "olivedrab3"=>array(154,205,50),
  3826.         "olivedrab4"=>array(105,139,34),
  3827.         "darkolivegreen1"=>array(202,255,112),
  3828.         "darkolivegreen2"=>array(188,238,104),
  3829.         "darkolivegreen3"=>array(162,205,90),
  3830.         "darkolivegreen4"=>array(110,139,61),
  3831.         "khaki1"=>array(255,246,143),
  3832.         "khaki2"=>array(238,230,133),
  3833.         "khaki3"=>array(205,198,115),
  3834.         "khaki4"=>array(139,134,78),
  3835.         "lightgoldenrod1"=>array(255,236,139),
  3836.         "lightgoldenrod2"=>array(238,220,130),
  3837.         "lightgoldenrod3"=>array(205,190,112),
  3838.         "lightgoldenrod4"=>array(139,129,76),
  3839.         "yellow1"=>array(255,255,0),
  3840.         "yellow2"=>array(238,238,0),
  3841.         "yellow3"=>array(205,205,0),
  3842.         "yellow4"=>array(139,139,0),
  3843.         "gold1"=>array(255,215,0),
  3844.         "gold2"=>array(238,201,0),
  3845.         "gold3"=>array(205,173,0),
  3846.         "gold4"=>array(139,117,0),
  3847.         "goldenrod1"=>array(255,193,37),
  3848.         "goldenrod2"=>array(238,180,34),
  3849.         "goldenrod3"=>array(205,155,29),
  3850.         "goldenrod4"=>array(139,105,20),
  3851.         "darkgoldenrod1"=>array(255,185,15),
  3852.         "darkgoldenrod2"=>array(238,173,14),
  3853.         "darkgoldenrod3"=>array(205,149,12),
  3854.         "darkgoldenrod4"=>array(139,101,8),
  3855.         "rosybrown1"=>array(255,193,193),
  3856.         "rosybrown2"=>array(238,180,180),
  3857.         "rosybrown3"=>array(205,155,155),
  3858.         "rosybrown4"=>array(139,105,105),
  3859.         "indianred1"=>array(255,106,106),
  3860.         "indianred2"=>array(238,99,99),
  3861.         "indianred3"=>array(205,85,85),
  3862.         "indianred4"=>array(139,58,58),
  3863.         "sienna1"=>array(255,130,71),
  3864.         "sienna2"=>array(238,121,66),
  3865.         "sienna3"=>array(205,104,57),
  3866.         "sienna4"=>array(139,71,38),
  3867.         "burlywood1"=>array(255,211,155),
  3868.         "burlywood2"=>array(238,197,145),
  3869.         "burlywood3"=>array(205,170,125),
  3870.         "burlywood4"=>array(139,115,85),
  3871.         "wheat1"=>array(255,231,186),
  3872.         "wheat2"=>array(238,216,174),
  3873.         "wheat3"=>array(205,186,150),
  3874.         "wheat4"=>array(139,126,102),
  3875.         "tan1"=>array(255,165,79),
  3876.         "tan2"=>array(238,154,73),
  3877.         "tan3"=>array(205,133,63),
  3878.         "tan4"=>array(139,90,43),
  3879.         "chocolate1"=>array(255,127,36),
  3880.         "chocolate2"=>array(238,118,33),
  3881.         "chocolate3"=>array(205,102,29),
  3882.         "chocolate4"=>array(139,69,19),
  3883.         "firebrick1"=>array(255,48,48),
  3884.         "firebrick2"=>array(238,44,44),
  3885.         "firebrick3"=>array(205,38,38),
  3886.         "firebrick4"=>array(139,26,26),
  3887.         "brown1"=>array(255,64,64),
  3888.         "brown2"=>array(238,59,59),
  3889.         "brown3"=>array(205,51,51),
  3890.         "brown4"=>array(139,35,35),
  3891.         "salmon1"=>array(255,140,105),
  3892.         "salmon2"=>array(238,130,98),
  3893.         "salmon3"=>array(205,112,84),
  3894.         "salmon4"=>array(139,76,57),
  3895.         "lightsalmon1"=>array(255,160,122),
  3896.         "lightsalmon2"=>array(238,149,114),
  3897.         "lightsalmon3"=>array(205,129,98),
  3898.         "lightsalmon4"=>array(139,87,66),
  3899.         "orange1"=>array(255,165,0),
  3900.         "orange2"=>array(238,154,0),
  3901.         "orange3"=>array(205,133,0),
  3902.         "orange4"=>array(139,90,0),
  3903.         "darkorange1"=>array(255,127,0),
  3904.         "darkorange2"=>array(238,118,0),
  3905.         "darkorange3"=>array(205,102,0),
  3906.         "darkorange4"=>array(139,69,0),
  3907.         "coral1"=>array(255,114,86),
  3908.         "coral2"=>array(238,106,80),
  3909.         "coral3"=>array(205,91,69),
  3910.         "coral4"=>array(139,62,47),
  3911.         "tomato1"=>array(255,99,71),
  3912.         "tomato2"=>array(238,92,66),
  3913.         "tomato3"=>array(205,79,57),
  3914.         "tomato4"=>array(139,54,38),
  3915.         "orangered1"=>array(255,69,0),
  3916.         "orangered2"=>array(238,64,0),
  3917.         "orangered3"=>array(205,55,0),
  3918.         "orangered4"=>array(139,37,0),
  3919.         "deeppink1"=>array(255,20,147),
  3920.         "deeppink2"=>array(238,18,137),
  3921.         "deeppink3"=>array(205,16,118),
  3922.         "deeppink4"=>array(139,10,80),
  3923.         "hotpink1"=>array(255,110,180),
  3924.         "hotpink2"=>array(238,106,167),
  3925.         "hotpink3"=>array(205,96,144),
  3926.         "hotpink4"=>array(139,58,98),
  3927.         "pink1"=>array(255,181,197),
  3928.         "pink2"=>array(238,169,184),
  3929.         "pink3"=>array(205,145,158),
  3930.         "pink4"=>array(139,99,108),
  3931.         "lightpink1"=>array(255,174,185),
  3932.         "lightpink2"=>array(238,162,173),
  3933.         "lightpink3"=>array(205,140,149),
  3934.         "lightpink4"=>array(139,95,101),
  3935.         "palevioletred1"=>array(255,130,171),
  3936.         "palevioletred2"=>array(238,121,159),
  3937.         "palevioletred3"=>array(205,104,137),
  3938.         "palevioletred4"=>array(139,71,93),
  3939.         "maroon1"=>array(255,52,179),
  3940.         "maroon2"=>array(238,48,167),
  3941.         "maroon3"=>array(205,41,144),
  3942.         "maroon4"=>array(139,28,98),
  3943.         "violetred1"=>array(255,62,150),
  3944.         "violetred2"=>array(238,58,140),
  3945.         "violetred3"=>array(205,50,120),
  3946.         "violetred4"=>array(139,34,82),
  3947.         "magenta1"=>array(255,0,255),
  3948.         "magenta2"=>array(238,0,238),
  3949.         "magenta3"=>array(205,0,205),
  3950.         "magenta4"=>array(139,0,139),
  3951.         "mediumred"=>array(140,34,34),         
  3952.         "orchid1"=>array(255,131,250),
  3953.         "orchid2"=>array(238,122,233),
  3954.         "orchid3"=>array(205,105,201),
  3955.         "orchid4"=>array(139,71,137),
  3956.         "plum1"=>array(255,187,255),
  3957.         "plum2"=>array(238,174,238),
  3958.         "plum3"=>array(205,150,205),
  3959.         "plum4"=>array(139,102,139),
  3960.         "mediumorchid1"=>array(224,102,255),
  3961.         "mediumorchid2"=>array(209,95,238),
  3962.         "mediumorchid3"=>array(180,82,205),
  3963.         "mediumorchid4"=>array(122,55,139),
  3964.         "darkorchid1"=>array(191,62,255),
  3965.         "darkorchid2"=>array(178,58,238),
  3966.         "darkorchid3"=>array(154,50,205),
  3967.         "darkorchid4"=>array(104,34,139),
  3968.         "purple1"=>array(155,48,255),
  3969.         "purple2"=>array(145,44,238),
  3970.         "purple3"=>array(125,38,205),
  3971.         "purple4"=>array(85,26,139),
  3972.         "mediumpurple1"=>array(171,130,255),
  3973.         "mediumpurple2"=>array(159,121,238),
  3974.         "mediumpurple3"=>array(137,104,205),
  3975.         "mediumpurple4"=>array(93,71,139),
  3976.         "thistle1"=>array(255,225,255),
  3977.         "thistle2"=>array(238,210,238),
  3978.         "thistle3"=>array(205,181,205),
  3979.         "thistle4"=>array(139,123,139),
  3980.         "gray1"=>array(10,10,10),
  3981.         "gray2"=>array(40,40,30),
  3982.         "gray3"=>array(70,70,70),
  3983.         "gray4"=>array(100,100,100),
  3984.         "gray5"=>array(130,130,130),
  3985.         "gray6"=>array(160,160,160),
  3986.         "gray7"=>array(190,190,190),
  3987.         "gray8"=>array(210,210,210),
  3988.         "gray9"=>array(240,240,240),
  3989.         "darkgray"=>array(100,100,100),
  3990.         "darkblue"=>array(0,0,139),
  3991.         "darkcyan"=>array(0,139,139),
  3992.         "darkmagenta"=>array(139,0,139),
  3993.         "darkred"=>array(139,0,0),
  3994.         "silver"=>array(192, 192, 192),
  3995.         "eggplant"=>array(144,176,168),
  3996.         "lightgreen"=>array(144,238,144));        
  3997.     }
  3998. //----------------
  3999. // PUBLIC METHODS
  4000.     // Colors can be specified as either
  4001.     // 1. #xxxxxx            HTML style
  4002.     // 2. "colorname"     as a named color
  4003.     // 3. array(r,g,b)    RGB triple
  4004.     // This function translates this to a native RGB format and returns an 
  4005.     // RGB triple.
  4006.     function Color($aColor) {
  4007.     if (is_string($aColor)) {
  4008.         // Strip of any alpha factor
  4009.         $aColor = strtok($aColor,'@');
  4010.         $alpha = 0+strtok($aColor);
  4011.         // Extract potential adjustment figure at end of color
  4012.         // specification
  4013.         $aColor = strtok($aColor,":");
  4014.         $adj = 0+strtok(":");
  4015.         if( $adj==0 ) $adj=1;
  4016.         if (substr($aColor, 0, 1) == "#") {
  4017.         return array($adj*hexdec(substr($aColor, 1, 2)), 
  4018.                  $adj*hexdec(substr($aColor, 3, 2)),
  4019.                  $adj*hexdec(substr($aColor, 5, 2)),
  4020.                  $alpha);
  4021.         } else {
  4022.               if(!isset($this->rgb_table[$aColor]) )
  4023.             JpGraphError::Raise(" Unknown color: <strong>$aColor</strong>");
  4024.         $tmp=$this->rgb_table[$aColor];
  4025.         return array($adj*$tmp[0],$adj*$tmp[1],$adj*$tmp[2],$alpha);
  4026.         }
  4027.     } elseif( is_array($aColor) ) {
  4028.         if( count($aColor)==3 ) {
  4029.         $aColor[3]=0;
  4030.         return $aColor;
  4031.         }
  4032.         else
  4033.         return $aColor;
  4034.     }
  4035.     else
  4036.         JpGraphError::Raise(" Unknown color specification: $aColor , size=".count($aColor));
  4037.     }
  4038.     
  4039.     // Compare two colors
  4040.     // return true if equal
  4041.     function Equal($aCol1,$aCol2) {
  4042.     $c1 = $this->Color($aCol1);
  4043.     $c2 = $this->Color($aCol2);
  4044.     if( $c1[0]==$c2[0] && $c1[1]==$c2[1] && $c1[2]==$c2[2] )
  4045.         return true;
  4046.     else
  4047.         return false;
  4048.     }
  4049.     
  4050.     // Allocate a new color in the current image
  4051.     // Return new color index, -1 if no more colors could be allocated
  4052.     function Allocate($aColor,$aAlpha=0.0) {
  4053.     list ($r, $g, $b, $a) = $this->color($aColor);
  4054.     // If alpha is specified in the color string then this
  4055.     // takes precedence over the second argument
  4056.     if( $a > 0 )
  4057.         $aAlpha = $a;
  4058.     if(@$GLOBALS['gd2']==true) {
  4059.         if( $aAlpha < 0 || $aAlpha > 1 ) {
  4060.         JpGraphError::Raise('Alpha parameter for color must be between 0.0 and 1.0');
  4061.         exit(1);
  4062.         }
  4063.         return imagecolorresolvealpha($this->img, $r, $g, $b, round($aAlpha * 127));
  4064.     } else {
  4065.         $index = imagecolorexact($this->img, $r, $g, $b);
  4066.         if ($index == -1) {
  4067.               $index = imagecolorallocate($this->img, $r, $g, $b);
  4068.               if( USE_APPROX_COLORS && $index == -1 )
  4069.             $index = imagecolorresolve($this->img, $r, $g, $b);
  4070.         } 
  4071.         return $index;
  4072.     }
  4073.     }
  4074. } // Class
  4075.  
  4076.     
  4077. //===================================================
  4078. // CLASS Image
  4079. // Description: Wrapper class with some goodies to form the
  4080. // Interface to low level image drawing routines.
  4081. //===================================================
  4082. class Image {
  4083.     var $img_format;
  4084.     var $expired=true;
  4085.     var $img;
  4086.     var $left_margin=30,$right_margin=30,$top_margin=20,$bottom_margin=30;
  4087.     var $plotwidth=0,$plotheight=0;
  4088.     var $rgb;
  4089.     var $current_color,$current_color_name;
  4090.     var $lastx=0, $lasty=0;
  4091.     var $width, $height;
  4092.     var $line_weight=1;
  4093.     var $line_style=1;    // Default line style is solid
  4094.     var $obs_list=array();
  4095.     var $font_size=12,$font_family=FF_FONT1, $font_style=FS_NORMAL;
  4096.     var $font_file='';
  4097.     var $text_halign="left",$text_valign="bottom";
  4098.     var $ttf=null;
  4099.     var $use_anti_aliasing=false;
  4100.     var $quality=null;
  4101.     var $colorstack=array(),$colorstackidx=0;
  4102.     //---------------
  4103.     // CONSTRUCTOR
  4104.     function Image($aWidth,$aHeight,$aFormat=DEFAULT_GFORMAT) {
  4105.     $this->CreateImgCanvas($aWidth,$aHeight);
  4106.     if( !$this->SetImgFormat($aFormat) ) {
  4107.         JpGraphError::Raise("JpGraph: Selected graphic format is either not supported or unknown [$aFormat]");
  4108.     }
  4109.     $this->ttf = new TTF();
  4110.     }
  4111.     
  4112.     function SetAutoMargin() {    
  4113.     GLOBAL $gJpgBrandTiming;
  4114.     $min_bm=0;
  4115.     if( $gJpgBrandTiming )
  4116.         $min_bm=15;        
  4117.     $lm = max(0,$this->width/7);
  4118.     $rm = max(0,$this->width/10);
  4119.     $tm = max(0,$this->height/7);
  4120.     $bm = max($min_bm,$this->height/7);
  4121.     $this->SetMargin($lm,$rm,$tm,$bm);        
  4122.     }
  4123.  
  4124.     function CreateRawCanvas($aWidth=0,$aHeight=0) {
  4125.     if( @$GLOBALS['gd2']==true && USE_TRUECOLOR ) {
  4126.         $this->img = @imagecreatetruecolor($aWidth, $aHeight);
  4127.         if( $this->img < 1 ) {
  4128.         die("<font color=red><b>JpGraph Error:</b></font> Can't create truecolor image. Check that you really have GD2 library installed.");
  4129.         }
  4130.         $this->SetAlphaBlending();
  4131.         imagefilledrectangle($this->img, 0, 0, $aWidth, $aHeight, 0xffffff);
  4132.     } else {
  4133.         $this->img = @imagecreate($aWidth, $aHeight);    
  4134.         if( $this->img < 1 ) {
  4135.         die("<font color=red><b>JpGraph Error:</b></font> Can't create image. Check that you really have the GD library installed.");
  4136.         }
  4137.     }        
  4138.     }
  4139.  
  4140.     function CopyCanvasH($aToHdl,$aFromHdl,$aToX,$aToY,$aFromX,$aFromY,$aWidth,$aHeight) {
  4141.     imagecopyresized($aToHdl,$aFromHdl,
  4142.              $aToX,$aToY,$aFromX,$aFromY, $aWidth,$aHeight,$aWidth,$aHeight);
  4143.     }
  4144.  
  4145.     function SetAlphaBlending($aFlg=true) {
  4146.     if( $GLOBALS['gd2'] )
  4147.         ImageAlphaBlending($this->img,$aFlg);
  4148.     else
  4149.         JpGraphError::Raise('Alphablending requires GD 2.x or higher.');
  4150.     }
  4151.  
  4152.     function SetCanvasH($aHdl) {
  4153.     $this->img = $aHdl;
  4154.     $this->rgb->img = $aHdl;
  4155.     }
  4156.  
  4157.     function CloneCanvasH() {
  4158.     $oldimage = $this->img;
  4159.     $this->CreateRawCanvas($this->width,$this->height);
  4160.     $this->rgb->img = $this->img;
  4161.     imagecopy($this->img,$oldimage,
  4162.           $this->left_margin,$this->top_margin,
  4163.           $this->left_margin,$this->top_margin,
  4164.           $this->plotwidth+1,$this->plotheight);
  4165.     return $oldimage;
  4166.     }
  4167.     
  4168.     function CreateImgCanvas($aWidth=0,$aHeight=0) {
  4169.     $this->width=$aWidth;
  4170.     $this->height=$aHeight;        
  4171.  
  4172.     if( $aWidth==0 || $aHeight==0 ) {
  4173.         // We will set the final size later. 
  4174.         // Note: The size must be specified before any other
  4175.         // img routines that stroke anything are called.
  4176.         $this->img = null;
  4177.         $this->rgb = null;
  4178.         return;
  4179.     }
  4180.     
  4181.     $this->SetAutoMargin();        
  4182.  
  4183.     $this->CreateRawCanvas($aWidth,$aHeight);
  4184.  
  4185.     $this->rgb = new RGB($this->img);                
  4186.         
  4187.     // First index is background so this will be white
  4188.     $this->SetColor("white");
  4189.     }
  4190.     
  4191.                 
  4192.     //---------------
  4193.     // PUBLIC METHODS    
  4194.  
  4195.     // Add observer. The observer will be notified when
  4196.     // the margin changes
  4197.     function AddObserver($aMethod,&$aObject) {
  4198.     $this->obs_list[]=array($aMethod,&$aObject);
  4199.     }
  4200.     
  4201.     // Call all observers
  4202.     function NotifyObservers() {
  4203.     //    foreach($this->obs_list as $o)
  4204.     //        $o[1]->$o[0]($this);
  4205.     for($i=0; $i < count($this->obs_list); ++$i) {
  4206.         $obj = & $this->obs_list[$i][1];
  4207.         $method = $this->obs_list[$i][0];
  4208.         $obj->$method($this);
  4209.     }
  4210.     }    
  4211.     
  4212.     function SetFont($family,$style=FS_NORMAL,$size=10) {
  4213.     if($family==FONT1_BOLD || $family==FONT2_BOLD || $family==FONT0 || $family==FONT1 || $family==FONT2 )
  4214.         JpGraphError::Raise(" Usage of FONT0, FONT1, FONT2 is deprecated. Use FF_xxx instead.");
  4215.     
  4216.     
  4217.     $this->font_family=$family;
  4218.     $this->font_style=$style;
  4219.     $this->font_size=$size;
  4220.     $this->font_file='';
  4221.     if( ($this->font_family==FF_FONT1 || $this->font_family==FF_FONT2) && $this->font_style==FS_BOLD ){
  4222.         ++$this->font_family;
  4223.     }
  4224.     if( $this->font_family > FF_FONT2+1 ) { // A TTF font so get the font file
  4225.  
  4226.         // Check that this PHP has support for TTF fonts
  4227.         if( !function_exists('imagettfbbox') ) {
  4228.         JpGraphError::Raise('This PHP build has not been configured with TTF support. You need to recompile your PHP installation with FreeType support.');
  4229.         exit();
  4230.         }
  4231.         $this->font_file = $this->ttf->File($this->font_family,$this->font_style);
  4232.     }
  4233.     }
  4234.     
  4235.     // Get the specific height for a text string
  4236.     function GetTextHeight($txt="",$angle=0) {
  4237.     $tmp = split("\n",$txt);
  4238.     $n = count($tmp);
  4239.     $m=0;
  4240.     for($i=0; $i< $n; ++$i)
  4241.         $m = max($m,strlen($tmp[$i]));
  4242.  
  4243.     if( $this->font_family <= FF_FONT2+1 ) {
  4244.         if( $angle==0 )
  4245.         return $n*imagefontheight($this->font_family);
  4246.         else 
  4247.         return $m*imagefontwidth($this->font_family);
  4248.     }
  4249.     else {
  4250.         $bbox = ImageTTFBBox($this->font_size,$angle,$this->font_file,$txt);
  4251.         return $bbox[1]-$bbox[5];
  4252.     }
  4253.     }
  4254.     
  4255.     // Estimate font height
  4256.     function GetFontHeight($angle=0) {
  4257.     $txt = "XOMg";
  4258.     return $this->GetTextHeight($txt,$angle);
  4259.     }
  4260.     
  4261.     // Approximate font width with width of letter "O"
  4262.     function GetFontWidth($angle=0) {
  4263.     $txt = 'O';
  4264.     return $this->GetTextWidth($txt,$angle);
  4265.     }
  4266.     
  4267.     // Get actual width of text in absolute pixels
  4268.     function GetTextWidth($txt,$angle=0) {
  4269.  
  4270.     $tmp = split("\n",$txt);
  4271.     $n = count($tmp);
  4272.     $m=0;
  4273.     for($i=0; $i < $n; ++$i) {
  4274.         $l=strlen($tmp[$i]);
  4275.         if( $l > $m ) {
  4276.         $m = $l;
  4277.         }
  4278.     }
  4279.  
  4280.     if( $this->font_family <= FF_FONT2+1 ) {
  4281.         if( $angle==0 ) {
  4282.         $width=$m*imagefontwidth($this->font_family);
  4283.         return $width;
  4284.         }
  4285.         else {
  4286.         // 90 degrees internal so height becomes width
  4287.         return $n*imagefontheight($this->font_family); 
  4288.         }
  4289.     }
  4290.     else {
  4291.         // For TTF fonts we must walk through a lines and find the 
  4292.         // widest one which we use as the width of the multi-line
  4293.         // paragraph
  4294.         $m=0;
  4295.         for( $i=0; $i < $n; ++$i ) {
  4296.         $bbox = ImageTTFBBox($this->font_size,$angle,$this->font_file,$tmp[$i]);
  4297.         $mm =  $bbox[2] - $bbox[0];
  4298.         if( $mm > $m ) 
  4299.             $m = $mm;
  4300.         }
  4301.         return $m;
  4302.     }
  4303.     }
  4304.     
  4305.     // Draw text with a box around it
  4306.     function StrokeBoxedText($x,$y,$txt,$dir=0,$fcolor="white",$bcolor="black",
  4307.                  $shadowcolor=false,$paragraph_align="left",
  4308.                  $xmarg=6,$ymarg=4,$cornerradius=0,$dropwidth=3) {
  4309.  
  4310.     if( !is_numeric($dir) ) {
  4311.         if( $dir=="h" ) $dir=0;
  4312.         elseif( $dir=="v" ) $dir=90;
  4313.         else JpGraphError::Raise(" Unknown direction specified in call to StrokeBoxedText() [$dir]");
  4314.     }
  4315.         
  4316.     if( $this->font_family >= FF_FONT0 && $this->font_family <= FF_FONT2+1) {    
  4317.         $width=$this->GetTextWidth($txt,$dir) ;
  4318.         $height=$this->GetTextHeight($txt,$dir) ;
  4319.     }
  4320.     else {
  4321.         $width=$this->GetBBoxWidth($txt,$dir) ;
  4322.         $height=$this->GetBBoxHeight($txt,$dir) ;
  4323.     }
  4324.     
  4325.     $height += 2*$ymarg;
  4326.     $width  += 2*$xmarg;
  4327.  
  4328.     if( $this->text_halign=="right" ) $x -= $width;
  4329.     elseif( $this->text_halign=="center" ) $x -= $width/2;
  4330.     if( $this->text_valign=="bottom" ) $y -= $height;
  4331.     elseif( $this->text_valign=="center" ) $y -= $height/2;
  4332.     
  4333.     if( $shadowcolor ) {
  4334.         $this->PushColor($shadowcolor);
  4335.         $this->FilledRoundedRectangle($x-$xmarg+$dropwidth,$y-$ymarg+$dropwidth,
  4336.                       $x+$width+$dropwidth,$y+$height+$dropwidth,
  4337.                       $cornerradius);
  4338.         $this->PopColor();
  4339.         $this->PushColor($fcolor);
  4340.         $this->FilledRoundedRectangle($x-$xmarg,$y-$ymarg,$x+$width,$y+$height,$cornerradius);        
  4341.         $this->PopColor();
  4342.         $this->PushColor($bcolor);
  4343.         $this->RoundedRectangle($x-$xmarg,$y-$ymarg,$x+$width,$y+$height,$cornerradius);
  4344.         $this->PopColor();
  4345.     }
  4346.     else {
  4347.         if( $fcolor ) {
  4348.         $oc=$this->current_color;
  4349.         $this->SetColor($fcolor);
  4350.         $this->FilledRoundedRectangle($x-$xmarg,$y-$ymarg,$x+$width,$y+$height,$cornerradius);
  4351.         $this->current_color=$oc;
  4352.         }
  4353.         if( $bcolor ) {
  4354.         $oc=$this->current_color;
  4355.         $this->SetColor($bcolor);            
  4356.         $this->RoundedRectangle($x-$xmarg,$y-$ymarg,$x+$width,$y+$height,$cornerradius);
  4357.         $this->current_color=$oc;            
  4358.         }
  4359.     }
  4360.         
  4361.     $h=$this->text_halign;
  4362.     $v=$this->text_valign;
  4363.     $this->SetTextAlign("left","top");
  4364.     $this->StrokeText($x, $y, $txt, $dir, $paragraph_align);
  4365.     $this->SetTextAlign($h,$v);
  4366.     }
  4367.  
  4368.     // Set text alignment    
  4369.     function SetTextAlign($halign,$valign="bottom") {
  4370.     $this->text_halign=$halign;
  4371.     $this->text_valign=$valign;
  4372.     }
  4373.     
  4374.     // Should we use anti-aliasing. Note: This really slows down graphics!
  4375.     function SetAntiAliasing() {
  4376.     $this->use_anti_aliasing=true;
  4377.     }
  4378.  
  4379.     function _StrokeBuiltinFont($x,$y,$txt,$dir=0,$paragraph_align="left") {
  4380.  
  4381.     if( is_numeric($dir) && $dir!=90 && $dir!=0) 
  4382.         JpGraphError::Raise(" Internal font does not support drawing text at arbitrary angle. Use TTF fonts instead.");
  4383.     
  4384.     $h=$this->GetTextHeight($txt);
  4385.     $fh=$this->GetFontHeight();
  4386.     $w=$this->GetTextWidth($txt);
  4387.     
  4388.     if( $this->text_halign=="right")                 
  4389.         $x -= $dir==0 ? $w : $h;
  4390.     elseif( $this->text_halign=="center" ) {
  4391.         // For center we subtract 1 pixel since this makes the middle
  4392.         // be prefectly in the middle
  4393.         $x -= $dir==0 ? $w/2-1 : $h/2;
  4394.     }
  4395.     if( $this->text_valign=="top" )
  4396.         $y += $dir==0 ? $h : $w;
  4397.     elseif( $this->text_valign=="center" )                 
  4398.         $y += $dir==0 ? $h/2 : $w/2;
  4399.     
  4400.     if( $dir==90 )
  4401.         imagestringup($this->img,$this->font_family,$x,$y,$txt,$this->current_color);
  4402.     else {
  4403.         if( ereg("\n",$txt) ) { 
  4404.         $tmp = split("\n",$txt);
  4405.         for($i=0; $i < count($tmp); ++$i) {
  4406.             $w1 = $this->GetTextWidth($tmp[$i]);
  4407.             if( $paragraph_align=="left" ) {
  4408.             imagestring($this->img,$this->font_family,$x,$y-$h+1+$i*$fh,$tmp[$i],$this->current_color);
  4409.             }
  4410.             elseif( $paragraph_align=="right" ) {
  4411.             imagestring($this->img,$this->font_family,$x+($w-$w1),
  4412.                     $y-$h+1+$i*$fh,$tmp[$i],$this->current_color);
  4413.             }
  4414.             else {
  4415.             imagestring($this->img,$this->font_family,$x+$w/2-$w1/2,
  4416.                     $y-$h+1+$i*$fh,$tmp[$i],$this->current_color);
  4417.             }
  4418.         }
  4419.         } 
  4420.         else {
  4421.         //Put the text
  4422.         imagestring($this->img,$this->font_family,$x,$y-$h+1,$txt,$this->current_color);
  4423.         }
  4424.     }
  4425.     }
  4426.  
  4427.     function AddTxtCR($aTxt) {
  4428.     // If the user has just specified a '\n'
  4429.     // instead of '\n\t' we have to add '\r' since
  4430.     // the width will be too muchy otherwise since when
  4431.     // we print we stroke the individually lines by hand.
  4432.     $e = explode("\n",$aTxt);
  4433.     $n = count($e);
  4434.     for($i=0; $i<$n; ++$i) {
  4435.         $e[$i]=str_replace("\r","",$e[$i]);
  4436.     }
  4437.     return implode("\n\r",$e);
  4438.     }
  4439.  
  4440.     function GetBBoxTTF($aTxt,$aAngle=0) {
  4441.     // Normalize the bounding box to become a minimum
  4442.     // enscribing rectangle
  4443.  
  4444.     $aTxt = $this->AddTxtCR($aTxt);
  4445.  
  4446.     if( !is_readable($this->font_file) ) {
  4447.         JpGraphError::Raise('Can not read font file ('.$this->font_file.') in call to Image::GetBBoxTTF. Please make sure that you have set a font before calling this method and that the font is installed in the TTF directory.');
  4448.     }
  4449.     $bbox=ImageTTFBBox($this->font_size,$aAngle,$this->font_file,$aTxt);
  4450.     if( $aAngle==0 ) 
  4451.         return $bbox;
  4452.     if( $aAngle >= 0 ) {
  4453.         if(  $aAngle <= 90 ) { //<=0        
  4454.         $bbox = array($bbox[6],$bbox[1],$bbox[2],$bbox[1],
  4455.                   $bbox[2],$bbox[5],$bbox[6],$bbox[5]);
  4456.         }
  4457.         elseif(  $aAngle <= 180 ) { //<= 2
  4458.         $bbox = array($bbox[4],$bbox[7],$bbox[0],$bbox[7],
  4459.                   $bbox[0],$bbox[3],$bbox[4],$bbox[3]);
  4460.         }
  4461.         elseif(  $aAngle <= 270 )  { //<= 3
  4462.         $bbox = array($bbox[2],$bbox[5],$bbox[6],$bbox[5],
  4463.                   $bbox[6],$bbox[1],$bbox[2],$bbox[1]);
  4464.         }
  4465.         else {
  4466.         $bbox = array($bbox[0],$bbox[3],$bbox[4],$bbox[3],
  4467.                   $bbox[4],$bbox[7],$bbox[0],$bbox[7]);
  4468.         }
  4469.     }
  4470.     elseif(  $aAngle < 0 ) {
  4471.         if( $aAngle <= -270 ) { // <= -3
  4472.         $bbox = array($bbox[6],$bbox[1],$bbox[2],$bbox[1],
  4473.                   $bbox[2],$bbox[5],$bbox[6],$bbox[5]);
  4474.         }
  4475.         elseif( $aAngle <= -180 ) { // <= -2
  4476.         $bbox = array($bbox[0],$bbox[3],$bbox[4],$bbox[3],
  4477.                   $bbox[4],$bbox[7],$bbox[0],$bbox[7]);
  4478.         }
  4479.         elseif( $aAngle <= -90 ) { // <= -1
  4480.         $bbox = array($bbox[2],$bbox[5],$bbox[6],$bbox[5],
  4481.                   $bbox[6],$bbox[1],$bbox[2],$bbox[1]);
  4482.         }
  4483.         else {
  4484.         $bbox = array($bbox[0],$bbox[3],$bbox[4],$bbox[3],
  4485.                   $bbox[4],$bbox[7],$bbox[0],$bbox[7]);
  4486.         }
  4487.     }    
  4488.     return $bbox;
  4489.     }
  4490.  
  4491.     function GetBBoxHeight($aTxt,$aAngle=0) {
  4492.     $box = $this->GetBBoxTTF($aTxt,$aAngle);
  4493.     return $box[1]-$box[7]+1;
  4494.     }
  4495.  
  4496.     function GetBBoxWidth($aTxt,$aAngle=0) {
  4497.     $box = $this->GetBBoxTTF($aTxt,$aAngle);
  4498.     return $box[2]-$box[0]+1;    
  4499.     }
  4500.  
  4501.     function _StrokeTTF($x,$y,$txt,$dir=0,$paragraph_align="left",$debug=false) {
  4502.  
  4503.     // Remember the anchor point before adjustment
  4504.     /*
  4505.     if( $debug ) {
  4506.         $ox=$x;
  4507.         $oy=$y;
  4508.     }
  4509.     */
  4510.  
  4511.     if( !ereg("\n",$txt) || ($dir>0 && ereg("\n",$txt)) ) {
  4512.         // Format a single line
  4513.  
  4514.         $txt = $this->AddTxtCR($txt);
  4515.  
  4516.         $bbox=$this->GetBBoxTTF($txt,$dir);
  4517.  
  4518.         // Align x,y ot lower left corner of bbox
  4519.         $x -= $bbox[0];
  4520.         $y -= $bbox[1];
  4521.  
  4522.         // Note to self: "topanchor" is deprecated after we changed the
  4523.         // bopunding box stuff. 
  4524.         if( $this->text_halign=="right" || $this->text_halign=="topanchor" ) 
  4525.         $x -= $bbox[2]-$bbox[0];
  4526.         elseif( $this->text_halign=="center" ) $x -= ($bbox[2]-$bbox[0])/2; 
  4527.         
  4528.         if( $this->text_valign=="top" ) $y += abs($bbox[5])+$bbox[1];
  4529.         elseif( $this->text_valign=="center" ) $y -= ($bbox[5]-$bbox[1])/2; 
  4530.  
  4531.  
  4532.         if($GLOBALS['gd2']) {
  4533.         $old = ImageAlphaBlending($this->img, true);
  4534.         }
  4535.  
  4536.         ImageTTFText ($this->img, $this->font_size, $dir, $x, $y, 
  4537.               $this->current_color,$this->font_file,$txt); 
  4538.  
  4539.         if($GLOBALS['gd2']) {
  4540.         ImageAlphaBlending($this->img, $old);
  4541.         }
  4542.  
  4543.         /*
  4544.             if( $debug ) {
  4545.         // Draw the bounding rectangle and the bounding box
  4546.         $box=ImageTTFBBox($this->font_size,$dir,$this->font_file,$txt);
  4547.         $p = array();
  4548.         $p1 = array();
  4549.         for($i=0; $i < 4; ++$i) {
  4550.             $p[] = $bbox[$i*2]+$x;
  4551.             $p[] = $bbox[$i*2+1]+$y;
  4552.             $p1[] = $box[$i*2]+$x;
  4553.             $p1[] = $box[$i*2+1]+$y;
  4554.         }
  4555.  
  4556.         // Draw bounding box
  4557.         $this->PushColor('green');
  4558.         $this->Polygon($p1,true);
  4559.         $this->PopColor();
  4560.         
  4561.         // Draw bounding rectangle
  4562.         $this->PushColor('darkgreen');
  4563.         $this->Polygon($p,true);
  4564.         $this->PopColor();
  4565.         
  4566.         // Draw a cross at the anchor point
  4567.         $this->PushColor('red');
  4568.         $this->Line($ox-15,$oy,$ox+15,$oy);
  4569.         $this->Line($ox,$oy-15,$ox,$oy+15);
  4570.         $this->PopColor();
  4571.             }
  4572.         */
  4573.  
  4574.     }
  4575.     else {
  4576.         // Format a text paragraph
  4577.         $fh=$this->GetFontHeight();
  4578.  
  4579.         // Line margin is 20% of font height
  4580.         $linemargin=round($fh*0.2);
  4581.         $fh += $linemargin;
  4582.         $w=$this->GetTextWidth($txt);
  4583.  
  4584.         $y -= $linemargin/2;
  4585.         $tmp = split("\n",$txt);
  4586.         $nl = count($tmp);
  4587.         $h = $nl * $fh;
  4588.  
  4589.         if( $this->text_halign=="right")                 
  4590.         $x -= $dir==0 ? $w : $h;
  4591.         elseif( $this->text_halign=="center" ) {
  4592.         $x -= $dir==0 ? $w/2 : $h/2;
  4593.         }
  4594.         
  4595.         if( $this->text_valign=="top" )
  4596.         $y +=    $dir==0 ? $h : $w;
  4597.         elseif( $this->text_valign=="center" )                 
  4598.         $y +=    $dir==0 ? $h/2 : $w/2;
  4599.  
  4600.         // Here comes a tricky bit. 
  4601.         // Since we have to give the position for the string at the
  4602.         // baseline this means thaht text will move slightly up
  4603.         // and down depending on any of it's character descend below
  4604.         // the baseline, for example a 'g'. To adjust the Y-position
  4605.         // we therefore adjust the text with the baseline Y-offset
  4606.         // as used for the current font and size. This will keep the
  4607.         // baseline at a fixed positoned disregarding the actual 
  4608.         // characters in the string. 
  4609.         $standardbox=ImageTTFBBox($this->font_size,$dir,$this->font_file,'Gg');
  4610.         $yadj = $standardbox[1];
  4611.         $xadj = $standardbox[0];
  4612.         for($i=0; $i < $nl; ++$i) {
  4613.         $wl = $this->GetTextWidth($tmp[$i]);
  4614.         $bbox=ImageTTFBBox($this->font_size,$dir,$this->font_file,$tmp[$i]);
  4615.         if( $paragraph_align=="left" ) {
  4616.             $xl = $x; 
  4617.         }
  4618.         elseif( $paragraph_align=="right" ) {
  4619.             $xl = $x + ($w-$wl);
  4620.         }
  4621.         else {
  4622.             // Center
  4623.             $xl = $x + $w/2 - $wl/2 ;
  4624.         }
  4625.  
  4626.         $xl -= $bbox[0];
  4627.         $yl = $y  - $yadj; 
  4628.         $xl = $xl  - $xadj; 
  4629.         if($GLOBALS['gd2']) {
  4630.             $old = ImageAlphaBlending($this->img, true);
  4631.         }
  4632.  
  4633.         ImageTTFText ($this->img, $this->font_size, $dir, $xl, $yl-($h-$fh)+$fh*$i, 
  4634.                   $this->current_color,$this->font_file,$tmp[$i]); 
  4635.         
  4636.         if($GLOBALS['gd2']) {
  4637.             ImageAlphaBlending($this->img, $old);
  4638.         }
  4639.         }
  4640.     }
  4641.     }
  4642.     
  4643.     function StrokeText($x,$y,$txt,$dir=0,$paragraph_align="left",$debug=false) {
  4644.  
  4645.     $x = round($x);
  4646.     $y = round($y);
  4647.  
  4648.     // Do special language encoding
  4649.     if( LANGUAGE_CYRILLIC )
  4650.         $txt = LanguageConv::ToCyrillic($txt);
  4651.  
  4652.     if( !is_numeric($dir) )
  4653.         JpGraphError::Raise(" Direction for text most be given as an angle between 0 and 90.");
  4654.             
  4655.     if( $this->font_family >= FF_FONT0 && $this->font_family <= FF_FONT2+1) {    
  4656.         $this->_StrokeBuiltinFont($x,$y,$txt,$dir,$paragraph_align,$debug);
  4657.     }
  4658.     elseif($this->font_family >= FF_COURIER && $this->font_family <= FF_BOOK)  { 
  4659.         $this->_StrokeTTF($x,$y,$txt,$dir,$paragraph_align,$debug);
  4660.     }
  4661.     else
  4662.         JpGraphError::Raise(" Unknown font font family specification. ");
  4663.     }
  4664.     
  4665.     function SetMargin($lm,$rm,$tm,$bm) {
  4666.     $this->left_margin=$lm;
  4667.     $this->right_margin=$rm;
  4668.     $this->top_margin=$tm;
  4669.     $this->bottom_margin=$bm;
  4670.     $this->plotwidth=$this->width - $this->left_margin-$this->right_margin ; 
  4671.     $this->plotheight=$this->height - $this->top_margin-$this->bottom_margin ;
  4672.     if( $this->plotwidth < 0  || $this->plotheight < 0 )
  4673.         JpGraphError::raise("To small plot area. ($lm,$rm,$tm,$bm : $this->plotwidth x $this->plotheight). With the given image size and margins there is to little space left for the plot. Increase the plot size or reduce the margins.");
  4674.     $this->NotifyObservers();
  4675.     }
  4676.  
  4677.     function SetTransparent($color) {
  4678.     imagecolortransparent ($this->img,$this->rgb->allocate($color));
  4679.     }
  4680.     
  4681.     function SetColor($color,$aAlpha=0) {
  4682.     $this->current_color_name = $color;
  4683.     $this->current_color=$this->rgb->allocate($color,$aAlpha);
  4684.     if( $this->current_color == -1 ) {
  4685.         $tc=imagecolorstotal($this->img);
  4686.         JpGraphError::Raise("<b> Can't allocate any more colors.</b><br>
  4687.                 Image has already allocated maximum of <b>$tc colors</b>. 
  4688.                 This might happen if you have anti-aliasing turned on
  4689.                 together with a background image or perhaps gradient fill 
  4690.                 since this requires many, many colors. Try to turn off
  4691.                 anti-aliasing.<p>
  4692.                 If there is still a problem try downgrading the quality of
  4693.                 the background image to use a smaller pallete to leave some 
  4694.                 entries for your graphs. You should try to limit the number
  4695.                 of colors in your background image to 64.<p>
  4696.                 If there is still problem set the constant 
  4697. <pre>
  4698. DEFINE(\"USE_APPROX_COLORS\",true);
  4699. </pre>
  4700.                 in jpgraph.php This will use approximative colors
  4701.                 when the palette is full.
  4702.                 <p>
  4703.                 Unfortunately there is not much JpGraph can do about this
  4704.                 since the palette size is a limitation of current graphic format and
  4705.                 what the underlying GD library suppports."); 
  4706.     }
  4707.     return $this->current_color;
  4708.     }
  4709.     
  4710.     function PushColor($color) {
  4711.     if( $color != "" ) {
  4712.         $this->colorstack[$this->colorstackidx]=$this->current_color_name;
  4713.         $this->colorstack[$this->colorstackidx+1]=$this->current_color;
  4714.         $this->colorstackidx+=2;
  4715.         $this->SetColor($color);
  4716.     }
  4717.     else {
  4718.         JpGraphError::Raise("Color specified as empty string in PushColor().");
  4719.     }
  4720.     }
  4721.     
  4722.     function PopColor() {
  4723.     if($this->colorstackidx<1)
  4724.         JpGraphError::Raise(" Negative Color stack index. Unmatched call to PopColor()");
  4725.     $this->current_color=$this->colorstack[--$this->colorstackidx];
  4726.     $this->current_color_name=$this->colorstack[--$this->colorstackidx];
  4727.     }
  4728.     
  4729.     
  4730.     // Why this duplication? Because this way we can call this method
  4731.     // for any image and not only the current objsct
  4732.     function AdjSat($sat) {    $this->_AdjSat($this->img,$sat);    }    
  4733.     
  4734.     function _AdjSat($img,$sat) {
  4735.     $nbr = imagecolorstotal ($img);
  4736.     for( $i=0; $i<$nbr; ++$i ) {
  4737.         $colarr = imagecolorsforindex ($img,$i);
  4738.         $rgb[0]=$colarr["red"];
  4739.         $rgb[1]=$colarr["green"];
  4740.         $rgb[2]=$colarr["blue"];
  4741.         $rgb = $this->AdjRGBSat($rgb,$sat);
  4742.         imagecolorset ($img, $i, $rgb[0], $rgb[1], $rgb[2]);
  4743.     }
  4744.     }
  4745.     
  4746.     function AdjBrightContrast($bright,$contr=0) {
  4747.     $this->_AdjBrightContrast($this->img,$bright,$contr);
  4748.     }
  4749.     function _AdjBrightContrast($img,$bright,$contr=0) {
  4750.     if( $bright < -1 || $bright > 1 || $contr < -1 || $contr > 1 )
  4751.         JpGraphError::Raise(" Parameters for brightness and Contrast out of range [-1,1]");        
  4752.     $nbr = imagecolorstotal ($img);
  4753.     for( $i=0; $i<$nbr; ++$i ) {
  4754.         $colarr = imagecolorsforindex ($img,$i);
  4755.         $r = $this->AdjRGBBrightContrast($colarr["red"],$bright,$contr);
  4756.         $g = $this->AdjRGBBrightContrast($colarr["green"],$bright,$contr);
  4757.         $b = $this->AdjRGBBrightContrast($colarr["blue"],$bright,$contr);        
  4758.         imagecolorset ($img, $i, $r, $g, $b);
  4759.     }
  4760.     }
  4761.     
  4762.     // Private helper function for adj sat
  4763.     // Adjust saturation for RGB array $u. $sat is a value between -1 and 1
  4764.     // Note: Due to GD inability to handle true color the RGB values are only between
  4765.     // 8 bit. This makes saturation quite sensitive for small increases in parameter sat.
  4766.     // 
  4767.     // Tip: To get a grayscale picture set sat=-100, values <-100 changes the colors
  4768.     // to it's complement.
  4769.     // 
  4770.     // Implementation note: The saturation is implemented directly in the RGB space
  4771.     // by adjusting the perpendicular distance between the RGB point and the "grey"
  4772.     // line (1,1,1). Setting $sat>0 moves the point away from the line along the perp.
  4773.     // distance and a negative value moves the point closer to the line.
  4774.     // The values are truncated when the color point hits the bounding box along the
  4775.     // RGB axis.
  4776.     // DISCLAIMER: I'm not 100% sure this is he correct way to implement a color 
  4777.     // saturation function in RGB space. However, it looks ok and has the expected effect.
  4778.     function AdjRGBSat($rgb,$sat) {
  4779.     // TODO: Should be moved to the RGB class
  4780.     // Grey vector
  4781.     $v=array(1,1,1);
  4782.  
  4783.     // Dot product
  4784.     $dot = $rgb[0]*$v[0]+$rgb[1]*$v[1]+$rgb[2]*$v[2];
  4785.  
  4786.     // Normalize dot product
  4787.     $normdot = $dot/3;    // dot/|v|^2
  4788.  
  4789.     // Direction vector between $u and its projection onto $v
  4790.     for($i=0; $i<3; ++$i)
  4791.         $r[$i] = $rgb[$i] - $normdot*$v[$i];
  4792.  
  4793.     // Adjustment factor so that sat==1 sets the highest RGB value to 255
  4794.     if( $sat > 0 ) {
  4795.         $m=0;
  4796.         for( $i=0; $i<3; ++$i) {
  4797.         if( sign($r[$i]) == 1 && $r[$i]>0)
  4798.             $m=max($m,(255-$rgb[$i])/$r[$i]);
  4799.         }
  4800.         $tadj=$m;
  4801.     }
  4802.     else
  4803.         $tadj=1;
  4804.         
  4805.     $tadj = $tadj*$sat;    
  4806.     for($i=0; $i<3; ++$i) {
  4807.         $un[$i] = round($rgb[$i] + $tadj*$r[$i]);        
  4808.         if( $un[$i]<0 ) $un[$i]=0;        // Truncate color when they reach 0
  4809.         if( $un[$i]>255 ) $un[$i]=255;// Avoid potential rounding error
  4810.     }        
  4811.     return $un;    
  4812.     }    
  4813.  
  4814.     // Private helper function for AdjBrightContrast
  4815.     function AdjRGBBrightContrast($rgb,$bright,$contr) {
  4816.     // TODO: Should be moved to the RGB class
  4817.     // First handle contrast, i.e change the dynamic range around grey
  4818.     if( $contr <= 0 ) {
  4819.         // Decrease contrast
  4820.         $adj = abs($rgb-128) * (-$contr);
  4821.         if( $rgb < 128 ) $rgb += $adj;
  4822.         else $rgb -= $adj;
  4823.     }
  4824.     else { // $contr > 0
  4825.         // Increase contrast
  4826.         if( $rgb < 128 ) $rgb = $rgb - ($rgb * $contr);
  4827.         else $rgb = $rgb + ((255-$rgb) * $contr);
  4828.     }
  4829.     
  4830.     // Add (or remove) various amount of white
  4831.     $rgb += $bright*255;    
  4832.     $rgb=min($rgb,255);
  4833.     $rgb=max($rgb,0);
  4834.     return $rgb;    
  4835.     }
  4836.     
  4837.     function SetLineWeight($weight) {
  4838.     $this->line_weight = $weight;
  4839.     }
  4840.     
  4841.     function SetStartPoint($x,$y) {
  4842.     $this->lastx=round($x);
  4843.     $this->lasty=round($y);
  4844.     }
  4845.     
  4846.     function Arc($cx,$cy,$w,$h,$s,$e) {
  4847.     // GD Arc doesn't like negative angles
  4848.     while( $s < 0) $s = $s+360;
  4849.     while( $e < 0) $e = $e+360;
  4850.         
  4851.     imagearc($this->img,round($cx),round($cy),round($w),round($h),
  4852.          $s,$e,$this->current_color);
  4853.     }
  4854.     
  4855.     function FilledArc($xc,$yc,$w,$h,$s,$e,$style="") {
  4856.     if( $GLOBALS['gd2'] ) {
  4857.         if( $style=="" ) 
  4858.         $style=IMG_ARC_PIE;
  4859.         imagefilledarc($this->img,round($xc),round($yc),round($w),round($h),
  4860.                $s,$e,$this->current_color,$style);
  4861.         return;
  4862.     }
  4863.  
  4864.     // In GD 1.x we have to do it ourself interesting enough there is surprisingly
  4865.     // little difference in time between doing it PHP and using the optimised GD 
  4866.     // library (roughly ~20%) I had expected it to be at least 100% slower doing it
  4867.     // manually with a polygon approximation in PHP.....
  4868.     $fillcolor = $this->current_color_name;
  4869.  
  4870.     $w /= 2; // We use radius in our calculations instead
  4871.     $h /= 2;
  4872.  
  4873.     // Setup the angles so we have the same conventions as the builtin
  4874.     // FilledArc() which is a little bit strange if you ask me....
  4875.  
  4876.     $s = 360-$s;
  4877.     $e = 360-$e;
  4878.  
  4879.     if( $e > $s ) {
  4880.         $e = $e - 360;
  4881.         $da = $s - $e; 
  4882.     }
  4883.     $da = $s-$e;
  4884.  
  4885.     // We use radians
  4886.     $s *= M_PI/180;
  4887.     $e *= M_PI/180;
  4888.     $da *= M_PI/180;
  4889.  
  4890.     // Calculate a polygon approximation
  4891.     $p[0] = $xc;
  4892.     $p[1] = $yc;
  4893.  
  4894.     // Heuristic on how many polygons we need to make the
  4895.     // arc look good
  4896.     $numsteps = round(8 * abs($da) * ($w+$h)*($w+$h)/1500);
  4897.  
  4898.     if( $numsteps == 0 ) return;
  4899.     if( $numsteps < 7 ) $numsteps=7;
  4900.     $delta = abs($da)/$numsteps;
  4901.     
  4902.     $pa=array();
  4903.     $a = $s;
  4904.     for($i=1; $i<=$numsteps; ++$i ) {
  4905.         $p[2*$i] = round($xc + $w*cos($a));
  4906.         $p[2*$i+1] = round($yc - $h*sin($a));
  4907.         //$a = $s + $i*$delta; 
  4908.         $a -= $delta; 
  4909.         $pa[2*($i-1)] = $p[2*$i];
  4910.         $pa[2*($i-1)+1] = $p[2*$i+1];
  4911.     }
  4912.  
  4913.     // Get the last point at the exact ending angle to avoid
  4914.     // any rounding errors.
  4915.     $p[2*$i] = round($xc + $w*cos($e));
  4916.     $p[2*$i+1] = round($yc - $h*sin($e));
  4917.     $pa[2*($i-1)] = $p[2*$i];
  4918.     $pa[2*($i-1)+1] = $p[2*$i+1];
  4919.     $i++;
  4920.  
  4921.     $p[2*$i] = $xc;
  4922.         $p[2*$i+1] = $yc;
  4923.     if( $fillcolor != "" ) {
  4924.         $this->PushColor($fillcolor);
  4925.         imagefilledpolygon($this->img,$p,count($p)/2,$this->current_color);
  4926.         //$this->FilledPolygon($p);
  4927.         $this->PopColor();
  4928.     }
  4929.     }
  4930.  
  4931.     function FilledCakeSlice($cx,$cy,$w,$h,$s,$e) {
  4932.     $this->CakeSlice($cx,$cy,$w,$h,$s,$e,$this->current_color_name);
  4933.     }
  4934.  
  4935.     function CakeSlice($xc,$yc,$w,$h,$s,$e,$fillcolor="",$arccolor="") {
  4936.     $this->PushColor($fillcolor);
  4937.     $this->FilledArc($xc,$yc,2*$w,2*$h,$s,$e);
  4938.     $this->PopColor();
  4939.  
  4940.  
  4941.     if( $arccolor != "" ) {
  4942.         $this->PushColor($arccolor);
  4943.         // We add 2 pixels to make the Arc() better aligned with
  4944.         // the filled arc. 
  4945.         $this->Arc($xc,$yc,2*$w+2,2*$h+2,$s,$e);
  4946.         $xx = $w * cos(2*M_PI - $s*M_PI/180) + $xc;
  4947.         $yy = $yc - $h * sin(2*M_PI - $s*M_PI/180);
  4948.         $this->Line($xc,$yc,$xx,$yy);
  4949.         $xx = $w * cos(2*M_PI - $e*M_PI/180) + $xc;
  4950.         $yy = $yc - $h * sin(2*M_PI - $e*M_PI/180);
  4951.         $this->Line($xc,$yc,$xx,$yy);
  4952.         $this->PopColor();
  4953.     }
  4954.  
  4955.     // if( $arccolor != "" ) {
  4956.     //$this->PushColor($arccolor);
  4957.     // Since IMG_ARC_NOFILL | IMG_ARC_EDGED does not work as described in the PHP manual
  4958.     // I have to do the edges manually with some potential rounding errors since I can't
  4959.     // be sure may endpoints gets calculated with the same accuracy as the builtin
  4960.     // Arc() function in GD
  4961.     //$this->FilledArc($cx,$cy,2*$w,2*$h,$s,$e, IMG_ARC_NOFILL | IMG_ARC_EDGED );
  4962.     //$this->PopColor();
  4963.     // }
  4964.     }
  4965.  
  4966.     function Ellipse($xc,$yc,$w,$h) {
  4967.     $this->Arc($xc,$yc,$w,$h,0,360);
  4968.     }
  4969.     
  4970.     // Breseham circle gives visually better result then using GD
  4971.     // built in arc(). It takes some more time but gives better
  4972.     // accuracy.
  4973.     function BresenhamCircle($xc,$yc,$r) {
  4974.     $d = 3-2*$r;
  4975.     $x = 0;
  4976.     $y = $r;
  4977.     while($x<=$y) {
  4978.         $this->Point($xc+$x,$yc+$y);            
  4979.         $this->Point($xc+$x,$yc-$y);
  4980.         $this->Point($xc-$x,$yc+$y);
  4981.         $this->Point($xc-$x,$yc-$y);
  4982.             
  4983.         $this->Point($xc+$y,$yc+$x);
  4984.         $this->Point($xc+$y,$yc-$x);
  4985.         $this->Point($xc-$y,$yc+$x);
  4986.         $this->Point($xc-$y,$yc-$x);
  4987.             
  4988.         if( $d<0 ) $d += 4*$x+6;
  4989.         else {
  4990.         $d += 4*($x-$y)+10;        
  4991.         --$y;
  4992.         }
  4993.         ++$x;
  4994.     }
  4995.     }
  4996.             
  4997.     function Circle($xc,$yc,$r) {
  4998.     if( USE_BRESENHAM )
  4999.         $this->BresenhamCircle($xc,$yc,$r);
  5000.     else {
  5001.  
  5002.         /*
  5003.             // Some experimental code snippet to see if we can get a decent 
  5004.         // result doing a trig-circle
  5005.         // Create an approximated circle with 0.05 rad resolution
  5006.         $end = 2*M_PI;
  5007.         $l = $r/10;
  5008.         if( $l < 3 ) $l=3;
  5009.         $step_size = 2*M_PI/(2*$r*M_PI/$l);
  5010.         $pts = array();
  5011.         $pts[] = $r + $xc;
  5012.         $pts[] = $yc;
  5013.         for( $a=$step_size; $a <= $end; $a += $step_size ) {
  5014.         $pts[] = round($xc + $r*cos($a));
  5015.         $pts[] = round($yc - $r*sin($a));
  5016.         }
  5017.         imagepolygon($this->img,$pts,count($pts)/2,$this->current_color);
  5018.         */
  5019.  
  5020.         $this->Arc($xc,$yc,$r*2,$r*2,0,360);        
  5021.  
  5022.         // For some reason imageellipse() isn't in GD 2.0.1, PHP 4.1.1
  5023.         //imageellipse($this->img,$xc,$yc,$r,$r,$this->current_color);
  5024.     }
  5025.     }
  5026.     
  5027.     function FilledCircle($xc,$yc,$r) {
  5028.     if( $GLOBALS['gd2'] ) {
  5029.         imagefilledellipse($this->img,round($xc),round($yc),
  5030.                    2*$r,2*$r,$this->current_color);
  5031.     }
  5032.     else {
  5033.         for( $i=1; $i < 2*$r; $i += 2 ) {
  5034.         // To avoid moire patterns we have to draw some
  5035.         // 1 extra "skewed" filled circles
  5036.         $this->Arc($xc,$yc,$i,$i,0,360);
  5037.         $this->Arc($xc,$yc,$i+1,$i,0,360);
  5038.         $this->Arc($xc,$yc,$i+1,$i+1,0,360);
  5039.         }
  5040.     }    
  5041.     }
  5042.     
  5043.     // Linear Color InterPolation
  5044.     function lip($f,$t,$p) {
  5045.     $p = round($p,1);
  5046.     $r = $f[0] + ($t[0]-$f[0])*$p;
  5047.     $g = $f[1] + ($t[1]-$f[1])*$p;
  5048.     $b = $f[2] + ($t[2]-$f[2])*$p;
  5049.     return array($r,$g,$b);
  5050.     }
  5051.  
  5052.     // Anti-aliased line. 
  5053.     // Note that this is roughly 8 times slower then a normal line!
  5054.     function WuLine($x1,$y1,$x2,$y2) {
  5055.     // Get foreground line color
  5056.     $lc = imagecolorsforindex($this->img,$this->current_color);
  5057.     $lc = array($lc["red"],$lc["green"],$lc["blue"]);
  5058.  
  5059.     $dx = $x2-$x1;
  5060.     $dy = $y2-$y1;
  5061.     
  5062.     if( abs($dx) > abs($dy) ) {
  5063.         if( $dx<0 ) {
  5064.         $dx = -$dx;$dy = -$dy;
  5065.         $tmp=$x2;$x2=$x1;$x1=$tmp;
  5066.         $tmp=$y2;$y2=$y1;$y1=$tmp;
  5067.         }
  5068.         $x=$x1<<16; $y=$y1<<16;
  5069.         $yinc = ($dy*65535)/$dx;
  5070.         while( ($x >> 16) < $x2 ) {
  5071.                 
  5072.         $bc = @imagecolorsforindex($this->img,imagecolorat($this->img,$x>>16,$y>>16));
  5073.         if( $bc <= 0 ) {
  5074.             JpGraphError::Raise('Problem with color palette and your GD setup. Please disable anti-aliasing or use GD2 with true-color. If you have GD2 library installed please make sure that you have set the USE_GD2 constant to true and that truecolor is enabled.');
  5075.         }
  5076.         $bc=array($bc["red"],$bc["green"],$bc["blue"]);
  5077.                 
  5078.         $this->SetColor($this->lip($lc,$bc,($y & 0xFFFF)/65535));
  5079.         imagesetpixel($this->img,$x>>16,$y>>16,$this->current_color);
  5080.         $this->SetColor($this->lip($lc,$bc,(~$y & 0xFFFF)/65535));
  5081.         imagesetpixel($this->img,$x>>16,($y>>16)+1,$this->current_color);
  5082.         $x += 65536; $y += $yinc;
  5083.         }
  5084.     }
  5085.     else {
  5086.         if( $dy<0 ) {
  5087.         $dx = -$dx;$dy = -$dy;
  5088.         $tmp=$x2;$x2=$x1;$x1=$tmp;
  5089.         $tmp=$y2;$y2=$y1;$y1=$tmp;
  5090.         }
  5091.         $x=$x1<<16; $y=$y1<<16;
  5092.         $xinc = ($dx*65535)/$dy;    
  5093.         while( ($y >> 16) < $y2 ) {
  5094.                 
  5095.         $bc = @imagecolorsforindex($this->img,imagecolorat($this->img,$x>>16,$y>>16));
  5096.         if( $bc <= 0 ) {
  5097.             JpGraphError::Raise('Problem with color palette and your GD setup. Please disable anti-aliasing or use GD2 with true-color. If you have GD2 library installed please make sure that you have set the USE_GD2 constant to true and truecolor is enabled.');
  5098.  
  5099.         }
  5100.  
  5101.         $bc=array($bc["red"],$bc["green"],$bc["blue"]);                
  5102.                 
  5103.         $this->SetColor($this->lip($lc,$bc,($x & 0xFFFF)/65535));
  5104.         imagesetpixel($this->img,$x>>16,$y>>16,$this->current_color);
  5105.         $this->SetColor($this->lip($lc,$bc,(~$x & 0xFFFF)/65535));
  5106.         imagesetpixel($this->img,($x>>16)+1,$y>>16,$this->current_color);
  5107.         $y += 65536; $x += $xinc;
  5108.         }
  5109.     }
  5110.     $this->SetColor($lc);
  5111.     imagesetpixel($this->img,$x2,$y2,$this->current_color);        
  5112.     imagesetpixel($this->img,$x1,$y1,$this->current_color);            
  5113.     }
  5114.  
  5115.     // Set line style dashed, dotted etc
  5116.     function SetLineStyle($s) {
  5117.     if( is_numeric($s) ) {
  5118.         if( $s<1 || $s>4 ) 
  5119.         JpGraphError::Raise(" Illegal numeric argument to SetLineStyle(): ($s)");
  5120.     }
  5121.     elseif( is_string($s) ) {
  5122.         if( $s == "solid" ) $s=1;
  5123.         elseif( $s == "dotted" ) $s=2;
  5124.         elseif( $s == "dashed" ) $s=3;
  5125.         elseif( $s == "longdashed" ) $s=4;
  5126.         else JpGraphError::Raise(" Illegal string argument to SetLineStyle(): $s");
  5127.     }
  5128.     else JpGraphError::Raise(" Illegal argument to SetLineStyle $s");
  5129.     $this->line_style=$s;
  5130.     }
  5131.     
  5132.     // Same as Line but take the line_style into account
  5133.     function StyleLine($x1,$y1,$x2,$y2) {
  5134.     switch( $this->line_style ) {
  5135.         case 1:// Solid
  5136.         $this->Line($x1,$y1,$x2,$y2);
  5137.         break;
  5138.         case 2: // Dotted
  5139.         $this->DashedLine($x1,$y1,$x2,$y2,1,6);
  5140.         break;
  5141.         case 3: // Dashed
  5142.         $this->DashedLine($x1,$y1,$x2,$y2,2,4);
  5143.         break;
  5144.         case 4: // Longdashes
  5145.         $this->DashedLine($x1,$y1,$x2,$y2,8,6);
  5146.         break;
  5147.         default:
  5148.         JpGraphError::Raise(" Unknown line style: $this->line_style ");
  5149.         break;
  5150.     }
  5151.     }
  5152.  
  5153.     function Line($x1,$y1,$x2,$y2) {
  5154.  
  5155.     $x1 = round($x1);
  5156.     $x2 = round($x2);
  5157.     $y1 = round($y1);
  5158.     $y2 = round($y2);
  5159.  
  5160.     if( $this->line_weight==0 ) return;
  5161.     if( $this->use_anti_aliasing ) {
  5162.         $dx = $x2-$x1;
  5163.         $dy = $y2-$y1;
  5164.         // Vertical, Horizontal or 45 lines don't need anti-aliasing
  5165.         if( $dx!=0 && $dy!=0 && $dx!=$dy ) {
  5166.         $this->WuLine($x1,$y1,$x2,$y2);
  5167.         return;
  5168.         }
  5169.     }
  5170.     if( $this->line_weight==1 ) {
  5171.         imageline($this->img,$x1,$y1,$x2,$y2,$this->current_color);
  5172.     }
  5173.     elseif( $x1==$x2 ) {        // Special case for vertical lines
  5174.         imageline($this->img,$x1,$y1,$x2,$y2,$this->current_color);
  5175.         $w1=floor($this->line_weight/2);
  5176.         $w2=floor(($this->line_weight-1)/2);
  5177.         for($i=1; $i<=$w1; ++$i) 
  5178.         imageline($this->img,$x1+$i,$y1,$x2+$i,$y2,$this->current_color);
  5179.         for($i=1; $i<=$w2; ++$i) 
  5180.         imageline($this->img,$x1-$i,$y1,$x2-$i,$y2,$this->current_color);
  5181.     }
  5182.     elseif( $y1==$y2 ) {        // Special case for horizontal lines
  5183.         imageline($this->img,$x1,$y1,$x2,$y2,$this->current_color);
  5184.         $w1=floor($this->line_weight/2);
  5185.         $w2=floor(($this->line_weight-1)/2);
  5186.         for($i=1; $i<=$w1; ++$i) 
  5187.         imageline($this->img,$x1,$y1+$i,$x2,$y2+$i,$this->current_color);
  5188.         for($i=1; $i<=$w2; ++$i) 
  5189.         imageline($this->img,$x1,$y1-$i,$x2,$y2-$i,$this->current_color);        
  5190.     }
  5191.     else {    // General case with a line at an angle
  5192.         $a = atan2($y1-$y2,$x2-$x1);
  5193.         // Now establish some offsets from the center. This gets a little
  5194.         // bit involved since we are dealing with integer functions and we
  5195.         // want the apperance to be as smooth as possible and never be thicker
  5196.         // then the specified width.
  5197.             
  5198.         // We do the trig stuff to make sure that the endpoints of the line
  5199.         // are perpendicular to the line itself.
  5200.         $dx=(sin($a)*$this->line_weight/2);
  5201.         $dy=(cos($a)*$this->line_weight/2);
  5202.  
  5203.         $pnts = array($x2+$dx,$y2+$dy,$x2-$dx,$y2-$dy,$x1-$dx,$y1-$dy,$x1+$dx,$y1+$dy);
  5204.         imagefilledpolygon($this->img,$pnts,count($pnts)/2,$this->current_color);
  5205.     }        
  5206.     $this->lastx=$x2; $this->lasty=$y2;        
  5207.     }
  5208.  
  5209.     function Polygon($p,$closed=FALSE) {
  5210.     if( $this->line_weight==0 ) return;
  5211.     $n=count($p);
  5212.     $oldx = $p[0];
  5213.     $oldy = $p[1];
  5214.     for( $i=2; $i < $n; $i+=2 ) {
  5215.         $this->Line($oldx,$oldy,$p[$i],$p[$i+1]);
  5216.         $oldx = $p[$i];
  5217.         $oldy = $p[$i+1];
  5218.     }
  5219.     if( $closed )
  5220.         $this->Line($oldx,$oldy,$p[0],$p[1]);
  5221.     }
  5222.     
  5223.     function FilledPolygon($pts) {
  5224.     $n=count($pts);
  5225.     for($i=0; $i < $n; ++$i) 
  5226.         $pts[$i] = round($pts[$i]);
  5227.     imagefilledpolygon($this->img,$pts,count($pts)/2,$this->current_color);
  5228.     }
  5229.     
  5230.     function Rectangle($xl,$yu,$xr,$yl) {
  5231.     $this->Polygon(array($xl,$yu,$xr,$yu,$xr,$yl,$xl,$yl,$xl,$yu));
  5232.     }
  5233.     
  5234.     function FilledRectangle($xl,$yu,$xr,$yl) {
  5235.     $this->FilledPolygon(array(round($xl),round($yu),round($xr),round($yu),round($xr),round($yl),round($xl),round($yl)));
  5236.     }
  5237.  
  5238.     function ShadowRectangle($xl,$yu,$xr,$yl,$fcolor=false,$shadow_width=3,$shadow_color=array(102,102,102)) {
  5239.     // This is complicated by the fact that we must also handle the case where
  5240.         // the reactangle has no fill color
  5241.     $this->PushColor($shadow_color);
  5242.     $this->FilledRectangle($xr-$shadow_width,$yu+$shadow_width,$xr,$yl);
  5243.     $this->FilledRectangle($xl+$shadow_width,$yl-$shadow_width,$xr,$yl);
  5244.     //$this->FilledRectangle($xl+$shadow_width,$yu+$shadow_width,$xr,$yl);
  5245.     $this->PopColor();
  5246.     if( $fcolor==false )
  5247.         $this->Rectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1);
  5248.     else {        
  5249.         $this->PushColor($fcolor);
  5250.         $this->FilledRectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1);
  5251.         $this->PopColor();
  5252.         $this->Rectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1);
  5253.     }
  5254.     }
  5255.  
  5256.     function FilledRoundedRectangle($xt,$yt,$xr,$yl,$r=5) {
  5257.     if( $r==0 ) {
  5258.         $this->FilledRectangle($xt,$yt,$xr,$yl);
  5259.         return;
  5260.     }
  5261.  
  5262.     // To avoid overlapping fillings (which will look strange
  5263.     // when alphablending is enabled) we have no choice but 
  5264.     // to fill the five distinct areas one by one.
  5265.     
  5266.     // Center square
  5267.     $this->FilledRectangle($xt+$r,$yt+$r,$xr-$r,$yl-$r);
  5268.     // Top band
  5269.     $this->FilledRectangle($xt+$r,$yt,$xr-$r,$yt+$r-1);
  5270.     // Bottom band
  5271.     $this->FilledRectangle($xt+$r,$yl-$r+1,$xr-$r,$yl);
  5272.     // Left band
  5273.     $this->FilledRectangle($xt,$yt+$r+1,$xt+$r-1,$yl-$r);
  5274.     // Right band
  5275.     $this->FilledRectangle($xr-$r+1,$yt+$r,$xr,$yl-$r);
  5276.  
  5277.     /*
  5278.     $this->FilledRectangle($xt+$r,$yt,$xr-$r,$yl);
  5279.     $this->FilledRectangle($xt,$yt+$r,$xr,$yl-$r);
  5280.     */
  5281.  
  5282.     // Topleft & Topright arc
  5283.     $this->FilledArc($xt+$r,$yt+$r,$r*2,$r*2,180,270);
  5284.     $this->FilledArc($xr-$r,$yt+$r,$r*2,$r*2,270,360);
  5285.  
  5286.     // Bottomleft & Bottom right arc
  5287.     $this->FilledArc($xt+$r,$yl-$r,$r*2,$r*2,90,180);
  5288.     $this->FilledArc($xr-$r,$yl-$r,$r*2,$r*2,0,90);
  5289.  
  5290.     }
  5291.  
  5292.     function RoundedRectangle($xt,$yt,$xr,$yl,$r=5) {    
  5293.  
  5294.     if( $r==0 ) {
  5295.         $this->Rectangle($xt,$yt,$xr,$yl);
  5296.         return;
  5297.     }
  5298.  
  5299.     // Top & Bottom line
  5300.     $this->Line($xt+$r,$yt,$xr-$r,$yt);
  5301.     $this->Line($xt+$r,$yl,$xr-$r,$yl);
  5302.  
  5303.     // Left & Right line
  5304.     $this->Line($xt,$yt+$r,$xt,$yl-$r);
  5305.     $this->Line($xr,$yt+$r,$xr,$yl-$r);
  5306.  
  5307.     // Topleft & Topright arc
  5308.     $this->Arc($xt+$r,$yt+$r,$r*2,$r*2,180,270);
  5309.     $this->Arc($xr-$r,$yt+$r,$r*2,$r*2,270,360);
  5310.  
  5311.     // Bottomleft & Bottomright arc
  5312.     $this->Arc($xt+$r,$yl-$r,$r*2,$r*2,90,180);
  5313.     $this->Arc($xr-$r,$yl-$r,$r*2,$r*2,0,90);
  5314.     }
  5315.  
  5316.  
  5317.     function StyleLineTo($x,$y) {
  5318.     $this->StyleLine($this->lastx,$this->lasty,$x,$y);
  5319.     $this->lastx=$x;
  5320.     $this->lasty=$y;
  5321.     }
  5322.     
  5323.     function LineTo($x,$y) {
  5324.     $this->Line($this->lastx,$this->lasty,$x,$y);
  5325.     $this->lastx=$x;
  5326.     $this->lasty=$y;
  5327.     }
  5328.     
  5329.     function Point($x,$y) {
  5330.     imagesetpixel($this->img,round($x),round($y),$this->current_color);
  5331.     }
  5332.     
  5333.     function Fill($x,$y) {
  5334.     imagefill($this->img,round($x),round($y),$this->current_color);
  5335.     }
  5336.  
  5337.     function FillToBorder($x,$y,$aBordColor) {
  5338.     $bc = $this->rgb->allocate($aBordColor);
  5339.     if( $bc == -1 ) {
  5340.         JpGraphError::Raise('Image::FillToBorder : Can not allocate more colors');
  5341.         exit();
  5342.     }
  5343.     imagefilltoborder($this->img,round($x),round($y),$bc,$this->current_color);
  5344.     }
  5345.     
  5346.     function DashedLine($x1,$y1,$x2,$y2,$dash_length=1,$dash_space=4) {
  5347.     // Code based on, but not identical to, work by Ariel Garza and James Pine
  5348.     $line_length = ceil (sqrt(pow(($x2 - $x1),2) + pow(($y2 - $y1),2)) );
  5349.     $dx = ($x2 - $x1) / $line_length;
  5350.     $dy = ($y2 - $y1) / $line_length;
  5351.     $lastx = $x1; $lasty = $y1;
  5352.     $xmax = max($x1,$x2);
  5353.     $xmin = min($x1,$x2);
  5354.     $ymax = max($y1,$y2);
  5355.     $ymin = min($y1,$y2);
  5356.     for ($i = 0; $i < $line_length; $i += ($dash_length + $dash_space)) {
  5357.         $x = ($dash_length * $dx) + $lastx;
  5358.         $y = ($dash_length * $dy) + $lasty;
  5359.             
  5360.         // The last section might overshoot so we must take a computational hit
  5361.         // and check this.
  5362.         if( $x>$xmax ) $x=$xmax;
  5363.         if( $y>$ymax ) $y=$ymax;
  5364.             
  5365.         if( $x<$xmin ) $x=$xmin;
  5366.         if( $y<$ymin ) $y=$ymin;
  5367.  
  5368.         $this->Line($lastx,$lasty,$x,$y);
  5369.         $lastx = $x + ($dash_space * $dx);
  5370.         $lasty = $y + ($dash_space * $dy);
  5371.     } 
  5372.     } 
  5373.  
  5374.     function SetExpired($aFlg=true) {
  5375.     $this->expired = $aFlg;
  5376.     }
  5377.     
  5378.     // Generate image header
  5379.     function Headers() {
  5380.     if ($this->expired) {
  5381.         header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
  5382.         header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT");
  5383.         header("Cache-Control: no-cache, must-revalidate");
  5384.         header("Pragma: no-cache");
  5385.     }
  5386.     header("Content-type: image/$this->img_format");
  5387.     }
  5388.  
  5389.     // Adjust image quality for formats that allow this
  5390.     function SetQuality($q) {
  5391.     $this->quality = $q;
  5392.     }
  5393.     
  5394.     // Stream image to browser or to file
  5395.     function Stream($aFile="") {
  5396.     $func="image".$this->img_format;
  5397.     if( $this->img_format=="jpeg" && $this->quality != null ) {
  5398.         $res = @$func($this->img,$aFile,$this->quality);
  5399.     }
  5400.     else {
  5401.         if( $aFile != "" ) {
  5402.         $res = @$func($this->img,$aFile);
  5403.         }
  5404.         else
  5405.         $res = @$func($this->img);
  5406.     }
  5407.     if( !$res )
  5408.         JpGraphError::Raise("Can't create or stream image to file $aFile Check that PHP has enough permission to write a file to the current directory.");
  5409.     }
  5410.         
  5411.     // Clear resource tide up by image
  5412.     function Destroy() {
  5413.     imagedestroy($this->img);
  5414.     }
  5415.     
  5416.     // Specify image format. Note depending on your installation
  5417.     // of PHP not all formats may be supported.
  5418.     function SetImgFormat($aFormat) {        
  5419.     $aFormat = strtolower($aFormat);
  5420.     $tst = true;
  5421.     $supported = imagetypes();
  5422.     if( $aFormat=="auto" ) {
  5423.         if( $supported & IMG_PNG )
  5424.         $this->img_format="png";
  5425.         elseif( $supported & IMG_JPG )
  5426.         $this->img_format="jpeg";
  5427.         elseif( $supported & IMG_GIF )
  5428.         $this->img_format="gif";
  5429.         else
  5430.         JpGraphError::Raise(" Your PHP (and GD-lib) installation does not appear to support any known graphic formats.".
  5431.                     "You need to first make sure GD is compiled as a module to PHP. If you also want to use JPEG images".
  5432.                     "you must get the JPEG library. Please see the PHP docs for details.");
  5433.                 
  5434.         return true;
  5435.     }
  5436.     else {
  5437.         if( $aFormat=="jpeg" || $aFormat=="png" || $aFormat=="gif" ) {
  5438.         if( $aFormat=="jpeg" && !($supported & IMG_JPG) )
  5439.             $tst=false;
  5440.         elseif( $aFormat=="png" && !($supported & IMG_PNG) ) 
  5441.             $tst=false;
  5442.         elseif( $aFormat=="gif" && !($supported & IMG_GIF) )     
  5443.             $tst=false;
  5444.         else {
  5445.             $this->img_format=$aFormat;
  5446.             return true;
  5447.         }
  5448.         }
  5449.         else 
  5450.         $tst=false;
  5451.         if( !$tst )
  5452.         JpGraphError::Raise(" Your PHP installation does not support the chosen graphic format: $aFormat");
  5453.     }
  5454.     }    
  5455. } // CLASS
  5456.  
  5457. //===================================================
  5458. // CLASS RotImage
  5459. // Description: Exactly as Image but draws the image at
  5460. // a specified angle around a specified rotation point.
  5461. //===================================================
  5462. class RotImage extends Image {
  5463.     var $m=array();
  5464.     var $a=0;
  5465.     var $dx=0,$dy=0,$transx=0,$transy=0; 
  5466.     
  5467.     function RotImage($aWidth,$aHeight,$a=0,$aFormat=DEFAULT_GFORMAT) {
  5468.     $this->Image($aWidth,$aHeight,$aFormat);
  5469.     $this->dx=$this->left_margin+$this->plotwidth/2;
  5470.     $this->dy=$this->top_margin+$this->plotheight/2;
  5471.     $this->SetAngle($a);    
  5472.     }
  5473.     
  5474.     function SetCenter($dx,$dy) {
  5475.     $old_dx = $this->dx;
  5476.     $old_dy = $this->dy;
  5477.     $this->dx=$dx;
  5478.     $this->dy=$dy;
  5479.     $this->SetAngle($this->a);
  5480.     return array($old_dx,$old_dy);
  5481.     }
  5482.     
  5483.     function SetTranslation($dx,$dy) {
  5484.     $old = array($this->transx,$this->transy);
  5485.     $this->transx = $dx;
  5486.     $this->transy = $dy;
  5487.     return $old;
  5488.     }
  5489.  
  5490.     function UpdateRotMatrice()  {
  5491.     $a = $this->a;
  5492.     $a *= M_PI/180;
  5493.     $sa=sin($a); $ca=cos($a);        
  5494.     // Create the rotation matrix
  5495.     $this->m[0][0] = $ca;
  5496.     $this->m[0][1] = -$sa;
  5497.     $this->m[0][2] = $this->dx*(1-$ca) + $sa*$this->dy ;
  5498.     $this->m[1][0] = $sa;
  5499.     $this->m[1][1] = $ca;
  5500.     $this->m[1][2] = $this->dy*(1-$ca) - $sa*$this->dx ;
  5501.     }
  5502.  
  5503.     function SetAngle($a) {
  5504.     $tmp = $this->a;
  5505.     $this->a = $a;
  5506.     $this->UpdateRotMatrice();
  5507.     return $tmp;
  5508.     }
  5509.  
  5510.     function Circle($xc,$yc,$r) {
  5511.     // Circle get's rotated through the Arc() call
  5512.     // made in the parent class
  5513.     parent::Circle($xc,$yc,$r);
  5514.     }
  5515.  
  5516.     function FilledCircle($xc,$yc,$r) {
  5517.     // If we use GD1 then Image::FilledCircle will use a 
  5518.     // call to Arc so it will get rotated through the Arc
  5519.     // call.
  5520.     if( $GLOBALS['gd2'] ) {
  5521.         list($xc,$yc) = $this->Rotate($xc,$yc);
  5522.     }
  5523.     parent::FilledCircle($xc,$yc,$r);
  5524.     }
  5525.  
  5526.     
  5527.     function Arc($xc,$yc,$w,$h,$s,$e) {
  5528.     list($xc,$yc) = $this->Rotate($xc,$yc);
  5529.     $s += $this->a;
  5530.     $e += $this->a;
  5531.     parent::Arc($xc,$yc,$w,$h,$s,$e);
  5532.     }
  5533.  
  5534.     function FilledArc($xc,$yc,$w,$h,$s,$e) {
  5535.     list($xc,$yc) = $this->Rotate($xc,$yc);
  5536.     $s += $this->a;
  5537.     $e += $this->a;
  5538.     parent::FilledArc($xc,$yc,$w,$h,$s,$e);
  5539.     }
  5540.  
  5541.     function SetMargin($lm,$rm,$tm,$bm) {
  5542.     parent::SetMargin($lm,$rm,$tm,$bm);
  5543.     $this->dx=$this->left_margin+$this->plotwidth/2;
  5544.     $this->dy=$this->top_margin+$this->plotheight/2;
  5545.     $this->UpdateRotMatrice();
  5546.     }
  5547.     
  5548.     function Rotate($x,$y) {
  5549.     // Optimization. Ignore rotation if Angle==0
  5550.     if( $this->a == 0 ) {
  5551.         return array($x + $this->transx, $y + $this->transy );
  5552.     }
  5553.     else {
  5554.         $x1=round($this->m[0][0]*$x + $this->m[0][1]*$y,1) + $this->m[0][2] + $this->transx;
  5555.         $y1=round($this->m[1][0]*$x + $this->m[1][1]*$y,1) + $this->m[1][2] + $this->transy;
  5556.         return array($x1,$y1);
  5557.     }
  5558.     }
  5559.     
  5560.     function ArrRotate($pnts) {
  5561.     for($i=0; $i < count($pnts)-1; $i+=2)
  5562.         list($pnts[$i],$pnts[$i+1]) = $this->Rotate($pnts[$i],$pnts[$i+1]);
  5563.     return $pnts;
  5564.     }
  5565.     
  5566.     function Line($x1,$y1,$x2,$y2) {
  5567.     list($x1,$y1) = $this->Rotate($x1,$y1);
  5568.     list($x2,$y2) = $this->Rotate($x2,$y2);
  5569.     parent::Line($x1,$y1,$x2,$y2);
  5570.     }
  5571.     
  5572.     function Rectangle($x1,$y1,$x2,$y2) {
  5573.     // Rectangle uses Line() so it will be rotated through that call
  5574.     parent::Rectangle($x1,$y1,$x2,$y2);
  5575.     }
  5576.     
  5577.     function FilledRectangle($x1,$y1,$x2,$y2) {
  5578.     if( $y1==$y2 || $x1==$x2 )
  5579.         $this->Line($x1,$y1,$x2,$y2);
  5580.     else 
  5581.         $this->FilledPolygon(array($x1,$y1,$x2,$y1,$x2,$y2,$x1,$y2));
  5582.     }
  5583.     
  5584.     function Polygon($pnts,$closed=FALSE) {
  5585.     //Polygon uses Line() so it will be rotated through that call
  5586.     parent::Polygon($pnts,$closed);
  5587.     }
  5588.     
  5589.     function FilledPolygon($pnts) {
  5590.     parent::FilledPolygon($this->ArrRotate($pnts));
  5591.     }
  5592.     
  5593.     function Point($x,$y) {
  5594.     list($xp,$yp) = $this->Rotate($x,$y);
  5595.     parent::Point($xp,$yp);
  5596.     }
  5597.     
  5598.     function DashedLine($x1,$y1,$x2,$y2,$length=1,$space=4) {
  5599.     list($x1,$y1) = $this->Rotate($x1,$y1);
  5600.     list($x2,$y2) = $this->Rotate($x2,$y2);
  5601.     parent::DashedLine($x1,$y1,$x2,$y2,$length,$space);
  5602.     }
  5603.     
  5604.     function StrokeText($x,$y,$txt,$dir=0,$paragraph_align="left",$debug=false) {
  5605.     list($xp,$yp) = $this->Rotate($x,$y);
  5606.     parent::StrokeText($xp,$yp,$txt,$dir,$paragraph_align,$debug);
  5607.     }
  5608. }
  5609.  
  5610. //===================================================
  5611. // CLASS ImgStreamCache
  5612. // Description: Handle caching of graphs to files
  5613. //===================================================
  5614. class ImgStreamCache {
  5615.     var $cache_dir;
  5616.     var $img=null;
  5617.     var $timeout=0;     // Infinite timeout
  5618.     //---------------
  5619.     // CONSTRUCTOR
  5620.     function ImgStreamCache(&$aImg, $aCacheDir=CACHE_DIR) {
  5621.     $this->img = &$aImg;
  5622.     $this->cache_dir = $aCacheDir;
  5623.     }
  5624.  
  5625. //---------------
  5626. // PUBLIC METHODS    
  5627.  
  5628.     // Specify a timeout (in minutes) for the file. If the file is older then the
  5629.     // timeout value it will be overwritten with a newer version.
  5630.     // If timeout is set to 0 this is the same as infinite large timeout and if
  5631.     // timeout is set to -1 this is the same as infinite small timeout
  5632.     function SetTimeout($aTimeout) {
  5633.     $this->timeout=$aTimeout;    
  5634.     }
  5635.     
  5636.     // Output image to browser and also write it to the cache
  5637.     function PutAndStream(&$aImage,$aCacheFileName,$aInline,$aStrokeFileName) {
  5638.     // Some debugging code to brand the image with numbe of colors
  5639.     // used
  5640.     GLOBAL $gJpgBrandTiming;
  5641.     
  5642.     if( $gJpgBrandTiming ) {
  5643.         global $tim;
  5644.         $t=$tim->Pop()/1000.0;
  5645.         $c=$aImage->SetColor("black");
  5646.         $t=sprintf(BRAND_TIME_FORMAT,round($t,3));
  5647.         imagestring($this->img->img,2,5,$this->img->height-20,$t,$c);            
  5648.     }
  5649.  
  5650.     // Check if we should stroke the image to an arbitrary file
  5651.     if( $aStrokeFileName!="" ) {
  5652.         if( $aStrokeFileName == "auto" )
  5653.         $aStrokeFileName = GenImgName();
  5654.         if( file_exists($aStrokeFileName) ) {
  5655.         // Delete the old file
  5656.         if( !@unlink($aStrokeFileName) )
  5657.             JpGraphError::Raise(" Can't delete cached image $aStrokeFileName. Permission problem?");
  5658.         }
  5659.         $aImage->Stream($aStrokeFileName);
  5660.         return;
  5661.     }
  5662.  
  5663.     if( $aCacheFileName != "" && USE_CACHE) {
  5664.  
  5665.         $aCacheFileName = $this->cache_dir . $aCacheFileName;
  5666.         if( file_exists($aCacheFileName) ) {
  5667.         if( !$aInline ) {
  5668.             // If we are generating image off-line (just writing to the cache)
  5669.             // and the file exists and is still valid (no timeout)
  5670.             // then do nothing, just return.
  5671.             $diff=time()-filemtime($aCacheFileName);
  5672.             if( $diff < 0 )
  5673.             JpGraphError::Raise(" Cached imagefile ($aCacheFileName) has file date in the future!!");
  5674.             if( $this->timeout>0 && ($diff <= $this->timeout*60) ) 
  5675.             return;        
  5676.         }            
  5677.         if( !@unlink($aCacheFileName) )
  5678.             JpGraphError::Raise(" Can't delete cached image $aStrokeFileName. Permission problem?");
  5679.         $aImage->Stream($aCacheFileName);    
  5680.         }
  5681.         else {
  5682.         $this->MakeDirs(dirname($aCacheFileName));
  5683.         if( !is_writeable(dirname($aCacheFileName)) ) {
  5684.             JpGraphError::Raise('PHP has not enough permissions to write to the cache file '.$aCacheFileName.'. Please make sure that the user running PHP has write permission for this file if you wan to use the cache system with JpGraph.');
  5685.         }
  5686.         $aImage->Stream($aCacheFileName);
  5687.         }
  5688.             
  5689.         $res=true;
  5690.         // Set group to specified
  5691.         if( CACHE_FILE_GROUP != "" )
  5692.         $res = @chgrp($aCacheFileName,CACHE_FILE_GROUP);
  5693.         if( CACHE_FILE_MOD != "" )
  5694.         $res = @chmod($aCacheFileName,CACHE_FILE_MOD);
  5695.         if( !$res )
  5696.         JpGraphError::Raise(" Can't set permission for cached image $aStrokeFileName. Permission problem?");
  5697.             
  5698.         $aImage->Destroy();
  5699.         if( $aInline ) {
  5700.         if ($fh = @fopen($aCacheFileName, "rb") ) {
  5701.             $this->img->Headers();
  5702.             fpassthru($fh);
  5703.             return;
  5704.         }
  5705.         else
  5706.             JpGraphError::Raise(" Cant open file from cache [$aFile]"); 
  5707.         }
  5708.     }
  5709.     elseif( $aInline ) {
  5710.         $this->img->Headers();             
  5711.         $aImage->Stream();    
  5712.         return;
  5713.     }
  5714.     }
  5715.     
  5716.     // Check if a given image is in cache and in that case
  5717.     // pass it directly on to web browser. Return false if the
  5718.     // image file doesn't exist or exists but is to old
  5719.     function GetAndStream($aCacheFileName) {
  5720.     $aCacheFileName = $this->cache_dir.$aCacheFileName;         
  5721.     if ( USE_CACHE && file_exists($aCacheFileName) && $this->timeout>=0 ) {
  5722.         $diff=time()-filemtime($aCacheFileName);
  5723.         if( $this->timeout>0 && ($diff > $this->timeout*60) ) {
  5724.         return false;        
  5725.         }
  5726.         else {
  5727.         if ($fh = @fopen($aCacheFileName, "rb")) {
  5728.             $this->img->Headers();
  5729.             fpassthru($fh);
  5730.             return true;
  5731.         }
  5732.         else
  5733.             JpGraphError::Raise(" Can't open cached image \"$aCacheFileName\" for reading.");
  5734.         }
  5735.     } 
  5736.     return false;
  5737.     }
  5738.     
  5739.     //---------------
  5740.     // PRIVATE METHODS    
  5741.     // Create all necessary directories in a path
  5742.     function MakeDirs($aFile) {
  5743.     $dirs = array();
  5744.     while ( !(file_exists($aFile)) ) {
  5745.         $dirs[] = $aFile;
  5746.         $aFile = dirname($aFile);
  5747.     }
  5748.     for ($i = sizeof($dirs)-1; $i>=0; $i--) {
  5749.         if(! @mkdir($dirs[$i],0777) )
  5750.         JpGraphError::Raise(" Can't create directory $aFile. Make sure PHP has write permission to this directory.");
  5751.         // We also specify mode here after we have changed group. 
  5752.         // This is necessary if Apache user doesn't belong the
  5753.         // default group and hence can't specify group permission
  5754.         // in the previous mkdir() call
  5755.         if( CACHE_FILE_GROUP != "" ) {
  5756.         $res=true;
  5757.         $res =@chgrp($dirs[$i],CACHE_FILE_GROUP);
  5758.         $res &= @chmod($dirs[$i],0777);
  5759.         if( !$res )
  5760.             JpGraphError::Raise(" Can't set permissions for $aFile. Permission problems?");
  5761.         }
  5762.     }
  5763.     return true;
  5764.     }    
  5765. } // CLASS Cache
  5766.     
  5767. //===================================================
  5768. // CLASS Legend
  5769. // Description: Responsible for drawing the box containing
  5770. // all the legend text for the graph
  5771. //===================================================
  5772. class Legend {
  5773.     var $color=array(0,0,0); // Default fram color
  5774.     var $fill_color=array(235,235,235); // Default fill color
  5775.     var $shadow=true; // Shadow around legend "box"
  5776.     var $shadow_color='gray';
  5777.     var $txtcol=array();
  5778.     var $mark_abs_size=8,$xmargin=10,$ymargin=3,$shadow_width=2;
  5779.     var $xpos=0.05, $ypos=0.15, $halign="right", $valign="top";
  5780.     var $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12;
  5781.     var $font_color='black';
  5782.     var $hide=false,$layout=LEGEND_VERT;
  5783.     var $weight=1;
  5784.     var $csimareas='';
  5785. //---------------
  5786. // CONSTRUCTOR
  5787.     function Legend() {
  5788.     // Empty
  5789.     }
  5790. //---------------
  5791. // PUBLIC METHODS    
  5792.     function Hide($aHide=true) {
  5793.     $this->hide=$aHide;
  5794.     }
  5795.     
  5796.     function SetShadow($aShow='gray',$aWidth=2) {
  5797.     if( is_string($aShow) ) {
  5798.         $this->shadow_color = $aShow;
  5799.         $this->shadow=true;
  5800.     }
  5801.     else
  5802.         $this->shadow=$aShow;
  5803.     $this->shadow_width=$aWidth;
  5804.     }
  5805.     
  5806.     function SetLineWeight($aWeight) {
  5807.     $this->weight = $aWeight;
  5808.     }
  5809.     
  5810.     function SetLayout($aDirection=LEGEND_VERT) {
  5811.     $this->layout=$aDirection;
  5812.     }
  5813.     
  5814.     // Set color on frame around box
  5815.     function SetColor($aFontColor,$aColor='black') {
  5816.     $this->font_color=$aFontColor;
  5817.     $this->color=$aColor;
  5818.     }
  5819.     
  5820.     function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
  5821.     $this->font_family = $aFamily;
  5822.     $this->font_style = $aStyle;
  5823.     $this->font_size = $aSize;
  5824.     }
  5825.     
  5826.     function Pos($aX,$aY,$aHAlign="right",$aVAlign="top") {
  5827.     if( !($aX<1 && $aY<1) )
  5828.         JpGraphError::Raise(" Position for legend must be given as percentage in range 0-1");
  5829.     $this->xpos=$aX;
  5830.     $this->ypos=$aY;
  5831.     $this->halign=$aHAlign;
  5832.     $this->valign=$aVAlign;
  5833.     }
  5834.  
  5835.     function SetFillColor($aColor) {
  5836.     $this->fill_color=$aColor;
  5837.     }
  5838.     
  5839.     function Add($aTxt,$aColor,$aPlotmark="",$aLinestyle=0,$csimtarget="",$csimalt="") {
  5840.     $this->txtcol[]=array($aTxt,$aColor,$aPlotmark,$aLinestyle,$csimtarget,$csimalt);
  5841.     }
  5842.  
  5843.     function GetCSIMAreas() {
  5844.     return $this->csimareas;
  5845.     }
  5846.     
  5847.     function Stroke(&$aImg) {
  5848.     if( $this->hide ) return;
  5849.  
  5850.     $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);        
  5851.  
  5852.     $nbrplots=count($this->txtcol);
  5853.     if( $nbrplots==0 ) return;
  5854.  
  5855.     // Find out the height we need for legend box
  5856.     if( $this->layout==LEGEND_VERT ) {
  5857.         $abs_height = 0;
  5858.         for($i=0; $i< $nbrplots; ++$i)
  5859.         $abs_height += $aImg->GetTextHeight($this->txtcol[$i][0])+$this->ymargin;
  5860.         $abs_height += 2*$this->ymargin;
  5861.     }
  5862.     else {
  5863.         $abs_height = 0;
  5864.         for($i=0; $i< $nbrplots; ++$i)
  5865.         $abs_height = max($abs_height,$aImg->GetTextHeight($this->txtcol[$i][0]));
  5866.         $abs_height += 2*$this->ymargin;
  5867.     }
  5868.                         
  5869.     // Find out maximum text width
  5870.     $mtw=0;
  5871.     foreach($this->txtcol as $p) {
  5872.         if( $this->layout==LEGEND_VERT )
  5873.         $mtw=max($mtw,$aImg->GetTextWidth($p[0]));
  5874.         else
  5875.         $mtw+=$aImg->GetTextWidth($p[0])+$this->mark_abs_size+2.4*$this->xmargin;
  5876.     }
  5877.  
  5878.     // Find out maximum width we need for legend box
  5879.     if( $this->layout==LEGEND_VERT )
  5880.         $abs_width=$mtw+2*$this->mark_abs_size+2*$this->xmargin;
  5881.     else
  5882.         $abs_width=$mtw+2*$this->xmargin;
  5883.  
  5884.     // Positioning of the legend box
  5885.     if( $this->halign=="left" )
  5886.         $xp=$this->xpos*$aImg->width;
  5887.     elseif( $this->halign=="center" )
  5888.         $xp=$this->xpos*$aImg->width - $abs_width/2; 
  5889.     else  
  5890.         $xp = $aImg->width - $this->xpos*$aImg->width - $abs_width;
  5891.     $yp=$this->ypos*$aImg->height;
  5892.     if( $this->valign=="center" )
  5893.         $yp-=$abs_height/2;
  5894.     elseif( $this->valign=="bottom" )
  5895.         $yp-=$abs_height;
  5896.             
  5897.     // Stroke legend box
  5898.     $aImg->SetColor($this->color);                
  5899.     $aImg->SetLineWeight($this->weight);
  5900.     if( $this->shadow )
  5901.         $aImg->ShadowRectangle($xp,$yp,$xp+$abs_width+$this->shadow_width,
  5902.                    $yp+$abs_height+$this->shadow_width,
  5903.                    $this->fill_color,$this->shadow_width,$this->shadow_color);
  5904.     else {
  5905.         $aImg->SetColor($this->fill_color);                
  5906.         $aImg->FilledRectangle($xp,$yp,$xp+$abs_width,$yp+$abs_height);
  5907.         $aImg->SetColor($this->color);                            
  5908.         $aImg->Rectangle($xp,$yp,$xp+$abs_width,$yp+$abs_height);
  5909.     }
  5910.  
  5911.     // x1,y1 is the position for the legend mark
  5912.     $aImg->SetLineWeight(1);
  5913.     $x1=$xp+$this->mark_abs_size/2+3;
  5914.     $y1=$yp + $this->ymargin; 
  5915.     
  5916.     $f2 =  round($aImg->GetFontHeight()/2);
  5917.     // Now stroke each legend in turn
  5918.  
  5919.     foreach($this->txtcol as $p) {
  5920.  
  5921.         $x1 = round($x1); $y1=round($y1);
  5922.         $aImg->SetColor($p[1]);
  5923.         if ( $p[2] != "" && $p[2]->GetType() > -1 ) {
  5924.           if( $p[3] > 0 ) {
  5925.         $aImg->SetLineStyle($p[3]);
  5926.         $aImg->StyleLine($x1-3,$y1+$f2,$x1+$this->mark_abs_size+3,$y1+$f2);
  5927.           }
  5928.           // Stroke a mark with the standard size
  5929.           $p[2]->iFormatCallback = '';
  5930.           $p[2]->SetDefaultWidth();
  5931.           $p[2]->Stroke($aImg,$x1+$this->mark_abs_size/2,$y1+$f2);
  5932.         } 
  5933.         elseif ( $p[2] != "" && (is_string($p[3]) || $p[3]>0 ) ) {
  5934.         $aImg->SetLineStyle($p[3]);
  5935.         $aImg->StyleLine($x1,$y1+$f2,$x1+$this->mark_abs_size,$y1+$f2);
  5936.         $aImg->StyleLine($x1,$y1+$f2+1,$x1+$this->mark_abs_size,$y1+$f2+1);
  5937.         } 
  5938.         else {
  5939.         $ym =  round($y1 + $f2 - $this->mark_abs_size/2);
  5940.         $aImg->FilledRectangle($x1,$ym,$x1+$this->mark_abs_size,$ym+$this->mark_abs_size);
  5941.         $aImg->SetColor($this->color);
  5942.         $aImg->Rectangle($x1,$ym,$x1+$this->mark_abs_size,$ym+$this->mark_abs_size);
  5943.         }
  5944.         $aImg->SetColor($this->font_color);
  5945.         $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);        
  5946.         $aImg->SetTextAlign("left","top");            
  5947.         $aImg->StrokeText(round($x1+$this->mark_abs_size+$this->xmargin*1.5),$y1,$p[0]);
  5948.  
  5949.         // Add CSIM for Legend if defined
  5950.         if( $p[4] != "" ) {
  5951.         $xe = $x1 + $this->xmargin+2*$this->mark_abs_size+$aImg->GetTextWidth($p[0]);
  5952.         $ye = $y1 + max($this->mark_abs_size,$aImg->GetTextHeight($p[0]));
  5953.         $coords = "$x1,$y1,$xe,$y1,$xe,$ye,$x1,$ye";
  5954.         $this->csimareas .= "<area shape=\"poly\" coords=\"$coords\" href=\"".$p[4]."\"";
  5955.         if( !empty($p[5]) ) {
  5956.             $tmp=sprintf($p[5],$p[0]);
  5957.             $this->csimareas .= " alt=\"$tmp\" title=\"$tmp\"";
  5958.         }
  5959.         $this->csimareas .= ">\n";
  5960.         }
  5961.         if( $this->layout==LEGEND_VERT )
  5962.         $y1 += $this->ymargin+max($this->mark_abs_size,$aImg->GetTextHeight($p[0]));
  5963.         else
  5964.         $x1 += 2*$this->xmargin+2*$this->mark_abs_size+$aImg->GetTextWidth($p[0]);
  5965.     }                                                 
  5966.     }
  5967. } // Class
  5968.     
  5969.  
  5970. //===================================================
  5971. // CLASS DisplayValue
  5972. // Description: Used to print data values at data points
  5973. //===================================================
  5974. class DisplayValue {    
  5975.     var $show=false,$format="%.1f",$negformat="";
  5976.     var $iFormCallback='';
  5977.     var $angle=0;
  5978.     var $ff=FF_FONT1,$fs=FS_NORMAL,$fsize=10;
  5979.     var $color="navy",$negcolor="";
  5980.     var $margin=5,$valign="",$halign="center";
  5981.     var $iHideZero=false;
  5982.  
  5983.     function Show($aFlag=true) {
  5984.     $this->show=$aFlag;
  5985.     }
  5986.  
  5987.     function SetColor($aColor,$aNegcolor="") {
  5988.     $this->color = $aColor;
  5989.     $this->negcolor = $aNegcolor;
  5990.     }
  5991.  
  5992.     function SetFont($aFontFamily,$aFontStyle=FS_NORMAL,$aFontSize=10) {
  5993.     $this->ff=$aFontFamily;
  5994.     $this->fs=$aFontStyle;
  5995.     $this->fsize=$aFontSize;
  5996.     }
  5997.  
  5998.     function SetMargin($aMargin) {
  5999.     $this->margin = $aMargin;
  6000.     }
  6001.  
  6002.     function SetAngle($aAngle) {
  6003.     $this->angle = $aAngle;
  6004.     }
  6005.  
  6006.     function SetAlign($aHAlign,$aVAlign='') {
  6007.     $this->halign = $aHAlign;
  6008.     $this->valign = $aVAlign;
  6009.     }
  6010.  
  6011.     function SetFormat($aFormat,$aNegFormat="") {
  6012.     $this->format= $aFormat;
  6013.     $this->negformat= $aNegFormat;
  6014.     }
  6015.  
  6016.     function SetFormatCallback($aFunc) {
  6017.     $this->iFormCallback = $aFunc;
  6018.     }
  6019.  
  6020.     function HideZero($aFlag=true) {
  6021.     $this->iHideZero=$aFlag;
  6022.     }
  6023.  
  6024.     function Stroke($img,$aVal,$x,$y) {
  6025.     if( $this->show ) 
  6026.     {
  6027.         if( $this->negformat=="" ) $this->negformat=$this->format;
  6028.         if( $this->negcolor=="" ) $this->negcolor=$this->color;
  6029.  
  6030.         if( $aVal==NULL || (is_string($aVal) && ($aVal=="" || $aVal=="-" || $aVal=="x" ) ) ) 
  6031.         return;
  6032.  
  6033.         if( is_numeric($aVal) && $aVal==0 && $this->iHideZero ) 
  6034.         return;
  6035.  
  6036.         // Since the value is used in different cirumstances we need to check what
  6037.         // kind of formatting we shall use. For example, to display values in a line
  6038.         // graph we simply display the formatted value, but in the case where the user
  6039.         // has already specified a text string we don't fo anything.
  6040.         if( $this->iFormCallback != '' ) {
  6041.         $f = $this->iFormCallback;
  6042.         $sval = $f($aVal);
  6043.         }
  6044.         elseif( is_numeric($aVal) ) {
  6045.         if( $aVal >= 0 )
  6046.             $sval=sprintf($this->format,$aVal);
  6047.         else
  6048.             $sval=sprintf($this->negformat,$aVal);
  6049.         }
  6050.         else
  6051.         $sval=$aVal;
  6052.  
  6053.         $y = $y-sign($aVal)*$this->margin;
  6054.         $txt = new Text($sval,$x,$y);
  6055.         $txt->SetFont($this->ff,$this->fs,$this->fsize);
  6056.         if( $this->valign == "" ) {
  6057.         if( $aVal >= 0 )
  6058.             $valign = "bottom";
  6059.         else
  6060.             $valign = "top";
  6061.         }
  6062.         else
  6063.         $valign = $this->valign;
  6064.         $txt->Align($this->halign,$valign);
  6065.  
  6066.         $txt->SetOrientation($this->angle);
  6067.         if( $aVal > 0 )
  6068.         $txt->SetColor($this->color);
  6069.         else
  6070.         $txt->SetColor($this->negcolor);
  6071.         $txt->Stroke($img);
  6072.     }
  6073.     }
  6074. }
  6075.  
  6076.  
  6077. //===================================================
  6078. // CLASS Plot
  6079. // Description: Abstract base class for all concrete plot classes
  6080. //===================================================
  6081. class Plot {
  6082.     var $line_weight=1;
  6083.     var $coords=array();
  6084.     var $legend="";
  6085.     var $csimtargets=array();    // Array of targets for CSIM
  6086.     var $csimareas="";            // Resultant CSIM area tags    
  6087.     var $csimalts=null;            // ALT:s for corresponding target
  6088.     var $color="black";
  6089.     var $numpoints=0;
  6090.     var $weight=1;    
  6091.     var $value;
  6092.     var $center=false;
  6093.     var $legendcsimtarget='';
  6094.     var $legendcsimalt='';
  6095. //---------------
  6096. // CONSTRUCTOR
  6097.     function Plot(&$aDatay,$aDatax=false) {
  6098.     $this->numpoints = count($aDatay);
  6099.     if( $this->numpoints==0 )
  6100.         JpGraphError::Raise(" Empty data array specified for plot. Must have at least one data point.");
  6101.     $this->coords[0]=$aDatay;
  6102.     if( is_array($aDatax) )
  6103.         $this->coords[1]=$aDatax;
  6104.     $this->value = new DisplayValue();
  6105.     }
  6106.  
  6107. //---------------
  6108. // PUBLIC METHODS    
  6109.  
  6110.     // Stroke the plot
  6111.     // "virtual" function which must be implemented by
  6112.     // the subclasses
  6113.     function Stroke(&$aImg,&$aXScale,&$aYScale) {
  6114.     JpGraphError::Raise("JpGraph: Stroke() must be implemented by concrete subclass to class Plot");
  6115.     }
  6116.  
  6117.     function StrokeDataValue($img,$aVal,$x,$y) {
  6118.     $this->value->Stroke($img,$aVal,$x,$y);
  6119.     }
  6120.     
  6121.     // Set href targets for CSIM    
  6122.     function SetCSIMTargets(&$aTargets,$aAlts=null) {
  6123.     $this->csimtargets=$aTargets;
  6124.     $this->csimalts=$aAlts;        
  6125.     }
  6126.      
  6127.     // Get all created areas
  6128.     function GetCSIMareas() {
  6129.     return $this->csimareas;
  6130.     }    
  6131.     
  6132.     // "Virtual" function which gets called before any scale
  6133.     // or axis are stroked used to do any plot specific adjustment
  6134.     function PreStrokeAdjust(&$aGraph) {
  6135.     if( substr($aGraph->axtype,0,4) == "text" && (isset($this->coords[1])) )
  6136.         JpGraphError::Raise("JpGraph: You can't use a text X-scale with specified X-coords. Use a \"int\" or \"lin\" scale instead.");
  6137.     return true;    
  6138.     }
  6139.     
  6140.     // Get minimum values in plot
  6141.     function Min() {
  6142.     if( isset($this->coords[1]) )
  6143.         $x=$this->coords[1];
  6144.     else
  6145.         $x="";
  6146.     if( $x != "" && count($x) > 0 )
  6147.         $xm=min($x);
  6148.     else 
  6149.         $xm=0;
  6150.     $y=$this->coords[0];
  6151.     if( count($y) > 0 ) {
  6152.         $ym = $y[0];
  6153.         $cnt = count($y);
  6154.         $i=0;
  6155.         while( $i<$cnt && !is_numeric($ym=$y[$i]) )
  6156.         $i++;
  6157.         while( $i < $cnt) {
  6158.         if( is_numeric($y[$i]) ) 
  6159.             $ym=min($ym,$y[$i]);
  6160.         ++$i;
  6161.         }            
  6162.     }
  6163.     else 
  6164.         $ym="";
  6165.     return array($xm,$ym);
  6166.     }
  6167.     
  6168.     // Get maximum value in plot
  6169.     function Max() {
  6170.     if( isset($this->coords[1]) )
  6171.         $x=$this->coords[1];
  6172.     else
  6173.         $x="";
  6174.  
  6175.     if( $x!="" && count($x) > 0 )
  6176.         $xm=max($x);
  6177.     else 
  6178.         $xm=count($this->coords[0])-1;    // We count from 0..(n-1)
  6179.     $y=$this->coords[0];
  6180.     if( count($y) > 0 ) {
  6181.         if( !isset($y[0]) ) {
  6182.         $y[0] = 0;
  6183. // Change in 1.5.1 Don't treat this as an error any more. Just silently concert to 0
  6184. //        JpGraphError::Raise(" You have not specified a y[0] value!!");
  6185.         }
  6186.         $cnt = count($y);
  6187.         $i=0;
  6188.         while( $i<$cnt && !is_numeric($ym=$y[$i]) )
  6189.         $i++;                
  6190.         while( $i < $cnt ) {
  6191.         if( is_numeric($y[$i]) ) $ym=max($ym,$y[$i]);
  6192.         ++$i;
  6193.         }
  6194.     }
  6195.     else 
  6196.         $ym="";
  6197.     return array($xm,$ym);
  6198.     }
  6199.     
  6200.     function SetColor($aColor) {
  6201.     $this->color=$aColor;
  6202.     }
  6203.     
  6204.     function SetLegend($aLegend,$aCSIM="",$aCSIMAlt="") {
  6205.     $this->legend = $aLegend;
  6206.     $this->legendcsimtarget = $aCSIM;
  6207.     $this->legendcsimalt = $aCSIMAlt;
  6208.     }
  6209.  
  6210.     function SetWeight($aWeight) {
  6211.     $this->weight=$aWeight;
  6212.     }
  6213.         
  6214.     function SetLineWeight($aWeight=1) {
  6215.     $this->line_weight=$aWeight;
  6216.     }
  6217.     
  6218.     function SetCenter($aCenter=true) {
  6219.     $this->center = $aCenter;
  6220.     }
  6221.     
  6222.     // This method gets called by Graph class to plot anything that should go
  6223.     // into the margin after the margin color has been set.
  6224.     function StrokeMargin(&$aImg) {
  6225.     return true;
  6226.     }
  6227.  
  6228.     // Framework function the chance for each plot class to set a legend
  6229.     function Legend(&$aGraph) {
  6230.     if( $this->legend != "" )
  6231.         $aGraph->legend->Add($this->legend,$this->color,"",0,$this->legendcsimtarget,$this->legendcsimalt);    
  6232.     }
  6233.     
  6234. } // Class
  6235.  
  6236.  
  6237. //===================================================
  6238. // CLASS PlotMark
  6239. // Description: Handles the plot marks in graphs
  6240. // mostly used in line and scatter plots.
  6241. //===================================================
  6242. class PlotMark {
  6243.     var $title, $show=true;
  6244.     var $type=-1, $weight=1;
  6245.     var $color="black", $width=4, $fill_color="blue";
  6246.     var $value,$csimtarget,$csimalt,$csimareas;
  6247.     var $iFormatCallback="";
  6248. //    --------------
  6249. // CONSTRUCTOR
  6250.     function PlotMark() {
  6251.     $this->title = new Text();
  6252.     $this->title->Hide();
  6253.     $this->csimareas = '';
  6254.     }
  6255. //---------------
  6256. // PUBLIC METHODS    
  6257.     function SetType($aType) {
  6258.     $this->type = $aType;
  6259.     }
  6260.     
  6261.     function SetCallback($aFunc) {
  6262.     $this->iFormatCallback = $aFunc;
  6263.     }
  6264.     
  6265.     function GetType() {
  6266.     return $this->type;
  6267.     }
  6268.     
  6269.     function SetColor($aColor) {
  6270.     $this->color=$aColor;
  6271.     }
  6272.     
  6273.     function SetFillColor($aFillColor) {
  6274.     $this->fill_color = $aFillColor;
  6275.     }
  6276.  
  6277.     function SetWeight($aWeight) {
  6278.     $this->weight = $aWeight;
  6279.     }
  6280.  
  6281.     // Synonym for SetWidth()
  6282.     function SetSize($aWidth) {
  6283.     $this->width=$aWidth;
  6284.     }
  6285.     
  6286.     function SetWidth($aWidth) {
  6287.     $this->width=$aWidth;
  6288.     }
  6289.  
  6290.     function SetDefaultWidth() {
  6291.     $this->width=4;
  6292.     }
  6293.     
  6294.     function GetWidth() {
  6295.     return $this->width;
  6296.     }
  6297.     
  6298.     function Hide($aHide=true) {
  6299.     $this->show = !$aHide;
  6300.     }
  6301.     
  6302.     function Show($aShow=true) {
  6303.     $this->show = $aShow;
  6304.     }
  6305.  
  6306.     function SetCSIMAltVal($aVal) {
  6307.         $this->value=$aVal;
  6308.     }
  6309.     
  6310.     function SetCSIMTarget($aTarget) {
  6311.         $this->csimtarget=$aTarget;
  6312.     }
  6313.     
  6314.     function SetCSIMAlt($aAlt) {
  6315.         $this->csimalt=$aAlt;
  6316.     }
  6317.     
  6318.     function GetCSIMAreas(){
  6319.         return $this->csimareas;
  6320.     }
  6321.         
  6322.     function AddCSIMPoly($aPts) {
  6323.         $coords = round($aPts[0]).", ".round($aPts[1]);
  6324.         $n = count($aPts)/2;
  6325.         for( $i=1; $i < $n; ++$i){
  6326.             $coords .= ", ".round($aPts[2*$i]).", ".round($aPts[2*$i+1]);
  6327.         }
  6328.         $this->csimareas="";    
  6329.         if( !empty($this->csimtarget) ) {
  6330.         $this->csimareas .= "<area shape=\"poly\" coords=\"$coords\" href=\"".$this->csimtarget."\"";
  6331.         if( !empty($this->csimalt) ) {                                        
  6332.         $tmp=sprintf($this->csimalt,$this->value);
  6333.         $this->csimareas .= " alt=\"$tmp\" title=\"$tmp\"";
  6334.         }
  6335.         $this->csimareas .= ">\n";
  6336.     }
  6337.     }
  6338.     
  6339.     function AddCSIMCircle($x,$y,$r) {
  6340.         $x = round($x); $y=round($y); $r=round($r);
  6341.         $this->csimareas="";    
  6342.         if( !empty($this->csimtarget) ) {
  6343.         $this->csimareas .= "<area shape=\"circle\" coords=\"$x,$y,$r\" href=\"".$this->csimtarget."\"";
  6344.             if( !empty($this->csimalt) ) {                                        
  6345.         $tmp=sprintf($this->csimalt,$this->value);
  6346.         $this->csimareas .= " alt=\"$tmp\" title=\"$tmp\"";
  6347.         }
  6348.             $this->csimareas .= ">\n";        
  6349.         }
  6350.     }
  6351.         
  6352.     function Stroke(&$img,$x,$y) {
  6353.     if( !$this->show ) return;
  6354.     if( $this->iFormatCallback != "" ) {
  6355.         $f = $this->iFormatCallback;
  6356.         list($width,$color,$fcolor) = $f($this->value);
  6357.         if( $width=="" ) $width = $this->width;
  6358.         if( $color=="" ) $color = $this->color;
  6359.         if( $fcolor=="" ) $fcolor = $this->fill_color;
  6360.     }
  6361.     else {
  6362.         $fcolor = $this->fill_color;
  6363.         $color = $this->color;
  6364.         $width = $this->width;
  6365.     }
  6366.     $weight = $this->weight;
  6367.     $dx=round($width/2,0);
  6368.     $dy=round($width/2,0);
  6369.     $pts=0;        
  6370.  
  6371.     switch( $this->type ) {
  6372.         case MARK_SQUARE:
  6373.         $c[]=$x-$dx;$c[]=$y-$dy;
  6374.         $c[]=$x+$dx;$c[]=$y-$dy;
  6375.         $c[]=$x+$dx;$c[]=$y+$dy;
  6376.         $c[]=$x-$dx;$c[]=$y+$dy;
  6377.         $c[]=$x-$dx;$c[]=$y-$dy;
  6378.         $pts=5;
  6379.         break;
  6380.         case MARK_UTRIANGLE:
  6381.         ++$dx;++$dy;
  6382.         $c[]=$x-$dx;$c[]=$y+0.87*$dy;    // tan(60)/2*$dx
  6383.         $c[]=$x;$c[]=$y-0.87*$dy;
  6384.         $c[]=$x+$dx;$c[]=$y+0.87*$dy;
  6385.         $c[]=$x-$dx;$c[]=$y+0.87*$dy;    // tan(60)/2*$dx
  6386.         $pts=4;
  6387.         break;
  6388.         case MARK_DTRIANGLE:
  6389.         ++$dx;++$dy;            
  6390.         $c[]=$x;$c[]=$y+0.87*$dy;    // tan(60)/2*$dx
  6391.         $c[]=$x-$dx;$c[]=$y-0.87*$dy;
  6392.         $c[]=$x+$dx;$c[]=$y-0.87*$dy;
  6393.         $c[]=$x;$c[]=$y+0.87*$dy;    // tan(60)/2*$dx
  6394.         $pts=4;
  6395.         break;                
  6396.         case MARK_DIAMOND:
  6397.         $c[]=$x;$c[]=$y+$dy;
  6398.         $c[]=$x-$dx;$c[]=$y;
  6399.         $c[]=$x;$c[]=$y-$dy;
  6400.         $c[]=$x+$dx;$c[]=$y;
  6401.         $c[]=$x;$c[]=$y+$dy;
  6402.         $pts=5;
  6403.         break;    
  6404.         case MARK_LEFTTRIANGLE:
  6405.         $c[]=$x;$c[]=$y;
  6406.         $c[]=$x;$c[]=$y+2*$dy;
  6407.         $c[]=$x+$dx*2;$c[]=$y;
  6408.         $c[]=$x;$c[]=$y;
  6409.         $pts=4;
  6410.         break;
  6411.         case MARK_RIGHTTRIANGLE:
  6412.         $c[]=$x-$dx*2;$c[]=$y;
  6413.         $c[]=$x;$c[]=$y+2*$dy;
  6414.         $c[]=$x;$c[]=$y;
  6415.         $c[]=$x-$dx*2;$c[]=$y;
  6416.         $pts=4;
  6417.         break;
  6418.         case MARK_FLASH:
  6419.         $dy *= 2;
  6420.         $c[]=$x+$dx/2; $c[]=$y-$dy;
  6421.         $c[]=$x-$dx+$dx/2; $c[]=$y+$dy*0.7-$dy;
  6422.         $c[]=$x+$dx/2; $c[]=$y+$dy*1.3-$dy;
  6423.         $c[]=$x-$dx+$dx/2; $c[]=$y+2*$dy-$dy;
  6424.         $img->SetLineWeight($weight);
  6425.         $img->SetColor($color);                    
  6426.         $img->Polygon($c);
  6427.         $img->SetLineWeight(1);
  6428.         $this->AddCSIMPoly($c);
  6429.         break;
  6430.     }
  6431.  
  6432.     if( $pts>0 ) {
  6433.         $this->AddCSIMPoly($c);
  6434.         $img->SetLineWeight($weight);
  6435.         $img->SetColor($fcolor);                                
  6436.         $img->FilledPolygon($c);
  6437.         $img->SetColor($color);                    
  6438.         $img->Polygon($c);
  6439.         $img->SetLineWeight(1);
  6440.     }
  6441.     elseif( $this->type==MARK_CIRCLE ) {
  6442.         $img->SetColor($color);                    
  6443.         $img->Circle($x,$y,$width);
  6444.         $this->AddCSIMCircle($x,$y,$width);
  6445.     }
  6446.     elseif( $this->type==MARK_FILLEDCIRCLE ) {
  6447.         $img->SetColor($fcolor);        
  6448.         $img->FilledCircle($x,$y,$width);
  6449.         $img->SetColor($color);    
  6450.         $img->Circle($x,$y,$width);
  6451.         $this->AddCSIMCircle($x,$y,$width);        
  6452.     }
  6453.     elseif( $this->type==MARK_CROSS ) {
  6454.         // Oversize by a pixel to match the X
  6455.         $img->SetColor($color);
  6456.         $img->SetLineWeight($weight);
  6457.         $img->Line($x,$y+$dy+1,$x,$y-$dy-1);
  6458.         $img->Line($x-$dx-1,$y,$x+$dx+1,$y);
  6459.         $this->AddCSIMCircle($x,$y,$dx);        
  6460.     }
  6461.     elseif( $this->type==MARK_X ) {
  6462.         $img->SetColor($color);
  6463.         $img->SetLineWeight($weight);
  6464.         $img->Line($x+$dx,$y+$dy,$x-$dx,$y-$dy);
  6465.         $img->Line($x-$dx,$y+$dy,$x+$dx,$y-$dy);        
  6466.         $this->AddCSIMCircle($x,$y,$dx+$dy);                
  6467.     }            
  6468.     elseif( $this->type==MARK_STAR ) {
  6469.         $img->SetColor($color);
  6470.         $img->SetLineWeight($weight);
  6471.         $img->Line($x+$dx,$y+$dy,$x-$dx,$y-$dy);
  6472.         $img->Line($x-$dx,$y+$dy,$x+$dx,$y-$dy);
  6473.         // Oversize by a pixel to match the X                
  6474.         $img->Line($x,$y+$dy+1,$x,$y-$dy-1);
  6475.         $img->Line($x-$dx-1,$y,$x+$dx+1,$y);
  6476.         $this->AddCSIMCircle($x,$y,$dx+$dy);        
  6477.     }
  6478.         
  6479.     // Stroke title
  6480.     $this->title->Align("center","center");
  6481.     $this->title->Stroke($img,$x,$y);            
  6482.     }
  6483. } // Class
  6484.  
  6485. //==============================================================================
  6486. // The following section contains classes to implement the "band" functionality
  6487. //==============================================================================
  6488.  
  6489. // Utility class to hold coordinates for a rectangle
  6490. class Rectangle {
  6491.     var $x,$y,$w,$h;
  6492.     var $xe, $ye;
  6493.     function Rectangle($aX,$aY,$aWidth,$aHeight) {
  6494.     $this->x=$aX;
  6495.     $this->y=$aY;
  6496.     $this->w=$aWidth;
  6497.     $this->h=$aHeight;
  6498.     $this->xe=$aX+$aWidth-1;
  6499.     $this->ye=$aY+$aHeight-1;
  6500.     }
  6501. }
  6502.  
  6503. //=====================================================================
  6504. // Class RectPattern
  6505. // Base class for pattern hierarchi that is used to display patterned
  6506. // bands on the graph. Any subclass that doesn't override Stroke()
  6507. // must at least implement method DoPattern(&$aImg) which is responsible
  6508. // for drawing the pattern onto the graph.
  6509. //=====================================================================
  6510. class RectPattern {
  6511.     var $color;
  6512.     var $weight;
  6513.     var $rect=null;
  6514.     var $doframe=true;
  6515.     var $linespacing;    // Line spacing in pixels
  6516.     var $iBackgroundColor=-1;  // Default is no background fill
  6517.     
  6518.     function RectPattern($aColor,$aWeight=1) {
  6519.     $this->color = $aColor;
  6520.     $this->weight = $aWeight;        
  6521.     }
  6522.  
  6523.     function SetBackground($aBackgroundColor) {
  6524.     $this->iBackgroundColor=$aBackgroundColor;
  6525.     }
  6526.  
  6527.     function SetPos(&$aRect) {
  6528.     $this->rect = $aRect;
  6529.     }
  6530.     
  6531.     function ShowFrame($aShow=true) {
  6532.     $this->doframe=$aShow;
  6533.     }
  6534.  
  6535.     function SetDensity($aDens) {
  6536.     if( $aDens <1 || $aDens > 100 )
  6537.         JpGraphError::Raise(" Desity for pattern must be between 1 and 100. (You tried $aDens)");
  6538.     // 1% corresponds to linespacing=50
  6539.     // 100 % corresponds to linespacing 1
  6540.     $this->linespacing = floor(((100-$aDens)/100.0)*50)+1;
  6541.  
  6542.     }
  6543.  
  6544.     function Stroke(&$aImg) {
  6545.     if( $this->rect == null )
  6546.         JpGraphError::Raise(" No positions specified for pattern.");
  6547.  
  6548.     if( !(is_numeric($this->iBackgroundColor) && $this->iBackgroundColor==-1) ) {
  6549.         $aImg->SetColor($this->iBackgroundColor);
  6550.         $aImg->FilledRectangle($this->rect->x,$this->rect->y,$this->rect->xe,$this->rect->ye); 
  6551.     }
  6552.  
  6553.     $aImg->SetColor($this->color);
  6554.     $aImg->SetLineWeight($this->weight);
  6555.  
  6556.     // Virtual function implemented by subclass
  6557.     $this->DoPattern($aImg);
  6558.  
  6559.     // Frame around the pattern area
  6560.     if( $this->doframe ) 
  6561.         $aImg->Rectangle($this->rect->x,$this->rect->y,$this->rect->xe,$this->rect->ye);
  6562.     }
  6563.  
  6564. }
  6565.  
  6566.  
  6567. //=====================================================================
  6568. // Class RectPatternSolid
  6569. // Implements a solid band
  6570. //=====================================================================
  6571. class RectPatternSolid extends RectPattern {
  6572.  
  6573.     function RectPatternSolid($aColor="black",$aWeight=1) {
  6574.     parent::RectPattern($aColor,$aWeight);
  6575.     }
  6576.  
  6577.     function DoPattern(&$aImg) {
  6578.     $aImg->SetColor($this->color);
  6579.     $aImg->FilledRectangle($this->rect->x,$this->rect->y,
  6580.                    $this->rect->xe,$this->rect->ye);
  6581.     }
  6582. }
  6583.  
  6584. //=====================================================================
  6585. // Class RectPatternHor
  6586. // Implements horizontal line pattern
  6587. //=====================================================================
  6588. class RectPatternHor extends RectPattern {
  6589.         
  6590.     function RectPatternHor($aColor="black",$aWeight=1,$aLineSpacing=7) {
  6591.     parent::RectPattern($aColor,$aWeight);
  6592.     $this->linespacing = $aLineSpacing;
  6593.     }
  6594.         
  6595.     function DoPattern(&$aImg) {
  6596.     $x0 = $this->rect->x;        
  6597.     $x1 = $this->rect->xe;
  6598.     $y = $this->rect->y;
  6599.     while( $y < $this->rect->ye ) {
  6600.         $aImg->Line($x0,$y,$x1,$y);
  6601.         $y += $this->linespacing;
  6602.     }
  6603.     }
  6604. }
  6605.  
  6606. //=====================================================================
  6607. // Class RectPatternVert
  6608. // Implements vertical line pattern
  6609. //=====================================================================
  6610. class RectPatternVert extends RectPattern {
  6611.     var $linespacing=10;    // Line spacing in pixels
  6612.         
  6613.     function RectPatternVert($aColor="black",$aWeight=1,$aLineSpacing=7) {
  6614.     parent::RectPattern($aColor,$aWeight);
  6615.     $this->linespacing = $aLineSpacing;
  6616.     }
  6617.  
  6618.     //--------------------
  6619.     // Private methods
  6620.     //
  6621.     function DoPattern(&$aImg) {
  6622.     $x = $this->rect->x;        
  6623.     $y0 = $this->rect->y;
  6624.     $y1 = $this->rect->ye;
  6625.     while( $x < $this->rect->xe ) {
  6626.         $aImg->Line($x,$y0,$x,$y1);
  6627.         $x += $this->linespacing;
  6628.     }
  6629.     }
  6630. }
  6631.  
  6632.  
  6633. //=====================================================================
  6634. // Class RectPatternRDiag
  6635. // Implements right diagonal pattern
  6636. //=====================================================================
  6637. class RectPatternRDiag extends RectPattern {
  6638.     var $linespacing;    // Line spacing in pixels
  6639.         
  6640.     function RectPatternRDiag($aColor="black",$aWeight=1,$aLineSpacing=12) {
  6641.     parent::RectPattern($aColor,$aWeight);
  6642.     $this->linespacing = $aLineSpacing;
  6643.     }
  6644.  
  6645.     function DoPattern(&$aImg) {
  6646.     //  --------------------
  6647.     //  | /   /   /   /   /|
  6648.     //  |/   /   /   /   / |
  6649.     //  |   /   /   /   /  |
  6650.     //  --------------------
  6651.     $xe = $this->rect->xe;
  6652.     $ye = $this->rect->ye;
  6653.     $x0 = $this->rect->x + round($this->linespacing/2); 
  6654.     $y0 = $this->rect->y;
  6655.     $x1 = $this->rect->x; 
  6656.     $y1 = $this->rect->y + round($this->linespacing/2);
  6657.  
  6658.     while($x0<=$xe && $y1<=$ye) {
  6659.         $aImg->Line($x0,$y0,$x1,$y1);
  6660.         $x0 += $this->linespacing;
  6661.         $y1 += $this->linespacing;
  6662.     }
  6663.  
  6664.     $x1 = $this->rect->x + ($y1-$ye);
  6665.     //$x1 = $this->rect->x +$this->linespacing;
  6666.     $y0=$this->rect->y; $y1=$ye; 
  6667.     while( $x0 <= $xe ) {
  6668.         $aImg->Line($x0,$y0,$x1,$y1);
  6669.         $x0 += $this->linespacing;
  6670.         $x1 += $this->linespacing;
  6671.     }
  6672.  
  6673.     $y0=$this->rect->y + ($x0-$xe);
  6674.     $x0=$xe;
  6675.     while( $y0 <= $ye ) {
  6676.         $aImg->Line($x0,$y0,$x1,$y1);
  6677.         $y0 += $this->linespacing;
  6678.         $x1 += $this->linespacing;
  6679.     }
  6680.     }
  6681.  
  6682. }
  6683.  
  6684. //=====================================================================
  6685. // Class RectPatternLDiag
  6686. // Implements left diagonal pattern
  6687. //=====================================================================
  6688. class RectPatternLDiag extends RectPattern {
  6689.     var $linespacing;    // Line spacing in pixels
  6690.         
  6691.     function RectPatternLDiag($aColor="black",$aWeight=1,$aLineSpacing=12) {
  6692.     $this->linespacing = $aLineSpacing;
  6693.     parent::RectPattern($aColor,$aWeight);
  6694.     }
  6695.  
  6696.     function DoPattern(&$aImg) {
  6697.     //  --------------------
  6698.     //  |\   \   \   \   \ |
  6699.     //  | \   \   \   \   \|
  6700.     //  |  \   \   \   \   |
  6701.     //  |------------------|
  6702.     $xe = $this->rect->xe;
  6703.     $ye = $this->rect->ye;
  6704.     $x0 = $this->rect->x + round($this->linespacing/2); 
  6705.     $y0 = $this->rect->ye;
  6706.     $x1 = $this->rect->x; 
  6707.     $y1 = $this->rect->ye - round($this->linespacing/2);
  6708.  
  6709.     while($x0<=$xe && $y1>=$this->rect->y) {
  6710.         $aImg->Line($x0,$y0,$x1,$y1);
  6711.         $x0 += $this->linespacing;
  6712.         $y1 -= $this->linespacing;
  6713.     }
  6714.  
  6715.     $x1 = $this->rect->x + ($this->rect->y-$y1);
  6716.     $y0=$ye; $y1=$this->rect->y; 
  6717.     while( $x0 <= $xe ) {
  6718.         $aImg->Line($x0,$y0,$x1,$y1);
  6719.         $x0 += $this->linespacing;
  6720.         $x1 += $this->linespacing;
  6721.     }
  6722.  
  6723.     $y0=$this->rect->ye - ($x0-$xe);
  6724.     $x0=$xe;
  6725.     while( $y0 >= $this->rect->y ) {
  6726.         $aImg->Line($x0,$y0,$x1,$y1);
  6727.         $y0 -= $this->linespacing;
  6728.         $x1 += $this->linespacing;
  6729.     }
  6730.     }
  6731. }
  6732.  
  6733. //=====================================================================
  6734. // Class RectPattern3DPlane
  6735. // Implements "3D" plane pattern
  6736. //=====================================================================
  6737. class RectPattern3DPlane extends RectPattern {
  6738.     var $alpha=50;  // Parameter that specifies the distance
  6739.     // to "simulated" horizon in pixel from the
  6740.     // top of the band. Specifies how fast the lines
  6741.     // converge.
  6742.  
  6743.     function RectPattern3DPlane($aColor="black",$aWeight=1) {
  6744.     parent::RectPattern($aColor,$aWeight);
  6745.     $this->SetDensity(10);  // Slightly larger default
  6746.     }
  6747.  
  6748.     function SetHorizon($aHorizon) {
  6749.     $this->alpha=$aHorizon;
  6750.     }
  6751.     
  6752.     function DoPattern(&$aImg) {
  6753.     // "Fake" a nice 3D grid-effect. 
  6754.     $x0 = $this->rect->x + $this->rect->w/2;
  6755.     $y0 = $this->rect->y;
  6756.     $x1 = $x0;
  6757.     $y1 = $this->rect->ye;
  6758.     $x0_right = $x0;
  6759.     $x1_right = $x1;
  6760.  
  6761.     // BTW "apa" means monkey in Swedish but is really a shortform for
  6762.     // "alpha+a" which was the labels I used on paper when I derived the
  6763.     // geometric to get the 3D perspective right. 
  6764.     // $apa is the height of the bounding rectangle plus the distance to the
  6765.     // artifical horizon (alpha)
  6766.     $apa = $this->rect->h + $this->alpha;
  6767.  
  6768.     // Three cases and three loops
  6769.     // 1) The endpoint of the line ends on the bottom line
  6770.     // 2) The endpoint ends on the side
  6771.     // 3) Horizontal lines
  6772.  
  6773.     // Endpoint falls on bottom line
  6774.     $middle=$this->rect->x + $this->rect->w/2;
  6775.     $dist=$this->linespacing;
  6776.     $factor=$this->alpha /($apa);
  6777.     while($x1>$this->rect->x) {
  6778.         $aImg->Line($x0,$y0,$x1,$y1);
  6779.         $aImg->Line($x0_right,$y0,$x1_right,$y1);
  6780.         $x1 = $middle - $dist;
  6781.         $x0 = $middle - $dist * $factor;
  6782.         $x1_right = $middle + $dist;
  6783.         $x0_right =  $middle + $dist * $factor;
  6784.         $dist += $this->linespacing;
  6785.     }
  6786.  
  6787.     // Endpoint falls on sides
  6788.     $dist -= $this->linespacing;
  6789.     $d=$this->rect->w/2;
  6790.     $c = $apa - $d*$apa/$dist;
  6791.     while( $x0>$this->rect->x ) {
  6792.         $aImg->Line($x0,$y0,$this->rect->x,$this->rect->ye-$c);
  6793.         $aImg->Line($x0_right,$y0,$this->rect->xe,$this->rect->ye-$c);
  6794.         $dist += $this->linespacing;            
  6795.         $x0 = $middle - $dist * $factor;
  6796.         $x1 = $middle - $dist;
  6797.         $x0_right =  $middle + $dist * $factor;            
  6798.         $c = $apa - $d*$apa/$dist;
  6799.     }        
  6800.         
  6801.     // Horizontal lines
  6802.     // They need some serious consideration since they are a function
  6803.     // of perspective depth (alpha) and density (linespacing)
  6804.     $x0=$this->rect->x;
  6805.     $x1=$this->rect->xe;
  6806.     $y=$this->rect->ye;
  6807.         
  6808.     // The first line is drawn directly. Makes the loop below slightly
  6809.     // more readable.
  6810.     $aImg->Line($x0,$y,$x1,$y);
  6811.     $hls = $this->linespacing;
  6812.         
  6813.     // A correction factor for vertical "brick" line spacing to account for
  6814.     // a) the difference in number of pixels hor vs vert
  6815.     // b) visual apperance to make the first layer of "bricks" look more
  6816.     // square.
  6817.     $vls = $this->linespacing*0.6;
  6818.         
  6819.     $ds = $hls*($apa-$vls)/$apa;
  6820.     // Get the slope for the "perspective line" going from bottom right
  6821.     // corner to top left corner of the "first" brick.
  6822.         
  6823.     // Uncomment the following lines if you want to get a visual understanding
  6824.     // of what this helpline does. BTW this mimics the way you would get the
  6825.     // perspective right when drawing on paper.
  6826.     /*
  6827.       $x0 = $middle;
  6828.       $y0 = $this->rect->ye;
  6829.       $len=floor(($this->rect->ye-$this->rect->y)/$vls);
  6830.       $x1 = $middle-round($len*$ds);
  6831.       $y1 = $this->rect->ye-$len*$vls;
  6832.       $aImg->PushColor("red");
  6833.       $aImg->Line($x0,$y0,$x1,$y1);
  6834.       $aImg->PopColor();
  6835.     */
  6836.         
  6837.     $y -= $vls;        
  6838.     $k=($this->rect->ye-($this->rect->ye-$vls))/($middle-($middle-$ds));
  6839.     $dist = $hls;
  6840.     while( $y>$this->rect->y ) {
  6841.         $aImg->Line($this->rect->x,$y,$this->rect->xe,$y);
  6842.         $adj = $k*$dist/(1+$dist*$k/$apa);
  6843.         if( $adj < 2 ) $adj=2;
  6844.         $y = $this->rect->ye - round($adj);
  6845.         $dist += $hls;
  6846.     }
  6847.     }
  6848. }
  6849.  
  6850. //=====================================================================
  6851. // Class RectPatternCross
  6852. // Vert/Hor crosses
  6853. //=====================================================================
  6854. class RectPatternCross extends RectPattern {
  6855.     var $vert=null;
  6856.     var $hor=null;
  6857.     function RectPatternCross($aColor="black",$aWeight=1) {
  6858.     parent::RectPattern($aColor,$aWeight);
  6859.     $this->vert = new RectPatternVert($aColor,$aWeight);
  6860.     $this->hor  = new RectPatternHor($aColor,$aWeight);
  6861.     }
  6862.  
  6863.     function SetOrder($aDepth) {
  6864.     $this->vert->SetOrder($aDepth);
  6865.     $this->hor->SetOrder($aDepth);
  6866.     }
  6867.  
  6868.     function SetPos(&$aRect) {
  6869.     parent::SetPos($aRect);
  6870.     $this->vert->SetPos($aRect);
  6871.     $this->hor->SetPos($aRect);
  6872.     }
  6873.  
  6874.     function SetDensity($aDens) {
  6875.     $this->vert->SetDensity($aDens);
  6876.     $this->hor->SetDensity($aDens);
  6877.     }
  6878.  
  6879.     function DoPattern(&$aImg) {
  6880.     $this->vert->DoPattern($aImg);
  6881.     $this->hor->DoPattern($aImg);
  6882.     }
  6883. }
  6884.  
  6885. //=====================================================================
  6886. // Class RectPatternDiagCross
  6887. // Vert/Hor crosses
  6888. //=====================================================================
  6889.  
  6890. class RectPatternDiagCross extends RectPattern {
  6891.     var $left=null;
  6892.     var $right=null;
  6893.     function RectPatternDiagCross($aColor="black",$aWeight=1) {
  6894.     parent::RectPattern($aColor,$aWeight);
  6895.     $this->right = new RectPatternRDiag($aColor,$aWeight);
  6896.     $this->left  = new RectPatternLDiag($aColor,$aWeight);
  6897.     }
  6898.  
  6899.     function SetOrder($aDepth) {
  6900.     $this->left->SetOrder($aDepth);
  6901.     $this->right->SetOrder($aDepth);
  6902.     }
  6903.  
  6904.     function SetPos(&$aRect) {
  6905.     parent::SetPos($aRect);
  6906.     $this->left->SetPos($aRect);
  6907.     $this->right->SetPos($aRect);
  6908.     }
  6909.  
  6910.     function SetDensity($aDens) {
  6911.     $this->left->SetDensity($aDens);
  6912.     $this->right->SetDensity($aDens);
  6913.     }
  6914.  
  6915.     function DoPattern(&$aImg) {
  6916.     $this->left->DoPattern($aImg);
  6917.     $this->right->DoPattern($aImg);
  6918.     }
  6919.  
  6920. }
  6921.  
  6922. //=====================================================================
  6923. // Class RectPatternFactory
  6924. // Factory class for rectangular pattern 
  6925. //=====================================================================
  6926. class RectPatternFactory {
  6927.     function RectPatternFactory() {
  6928.     // Empty
  6929.     }
  6930.     function Create($aPattern,$aColor,$aWeight=1) {
  6931.     switch($aPattern) {
  6932.         case BAND_RDIAG:
  6933.         $obj =  new RectPatternRDiag($aColor,$aWeight);
  6934.         break;
  6935.         case BAND_LDIAG:
  6936.         $obj =  new RectPatternLDiag($aColor,$aWeight);
  6937.         break;
  6938.         case BAND_SOLID:
  6939.         $obj =  new RectPatternSolid($aColor,$aWeight);
  6940.         break;
  6941.         case BAND_VLINE:
  6942.         $obj =  new RectPatternVert($aColor,$aWeight);
  6943.         break;
  6944.         case BAND_HLINE:
  6945.         $obj =  new RectPatternHor($aColor,$aWeight);
  6946.         break;
  6947.         case BAND_3DPLANE:
  6948.         $obj =  new RectPattern3DPlane($aColor,$aWeight);
  6949.         break;
  6950.         case BAND_HVCROSS:
  6951.         $obj =  new RectPatternCross($aColor,$aWeight);
  6952.         break;
  6953.         case BAND_DIAGCROSS:
  6954.         $obj =  new RectPatternDiagCross($aColor,$aWeight);
  6955.         break;
  6956.         default:
  6957.         JpGraphError::Raise(" Unknown pattern specification ($aPattern)");
  6958.     }
  6959.     return $obj;
  6960.     }
  6961. }
  6962.  
  6963.  
  6964. //=====================================================================
  6965. // Class PlotBand
  6966. // Factory class which is used by the client.
  6967. // It is reposnsible for factoring the corresponding pattern
  6968. // concrete class.
  6969. //=====================================================================
  6970. class PlotBand {
  6971.     var $prect=null;
  6972.     var $depth;
  6973.     var $dir, $min, $max;
  6974.  
  6975.     function PlotBand($aDir,$aPattern,$aMin,$aMax,$aColor="black",$aWeight=1,$aDepth=DEPTH_BACK) {
  6976.     $f =  new RectPatternFactory();
  6977.     $this->prect = $f->Create($aPattern,$aColor,$aWeight);
  6978.     $this->dir = $aDir;
  6979.     $this->min = $aMin;
  6980.     $this->max = $aMax;
  6981.     $this->depth=$aDepth;
  6982.     }
  6983.     
  6984.     // Set position. aRect contains absolute image coordinates
  6985.     function SetPos(&$aRect) {
  6986.     assert( $this->prect != null ) ;
  6987.     $this->prect->SetPos($aRect);
  6988.     }
  6989.     
  6990.     function ShowFrame($aFlag=true) {
  6991.     $this->prect->ShowFrame($aFlag);
  6992.     }
  6993.  
  6994.     // Set z-order. In front of pplot or in the back
  6995.     function SetOrder($aDepth) {
  6996.     $this->depth=$aDepth;
  6997.     }
  6998.     
  6999.     function SetDensity($aDens) {
  7000.     $this->prect->SetDensity($aDens);
  7001.     }
  7002.     
  7003.     function GetDir() {
  7004.     return $this->dir;
  7005.     }
  7006.     
  7007.     function GetMin() {
  7008.     return $this->min;
  7009.     }
  7010.     
  7011.     function GetMax() {
  7012.     return $this->max;
  7013.     }
  7014.     
  7015.     // Display band
  7016.     function Stroke(&$aImg,&$aXScale,&$aYScale) {
  7017.     assert( $this->prect != null ) ;
  7018.     if( $this->dir == HORIZONTAL ) {
  7019.         if( !is_numeric($this->min) && $this->min == "min" ) $this->min = $aYScale->GetMinVal();
  7020.         if( !is_numeric($this->max) && $this->max == "max" ) $this->max = $aYScale->GetMaxVal();
  7021.  
  7022.         // Trucate to limit of axis
  7023.         $this->min = max($this->min, $aYScale->GetMinVal());
  7024.         $this->max = min($this->max, $aYScale->GetMaxVal());
  7025.  
  7026.         $x=$aXScale->scale_abs[0];
  7027.         $y=$aYScale->Translate($this->max);
  7028.         $width=$aXScale->scale_abs[1]-$aXScale->scale_abs[0]+1;
  7029.         $height=abs($y-$aYScale->Translate($this->min))+1;
  7030.         $this->prect->SetPos(new Rectangle($x,$y,$width,$height));
  7031.     }
  7032.     else {    // VERTICAL
  7033.         if( !is_numeric($this->min) && $this->min == "min" ) $this->min = $aXScale->GetMinVal();
  7034.         if( !is_numeric($this->max) && $this->max == "max" ) $this->max = $aXScale->GetMaxVal();
  7035.  
  7036.         // Trucate to limit of axis
  7037.         $this->min = max($this->min, $aXScale->GetMinVal());
  7038.         $this->max = min($this->max, $aXScale->GetMaxVal());
  7039.  
  7040.         $y=$aYScale->scale_abs[1];
  7041.         $x=$aXScale->Translate($this->min);
  7042.         $height=abs($aYScale->scale_abs[1]-$aYScale->scale_abs[0]);
  7043.         $width=abs($x-$aXScale->Translate($this->max));
  7044.         $this->prect->SetPos(new Rectangle($x,$y,$width,$height));
  7045.     }
  7046.     $this->prect->Stroke($aImg);
  7047.     }
  7048. }
  7049.  
  7050. //===================================================
  7051. // CLASS PlotLine
  7052. // Description: 
  7053. // Data container class to hold properties for a static
  7054. // line that is drawn directly in the plot area.
  7055. // Usefull to add static borders inside a plot to show
  7056. // for example set-values
  7057. //===================================================
  7058. class PlotLine {
  7059.     var $weight=1;
  7060.     var $color="black";
  7061.     var $direction=-1; 
  7062.     var $scaleposition;
  7063.  
  7064. //---------------
  7065. // CONSTRUCTOR
  7066.     function PlotLine($aDir=HORIZONTAL,$aPos=0,$aColor="black",$aWeight=1) {
  7067.     $this->direction = $aDir;
  7068.     $this->color=$aColor;
  7069.     $this->weight=$aWeight;
  7070.     $this->scaleposition=$aPos;
  7071.     }
  7072.     
  7073. //---------------
  7074. // PUBLIC METHODS    
  7075.     function SetPosition($aScalePosition) {
  7076.     $this->scaleposition=$aScalePosition;
  7077.     }
  7078.     
  7079.     function SetDirection($aDir) {
  7080.     $this->direction = $aDir;
  7081.     }
  7082.     
  7083.     function SetColor($aColor) {
  7084.     $this->color=$aColor;
  7085.     }
  7086.     
  7087.     function SetWeight($aWeight) {
  7088.     $this->weight=$aWeight;
  7089.     }
  7090.     
  7091.     function Stroke(&$aImg,&$aXScale,&$aYScale) {
  7092.     $aImg->SetColor($this->color);
  7093.     $aImg->SetLineWeight($this->weight);        
  7094.     if( $this->direction == VERTICAL ) {
  7095.         $ymin_abs=$aYScale->Translate($aYScale->GetMinVal());
  7096.         $ymax_abs=$aYScale->Translate($aYScale->GetMaxVal());
  7097.         $xpos_abs=$aXScale->Translate($this->scaleposition);
  7098.         $aImg->Line($xpos_abs, $ymin_abs, $xpos_abs, $ymax_abs);
  7099.     }
  7100.     elseif( $this->direction == HORIZONTAL ) {
  7101.         $xmin_abs=$aXScale->Translate($aXScale->GetMinVal());
  7102.         $xmax_abs=$aXScale->Translate($aXScale->GetMaxVal());
  7103.         $ypos_abs=$aYScale->Translate($this->scaleposition);
  7104.         $aImg->Line($xmin_abs, $ypos_abs, $xmax_abs, $ypos_abs);
  7105.     }
  7106.     else
  7107.         JpGraphError::Raise(" Illegal direction for static line");
  7108.     }
  7109. }
  7110.  
  7111. // <EOF>
  7112. ?>
  7113.