Xaraya PHP Cross Reference Web Portal Systems

Source: /modules/opentracker/jpgraph/jpgraph.php - 8034 lines - 273993 bytes - Summary - Text - Print

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