Advertisement
SkullCrack

/Graf/ jpgraph.php

Jun 27th, 2011
67
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 173.21 KB | None | 0 0
  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.61 2002/04/20 18:33:18 aditus Exp $
  8. //
  9. // License: This code is released under QPL 1.0
  10. // Copyright (C) 2001,2002 Johan Persson
  11. //========================================================================
  12.  
  13. //------------------------------------------------------------------------
  14. // Directories. Must be updated to reflect your installation
  15. //------------------------------------------------------------------------
  16.  
  17. // The full absolute name of directory to be used as a cache. This directory MUST
  18. // be readable and writable for PHP. Must end with '/'
  19. DEFINE("CACHE_DIR","./cache/");
  20.  
  21. // The URL relative name where the cache can be found, i.e
  22. // under what HTTP directory can the cache be found. Normally
  23. // you would probably assign an alias in apache configuration
  24. // for the cache directory. Must end with '/'
  25. DEFINE("APACHE_CACHE_DIR","./cache/");
  26.  
  27. // Directory for TTF fonts. Must end with '/'
  28. DEFINE("TTF_DIR","./fonty/");
  29.  
  30. //------------------------------------------------------------------------
  31. // Various JpGraph Settings. PLEASE adjust accordingly to you
  32. // system setup. Note that cache functionlity is turned off by
  33. // default (Enable by setting USE_CACHE to true)
  34. //------------------------------------------------------------------------
  35.  
  36. // Specify if we should use GD 2.x or GD 1.x
  37. // If you have GD 2.x installed it is recommended that you use it
  38. // since it will give a slightly, slightly better visual apperance
  39. // for arcs. If you don't have GD2 installed this must be set to false!
  40. DEFINE("USE_LIBRARY_GD2",true);
  41.  
  42. // Should the image be a truecolor image?
  43. // Note 1: Can only be used with GD 2.0.2 and above.
  44. // Note 2: GD 2.0.1 + PHP 4.0.6 on Win32 crashes when trying to use
  45. // trucolor. Truecolor support is to be considered alpha since GD 2.x
  46. // is still not considered stable (especially on Win32).
  47. // Note 1: MUST be enabled to get background images working with GD2
  48. // Note 2: If enabled then truetype fonts will look very ugly
  49. // => You can't have both background images and truetype fonts in the same
  50. // image until these bugs has been fixed in GD 2.01  
  51. DEFINE('USE_TRUECOLOR',true);
  52.  
  53. // Should the cache be used at all? By setting this to false no
  54. // files will be generated in the cache directory.  
  55. // The difference from READ_CACHE being that setting READ_CACHE to
  56. // false will still create the image in the cache directory
  57. // just not use it. By setting USE_CACHE=false no files will even
  58. // be generated in the cache directory.
  59. DEFINE("USE_CACHE",false);
  60.  
  61. // Should we try to find an image in the cache before generating it?
  62. // Set this define to false to bypass the reading of the cache and always
  63. // regenerate the image. Note that even if reading the cache is
  64. // disabled the cached will still be updated with the newly generated
  65. // image. Set also "USE_CACHE" below.
  66. DEFINE("READ_CACHE",true);
  67.  
  68. // Deafult graphic format set to "auto" which will automtically
  69. // choose the best available format in the order png,gif,jpg
  70. // (The supported format depends on what your PHP installation supports)
  71. DEFINE("DEFAULT_GFORMAT","auto");
  72.  
  73. // Determine if the error handler should be image based or purely
  74. // text based. Image based makes it easier since the script will
  75. // always return an image even in case of errors.
  76. DEFINE("USE_IMAGE_ERROR_HANDLER",true);
  77.  
  78. // If the color palette is full should JpGraph try to allocate
  79. // the closest match? If you plan on using background image or
  80. // gradient fills it might be a good idea to enable this.
  81. // If not you will otherwise get an error saying that the color palette is
  82. // exhausted. The drawback of using approximations is that the colors
  83. // might not be exactly what you specified.
  84. // Note1: This does only apply to paletted images, not truecolor
  85. // images since they don't have the limitations of maximum number
  86. // of colors.
  87. DEFINE("USE_APPROX_COLORS",true);
  88.  
  89. // Special unicode language support
  90. DEFINE("LANGUAGE_CYRILLIC",false);
  91.  
  92. //------------------------------------------------------------------------
  93. // The following constants should rarely have to be changed !
  94. //------------------------------------------------------------------------
  95.  
  96. // Should the time taken to generate each picture be branded to the lower
  97. // left in corner in each generated image? Useful for performace measurements
  98. // generating graphs
  99. DEFINE("BRAND_TIMING",false);
  100.  
  101. // What format should be used for the timing string?
  102. DEFINE("BRAND_TIME_FORMAT","Generated in: %01.3fs");
  103.  
  104. // What group should the cached file belong to
  105. // (Set to "" will give the default group for the "PHP-user")
  106. // Please note that the Apache user must be a member of the
  107. // specified group since otherwise it is impossible for Apache
  108. // to set the specified group.
  109. DEFINE("CACHE_FILE_GROUP","");
  110.  
  111. // What permissions should the cached file have
  112. // (Set to "" will give the default persmissions for the "PHP-user")
  113. DEFINE("CACHE_FILE_MOD","");
  114.  
  115. // Decide if we should use the bresenham circle algorithm or the
  116. // built in Arc(). Bresenham gives better visual apperance of circles
  117. // but is more CPU intensive and slower then the built in Arc() function
  118. // in GD. Turned off by default for speed
  119. DEFINE("USE_BRESENHAM",false);
  120.  
  121. // Should usage of deprecated functions and parameters give a fatal error?
  122. // (Useful to check if code is future proof.)
  123. DEFINE("ERR_DEPRECATED",true);
  124.  
  125. // Enable some extra debug information to be shown.
  126. // (Should only be changed if your first name is Johan and you
  127. // happen to know what you are doing!!)
  128. DEFINE("JPG_DEBUG",false);
  129.  
  130. //------------------------------------------------------------------
  131. // Constants which are used as parameters for the method calls
  132. //------------------------------------------------------------------
  133.  
  134. // TTF Font families
  135. DEFINE("FF_COURIER",10);
  136. DEFINE("FF_VERDANA",11);
  137. DEFINE("FF_TIMES",12);
  138. DEFINE("FF_HANDWRT",13);
  139. DEFINE("FF_COMIC",14);
  140. DEFINE("FF_ARIAL",15);
  141. DEFINE("FF_BOOK",16);
  142.  
  143. // TTF Font styles
  144. DEFINE("FS_NORMAL",1);
  145. DEFINE("FS_BOLD",2);
  146. DEFINE("FS_ITALIC",3);
  147. DEFINE("FS_BOLDIT",4);
  148.  
  149. //Definitions for internal font, new style
  150. DEFINE("FF_FONT0",1);
  151. DEFINE("FF_FONT1",2);
  152. DEFINE("FF_FONT2",4);
  153.  
  154. //Definitions for internal font, old style
  155. // (Only defined here to be able to generate an error mesage
  156. // when used)
  157. DEFINE("FONT0",99);         // Deprecated from 1.2
  158. DEFINE("FONT1",98);         // Deprecated from 1.2
  159. DEFINE("FONT1_BOLD",97);    // Deprecated from 1.2
  160. DEFINE("FONT2",96);         // Deprecated from 1.2
  161. DEFINE("FONT2_BOLD",95);    // Deprecated from 1.2
  162.  
  163. // Tick density
  164. DEFINE("TICKD_DENSE",1);
  165. DEFINE("TICKD_NORMAL",2);
  166. DEFINE("TICKD_SPARSE",3);
  167. DEFINE("TICKD_VERYSPARSE",4);
  168.  
  169. // Side for ticks and labels.
  170. DEFINE("SIDE_LEFT",-1);
  171. DEFINE("SIDE_RIGHT",1);
  172. DEFINE("SIDE_DOWN",-1);
  173. DEFINE("SIDE_UP",1);
  174.  
  175. // Legend type stacked vertical or horizontal
  176. DEFINE("LEGEND_VERT",0);
  177. DEFINE("LEGEND_HOR",1);
  178.  
  179. // Mark types for plot marks
  180. DEFINE("MARK_SQUARE",1);
  181. DEFINE("MARK_UTRIANGLE",2);
  182. DEFINE("MARK_DTRIANGLE",3);
  183. DEFINE("MARK_DIAMOND",4);
  184. DEFINE("MARK_CIRCLE",5);
  185. DEFINE("MARK_FILLEDCIRCLE",6);
  186. DEFINE("MARK_CROSS",7);
  187. DEFINE("MARK_STAR",8);
  188. DEFINE("MARK_X",9);
  189.  
  190. // Styles for gradient color fill
  191. DEFINE("GRAD_VER",1);
  192. DEFINE("GRAD_HOR",2);
  193. DEFINE("GRAD_MIDHOR",3);
  194. DEFINE("GRAD_MIDVER",4);
  195. DEFINE("GRAD_CENTER",5);
  196. DEFINE("GRAD_WIDE_MIDVER",6);
  197. DEFINE("GRAD_WIDE_MIDHOR",7);
  198.  
  199. // Inline defines
  200. DEFINE("INLINE_YES",1);
  201. DEFINE("INLINE_NO",0);
  202.  
  203. // Format for background images
  204. DEFINE("BGIMG_FILLPLOT",1);
  205. DEFINE("BGIMG_FILLFRAME",2);
  206. DEFINE("BGIMG_COPY",3);
  207. DEFINE("BGIMG_CENTER",4);
  208.  
  209. // Depth of objects
  210. DEFINE("DEPTH_BACK",0);
  211. DEFINE("DEPTH_FRONT",1);
  212.  
  213. // Direction
  214. DEFINE("VERTICAL",1);
  215. DEFINE("HORIZONTAL",0);
  216.  
  217. // Constants for types of static bands in plot area
  218. DEFINE("BAND_RDIAG",1); // Right diagonal lines
  219. DEFINE("BAND_LDIAG",2); // Left diagonal lines
  220. DEFINE("BAND_SOLID",3); // Solid one color
  221. DEFINE("BAND_LVERT",4); // Vertical lines
  222. DEFINE("BAND_LHOR",5);  // Horizontal lines
  223. DEFINE("BAND_VLINE",4); // Vertical lines
  224. DEFINE("BAND_HLINE",5);  // Horizontal lines
  225. DEFINE("BAND_3DPLANE",6);  // "3D" Plane
  226. DEFINE("BAND_HVCROSS",7);  // Vertical/Hor crosses
  227. DEFINE("BAND_DIAGCROSS",8); // Diagonal crosses
  228.  
  229. //
  230. // First of all set up a default error handler
  231. //
  232.  
  233. //=============================================================
  234. // The default trivial text error handler.
  235. //=============================================================
  236. class JpGraphErrObject {
  237.     function JpGraphErrObject() {
  238.     // Empty. Reserved for future use
  239.     }
  240.  
  241.     // If aHalt is true then execution can't continue. Typical used for
  242.     // fatal errors
  243.     function Raise($aMsg,$aHalt=true) {
  244.     $aMsg = "<b>JpGraph Error:</b> ".$aMsg;
  245.     if( $aHalt )
  246.         die($aMsg);
  247.     else
  248.         echo $aMsg."<p>";
  249.     }
  250. }
  251.  
  252. //==============================================================
  253. // An image based error handler
  254. //==============================================================
  255. class JpGraphErrObjectImg {
  256.  
  257.     // Split a line by inserting a newline after aWordCnt words
  258.     function InsertLineBreaks($aStr,$aWordCnt=11) {
  259.     $tok = strtok($aStr," ");
  260.     $cnt = 0;
  261.     $s = "";
  262.     while( $tok ) {
  263.         $s .= $tok;
  264.         if( $cnt==$aWordCnt-1 ) {
  265.         $s .= "\n";
  266.         $cnt = 0;
  267.         }
  268.         else
  269.         $s .= " ";
  270.         $cnt++;
  271.         $tok = strtok(" ");
  272.     }
  273.     return $s;
  274.     }
  275.  
  276.     function Raise($aMsg,$aHalt=true) {
  277.     if( headers_sent() ) {
  278.         // Special case for headers already sent error. Dont
  279.         // return an image since it can't be displayed
  280.         die("<b>JpGraph Error:</b> ".$aMsg);       
  281.     }
  282.  
  283.     $w=450; $h=110;
  284.     $img = new Image($w,$h);
  285.     $img->SetColor("darkred");
  286.     $img->Rectangle(0,0,$w-1,$h-1);
  287.     $img->SetFont(FF_FONT1,FS_BOLD);
  288.     $img->StrokeText(10,20,"JpGraph Error:");
  289.     $img->SetColor("black");
  290.     $img->SetFont(FF_FONT1,FS_NORMAL);
  291.     $txt = new Text($this->InsertLineBreaks($aMsg),10,20);
  292.     $txt->Align("left","top");
  293.     $txt->Stroke($img);
  294.     $img->Headers();
  295.     $img->Stream();
  296.     die();
  297.     }
  298. }
  299.  
  300. //
  301. // A wrapper class that is used to access the specified error object
  302. // (to hide the global error parameter and avoid having a GLOBAL directive
  303. // in all methods.
  304. //
  305. class JpGraphError {
  306.     function Install($aErrObject) {
  307.     GLOBAL $__jpg_err;
  308.     $__jpg_err = $aErrObject;
  309.     }
  310.     function Raise($aMsg,$aHalt=true){
  311.     GLOBAL $__jpg_err;
  312.     $tmp = new $__jpg_err;
  313.     $tmp->Raise($aMsg,$aHalt);
  314.     }
  315. }
  316.  
  317. //
  318. // ... and install the default error handler
  319. //
  320. if( USE_IMAGE_ERROR_HANDLER ) {
  321.     JpGraphError::Install("JpGraphErrObjectImg");
  322. }
  323. else {
  324.     JpGraphError::Install("JpGraphErrObject");
  325. }
  326.  
  327.  
  328. //
  329. //Check if there were any warnings, perhaps some wrong includes by the
  330. //user
  331. //
  332. if( isset($GLOBALS['php_errormsg']) ) {
  333.     JpGraphError::Raise("<b>General PHP error:</b><br>".$GLOBALS['php_errormsg']);
  334. }
  335.  
  336. //
  337. // Check what version of the GD library is being used
  338. //
  339. if( USE_LIBRARY_GD2 ) {
  340.     $gd2 = true;
  341.     $copyfunc = "imagecopyresampled";
  342. } elseif(function_exists('imagecopyresized')) {
  343.     $copyfunc = "imagecopyresized";
  344.     $gd2 = false;
  345. }
  346. else {
  347.     JpGraphError::Raise(" Your PHP installation does not seem to
  348.     have the required GD library.
  349.     Please see the PHP documentation on how to install and enable the GD library.");
  350. }
  351.  
  352. // Usefull mathematical function
  353. function sign($a) {if( $a>=0) return 1; else return -1;}
  354.  
  355. // Utility function to generate an image name based on the filename we
  356. // are running from AND assuming we use auto detection of graphic format
  357. // (top level), i.e it is safe to call this function
  358. // from a script that uses JpGraph
  359. function GenImgName() {
  360.     global $HTTP_SERVER_VARS;
  361.     $supported = imagetypes();
  362.     if( $supported & IMG_PNG )
  363.     $img_format="png";
  364.     elseif( $supported & IMG_GIF )
  365.     $img_format="gif";
  366.     elseif( $supported & IMG_JPG )
  367.     $img_format="jpeg";
  368.    
  369.     $fname=basename($HTTP_SERVER_VARS['PHP_SELF']);
  370.     // Replace the ".php" extension with the image format extension
  371.     return substr($fname,0,strlen($fname)-4).".".$img_format;
  372. }
  373.  
  374.  
  375. class LanguageConv {
  376.  
  377. //  Translate iso encoding to unicode
  378.     function iso2uni ($isoline){
  379.     for ($i=0; $i < strlen($isoline); $i++){
  380.         $thischar=substr($isoline,$i,1);
  381.         $charcode=ord($thischar);
  382.         $uniline.=($charcode>175) ? "&#" . (1040+($charcode-176)). ";" : $thischar;
  383.     }
  384.     return $uniline;
  385.     }
  386.  
  387.     function ToCyrillic($aTxt) {
  388.     $koistring = $aTxt;
  389.     $isostring = convert_cyr_string($koistring, "k", "i");
  390.     $unistring = LanguageConv::iso2uni($isostring);
  391.     $this->t = $unistring;
  392.     return $aTxt;
  393.     }
  394. }
  395.  
  396. //===================================================
  397. // CLASS JpgTimer
  398. // Description: General timing utility class to handle
  399. // timne measurement of generating graphs. Multiple
  400. // timers can be started by pushing new on a stack.
  401. //===================================================
  402. class JpgTimer {
  403.     var $start;
  404.     var $idx;  
  405. //---------------
  406. // CONSTRUCTOR
  407.     function JpgTimer() {
  408.     $this->idx=0;
  409.     }
  410.  
  411. //---------------
  412. // PUBLIC METHODS  
  413.  
  414.     // Push a new timer start on stack
  415.     function Push() {
  416.     list($ms,$s)=explode(" ",microtime()); 
  417.     $this->start[$this->idx++]=floor($ms*1000) + 1000*$s;  
  418.     }
  419.  
  420.     // Pop the latest timer start and return the diff with the
  421.     // current time
  422.     function Pop() {
  423.     assert($this->idx>0);
  424.     list($ms,$s)=explode(" ",microtime()); 
  425.     $etime=floor($ms*1000) + (1000*$s);
  426.     $this->idx--;
  427.     return $etime-$this->start[$this->idx];
  428.     }
  429. } // Class
  430.  
  431.  
  432. //===================================================
  433. // CLASS Graph
  434. // Description: Main class to handle graphs
  435. //===================================================
  436. class Graph {
  437.     var $cache=null;        // Cache object (singleton)
  438.     var $img=null;          // Img object (singleton)
  439.     var $plots=array(); // Array of all plot object in the graph (for Y 1 axis)
  440.     var $y2plots=array();// Array of all plot object in the graph (for Y 2 axis)
  441.     var $xscale=null;       // X Scale object (could be instance of LinearScale or LogScale
  442.     var $yscale=null,$y2scale=null;
  443.     var $cache_name;        // File name to be used for the current graph in the cache directory
  444.     var $xgrid=null;        // X Grid object (linear or logarithmic)
  445.     var $ygrid=null,$y2grid=null; //dito for Y
  446.     var $doframe=true,$frame_color=array(0,0,0), $frame_weight=1;   // Frame around graph
  447.     var $boxed=false, $box_color=array(0,0,0), $box_weight=1;       // Box around plot area
  448.     var $doshadow=false,$shadow_width=4,$shadow_color=array(102,102,102);   // Shadow for graph
  449.     var $xaxis=null;        // X-axis (instane of Axis class)
  450.     var $yaxis=null, $y2axis=null;  // Y axis (instance of Axis class)
  451.     var $margin_color=array(198,198,198);   // Margin coor of graph
  452.     var $plotarea_color=array(255,255,255); // Plot area color
  453.     var $title,$subtitle;   // Title and subtitle text object
  454.     var $axtype="linlin";   // Type of axis
  455.     var $xtick_factor;  // Factot to determine the maximum number of ticks depending on the plot with
  456.     var $texts=null;        // Text object to ge shown in the graph
  457.     var $lines=null;
  458.     var $bands=null;
  459.     var $text_scale_off=0;  // Text scale offset in world coordinates
  460.     var $background_image="",$background_image_type=-1,$background_image_format="png";
  461.     var $background_image_bright=0,$background_image_contr=0,$background_image_sat=0;
  462.     var $image_bright=0, $image_contr=0, $image_sat=0;
  463.     var $inline;
  464.     var $showcsim=0,$csimcolor="red"; //debug stuff, draw the csim boundaris on the image if <>0
  465.     var $grid_depth=DEPTH_BACK; // Draw grid under all plots as default
  466. //---------------
  467. // CONSTRUCTOR
  468.  
  469.     // aWIdth       Width in pixels of image
  470.     // aHeight      Height in pixels of image
  471.     // aCachedName  Name for image file in cache directory
  472.     //  aTimeOut    Timeout in minutes for image in cache
  473.     // aInline      If true the image is streamed back in the call to Stroke()
  474.     //             If false the image is just created in the cache
  475.     function Graph($aWidth=300,$aHeight=200,$aCachedName="",$aTimeOut=0,$aInline=true) {
  476.        
  477.     // If timing is used create a new timing object
  478.     if( BRAND_TIMING ) {
  479.         global $tim;
  480.         $tim = new JpgTimer();
  481.         $tim->Push();
  482.     }
  483.        
  484.     // Automtically generate the image file name based on the name of the script that
  485.     // generates the graph
  486.     if( $aCachedName=="auto" )
  487.         $aCachedName=GenImgName();
  488.            
  489.     // Should the image be streamed back to the browser or only to the cache?
  490.     $this->inline=$aInline;
  491.        
  492.     $this->img      =  new RotImage($aWidth,$aHeight);
  493.     $this->cache    =  new ImgStreamCache($this->img);
  494.     $this->cache->SetTimeOut($aTimeOut);
  495.     $this->title    =  new Text();
  496.     $this->subtitle =  new Text();
  497.     $this->legend   =  new Legend();
  498.        
  499.     // If the cached version exist just read it directly from the
  500.     // cache, stream it back to browser and exit
  501.     if( $aCachedName!="" && READ_CACHE && $aInline )
  502.         if( $this->cache->GetAndStream($aCachedName) ) {
  503.         exit();
  504.         }
  505.                
  506.     $this->cache_name = $aCachedName;
  507.     $this->SetTickDensity(); // Normal density
  508.     }
  509. //---------------
  510. // PUBLIC METHODS  
  511.  
  512.     // Should the grid be in front or back of the plot?
  513.     function SetGridDepth($aDepth) {
  514.     $this->grid_depth=$aDepth;
  515.     }
  516.    
  517.     // Specify graph angle 0-360 degrees.
  518.     function SetAngle($aAngle) {
  519.     $this->img->SetAngle($aAngle);
  520.     }
  521.    
  522.     // Add a plot object to the graph
  523.     function Add(&$aPlot) {
  524.     if( $aPlot == null )
  525.         JpGraphError::Raise("<b></b> Graph::Add() You tried to add a null plot to the graph.");            
  526.     $this->plots[] = &$aPlot;
  527.     }
  528.    
  529.     // Add plot to second Y-scale
  530.     function AddY2(&$aPlot) {
  531.     if( $aPlot == null )
  532.         JpGraphError::Raise("<b></b> Graph::AddY2() You tried to add a null plot to the graph.");              
  533.     $this->y2plots[] = &$aPlot;
  534.     }
  535.    
  536.     // Add text object to the graph
  537.     function AddText(&$aTxt) {
  538.     if( $aTxt == null )
  539.         JpGraphError::Raise("<b></b> Graph::AddText() You tried to add a null text to the graph.");    
  540.     if( is_array($aTxt) ) {
  541.         for($i=0; $i<count($aTxt); ++$i )
  542.         $this->texts[]=&$aTxt[$i];
  543.     }
  544.     else
  545.         $this->texts[] = &$aTxt;
  546.     }
  547.    
  548.     // Add a line object (class PlotLine) to the graph
  549.     function AddLine(&$aLine) {
  550.     if( $aLine == null )
  551.         JpGraphError::Raise("<b></b> Graph::AddLine() You tried to add a null line to the graph.");    
  552.     if( is_array($aLine) ) {
  553.         for($i=0; $i<count($aLine); ++$i )
  554.         $this->lines[]=&$aLine[$i];
  555.     }
  556.     else
  557.         $this->lines[] = &$aLine;
  558.     }
  559.  
  560.     // Add vertical or horizontal band
  561.     function AddBand(&$aBand) {
  562.     if( $aBand == null )
  563.         JpGraphError::Raise(" Graph::AddBand() You tried to add a null band to the graph.");
  564.     if( is_array($aBand) ) {
  565.         for($i=0; $i<count($aBand); ++$i )
  566.         $this->bands[] = &$aBand[$i];
  567.     }
  568.     else
  569.         $this->bands[] = &$aBand;
  570.     }
  571.  
  572.    
  573.     // Specify a background image
  574.     function SetBackgroundImage($aFileName,$aBgType=BKIMG_FILLPLOT,$aImgFormat="png") {
  575.  
  576.     if( $GLOBALS["gd2"] && !USE_TRUECOLOR ) {
  577.         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.");
  578.     }
  579.  
  580.     $this->background_image = $aFileName;
  581.     $this->background_image_type=$aBgType;
  582.     $this->background_image_format=$aImgFormat;
  583.     }
  584.    
  585.     // Adjust brightness and constrast for background image
  586.     function AdjBackgroundImage($aBright,$aContr=0,$aSat=0) {
  587.     $this->background_image_bright=$aBright;
  588.     $this->background_image_contr=$aContr;
  589.     $this->background_image_sat=$aSat;
  590.     }
  591.    
  592.     // Adjust brightness and constrast for image
  593.     function AdjImage($aBright,$aContr=0,$aSat=0) {
  594.     $this->image_bright=$aBright;
  595.     $this->image_contr=$aContr;
  596.     $this->image_sat=$aSat;
  597.     }
  598.    
  599.     // Set a frame around the plot area
  600.     function SetBox($aDrawPlotFrame=true,$aPlotFrameColor=array(0,0,0),$aPlotFrameWeight=1) {
  601.     $this->boxed = $aDrawPlotFrame;
  602.     $this->box_weight = $aPlotFrameWeight;
  603.     $this->box_color = $aPlotFrameColor;
  604.     }
  605.    
  606.     // Specify color for the plotarea (not the margins)
  607.     function SetColor($aColor) {
  608.     $this->plotarea_color=$aColor;
  609.     }
  610.    
  611.     // Specify color for the margins (all areas outside the plotarea)
  612.     function SetMarginColor($aColor) {
  613.     $this->margin_color=$aColor;
  614.     }
  615.    
  616.     // Set a frame around the entire image
  617.     function SetFrame($aDrawImgFrame=true,$aImgFrameColor=array(0,0,0),$aImgFrameWeight=1) {
  618.     $this->doframe = $aDrawImgFrame;
  619.     $this->frame_color = $aImgFrameColor;
  620.     $this->frame_weight = $aImgFrameWeight;
  621.     }
  622.        
  623.     // Set the shadow around the whole image
  624.     function SetShadow($aShowShadow=true,$aShadowWidth=5,$aShadowColor=array(102,102,102)) {
  625.     $this->doshadow = $aShowShadow;
  626.     $this->shadow_color = $aShadowColor;
  627.     $this->shadow_width = $aShadowWidth;
  628.     }
  629.  
  630.     // Specify x,y scale. Note that if you manually specify the scale
  631.     // you must also specify the tick distance with a call to Ticks::Set()
  632.     function SetScale($aAxisType,$aYMin=1,$aYMax=1,$aXMin=1,$aXMax=1) {
  633.     $this->axtype = $aAxisType;
  634.  
  635.     $yt=substr($aAxisType,-3,3);
  636.     if( $yt=="lin" )
  637.         $this->yscale = new LinearScale($aYMin,$aYMax);
  638.     elseif( $yt == "int" ) {
  639.         $this->yscale = new LinearScale($aYMin,$aYMax);
  640.         $this->yscale->SetIntScale();
  641.     }
  642.     elseif( $yt=="log" )
  643.         $this->yscale = new LogScale($aYMin,$aYMax);
  644.     else
  645.         JpGraphError::Raise(" Unknown scale specification for Y-scale. ($axtype)");
  646.            
  647.     $xt=substr($aAxisType,0,3);
  648.     if( $xt == "lin" || $xt == "tex" )
  649.         $this->xscale = new LinearScale($aXMin,$aXMax,"x");
  650.     elseif( $xt == "int" ) {
  651.         $this->xscale = new LinearScale($aXMin,$aXMax,"x");
  652.         $this->xscale->SetIntScale();
  653.     }
  654.     elseif( $xt == "log" )
  655.         $this->xscale = new LogScale($aXMin,$aXMax,"x");
  656.     else
  657.         JpGraphError::Raise(" Unknown scale specification for X-scale. ($aAxisType)");
  658.  
  659.     $this->xscale->Init($this->img);
  660.     $this->yscale->Init($this->img);                       
  661.                    
  662.     $this->xaxis = new Axis($this->img,$this->xscale);
  663.     $this->yaxis = new Axis($this->img,$this->yscale);
  664.     $this->xgrid = new Grid($this->xaxis);
  665.     $this->ygrid = new Grid($this->yaxis); 
  666.     $this->ygrid->Show();          
  667.     }
  668.    
  669.     // Specify secondary Y scale
  670.     function SetY2Scale($aAxisType="lin",$aY2Min=1,$aY2Max=1) {
  671.     if( $aAxisType=="lin" )
  672.         $this->y2scale = new LinearScale($aY2Min,$aY2Max);
  673.     elseif( $aAxisType=="log" ) {
  674.         $this->y2scale = new LogScale($aY2Min,$aY2Max);
  675.     }
  676.     else JpGraphError::Raise("JpGraph: Unsupported Y2 axis type: $axtype<br>");
  677.            
  678.     $this->y2scale->Init($this->img);  
  679.     $this->y2axis = new Axis($this->img,$this->y2scale);
  680.     $this->y2axis->scale->ticks->SetDirection(SIDE_LEFT);
  681.     $this->y2axis->SetLabelPos(SIDE_RIGHT);
  682.        
  683.     // Deafult position is the max x-value
  684.     $this->y2axis->SetPos($this->xscale->GetMaxVal());
  685.     $this->y2grid = new Grid($this->y2axis);                           
  686.     }
  687.    
  688.     // Specify density of ticks when autoscaling 'normal', 'dense', 'sparse', 'verysparse'
  689.     // The dividing factor have been determined heuristically according to my aesthetic
  690.     // sense (or lack off) y.m.m.v !
  691.     function SetTickDensity($aYDensity=TICKD_NORMAL,$aXDensity=TICKD_NORMAL) {
  692.     $this->xtick_factor=30;
  693.     $this->ytick_factor=25;    
  694.     switch( $aYDensity ) {
  695.         case TICKD_DENSE:
  696.         $this->ytick_factor=12;        
  697.         break;
  698.         case TICKD_NORMAL:
  699.         $this->ytick_factor=25;        
  700.         break;
  701.         case TICKD_SPARSE:
  702.         $this->ytick_factor=40;        
  703.         break;
  704.         case TICKD_VERYSPARSE:
  705.         $this->ytick_factor=100;           
  706.         break;     
  707.         default:
  708.         JpGraphError::Raise("JpGraph: Unsupported Tick density: $densy");
  709.     }
  710.     switch( $aXDensity ) {
  711.         case TICKD_DENSE:
  712.         $this->xtick_factor=18;                        
  713.         break;
  714.         case TICKD_NORMAL:
  715.         $this->xtick_factor=30;        
  716.         break;
  717.         case TICKD_SPARSE:
  718.         $this->xtick_factor=45;                
  719.         break;
  720.         case TICKD_VERYSPARSE:
  721.         $this->xtick_factor=60;                            
  722.         break;     
  723.         default:
  724.         JpGraphError::Raise("JpGraph: Unsupported Tick density: $densx");
  725.     }      
  726.     }
  727.    
  728.     // Get a string of all image map areas 
  729.     function GetCSIMareas() {
  730.     $csim="";
  731.     foreach ($this->plots as $p) {
  732.         $csim.= $p->GetCSIMareas();
  733.     }      
  734.     return $csim;
  735.     }
  736.    
  737.     // Get a complete <MAP>..</MAP> tag for the final image map
  738.     function GetHTMLImageMap($aMapName) {
  739.     $im = "<MAP NAME=\"$aMapName\">\n";
  740.     $im .= $this->GetCSIMareas();
  741.     $im .= "</MAP>";
  742.     return $im;
  743.     }
  744.  
  745.     // Stroke the graph
  746.     // $aStrokeFileName If != "" the image will be written to this file and NOT
  747.     // streamed back to the browser
  748.     function Stroke($aStrokeFileName="") {     
  749.        
  750.     // Do any pre-stroke adjustment that is needed by the different plot types
  751.     // (i.e bar plots want's to add an offset to the x-labels etc)
  752.     for($i=0; $i<count($this->plots)    ; ++$i ) {
  753.         $this->plots[$i]->PreStrokeAdjust($this);
  754.         $this->plots[$i]->Legend($this);
  755.     }
  756.        
  757.     // Any plots on the second Y scale?
  758.     if( $this->y2scale != null ) {
  759.         for($i=0; $i<count($this->y2plots)  ; ++$i ) {
  760.         $this->y2plots[$i]->PreStrokeAdjust($this);
  761.         $this->y2plots[$i]->Legend($this);
  762.         }
  763.     }
  764.        
  765.     // Bail out if any of the Y-axis not been specified and
  766.     // has no plots. (This means it is impossible to do autoscaling and
  767.     // no other scale was given so we can't possible draw anything). If you use manual
  768.     // scaling you also have to supply the tick steps as well.
  769.     if( (!$this->yscale->IsSpecified() && count($this->plots)==0) ||
  770.         ($this->y2scale!=null && !$this->y2scale->IsSpecified() && count($this->y2plots)==0) ) {
  771.         JpGraphError::Raise("<strong>JpGraph: Can't draw unspecified Y-scale.</strong><br>
  772.                 You have either:
  773.                 <br>* Specified an Y axis for autoscaling but have not supplied any plots
  774.                 <br>* Specified a scale manually but have forgot to specify the tick steps");
  775.     }
  776.        
  777.     // Bail out if no plots and no specified X-scale
  778.     if( (!$this->xscale->IsSpecified() && count($this->plots)==0 && count($this->y2plots)==0) )
  779.         JpGraphError::Raise("<strong>JpGraph: Can't draw unspecified X-scale.</strong><br>No plots.<br>");
  780.  
  781.     //Check if we should autoscale y-axis
  782.     if( !$this->yscale->IsSpecified() && count($this->plots)>0 ) {
  783.         list($min,$max) = $this->GetPlotsYMinMax($this->plots);
  784.         $this->yscale->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor);
  785.     }
  786.  
  787.     if( $this->y2scale != null)
  788.         if( !$this->y2scale->IsSpecified() && count($this->y2plots)>0 ) {
  789.         list($min,$max) = $this->GetPlotsYMinMax($this->y2plots);
  790.         $this->y2scale->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor);
  791.         }          
  792.                
  793.     //Check if we should autoscale x-axis
  794.     if( !$this->xscale->IsSpecified() ) {
  795.         if( substr($this->axtype,0,4) == "text" ) {
  796.         $max=0;
  797.         foreach( $this->plots as $p ) {
  798.             $max=max($max,$p->numpoints-1);
  799.         }
  800.         $min=0;
  801.         if( $this->y2axis != null ) {
  802.             foreach( $this->y2plots as $p ) {
  803.             $max=max($max,$p->numpoints-1);
  804.             }          
  805.         }
  806.         $this->xscale->Update($this->img,$min,$max);
  807.         $this->xscale->ticks->Set($this->xaxis->tick_step,1);
  808.         $this->xscale->ticks->SupressMinorTickMarks();
  809.         }
  810.         else {
  811.         list($min,$ymin) = $this->plots[0]->Min();
  812.         list($max,$ymax) = $this->plots[0]->Max();
  813.         foreach( $this->plots as $p ) {
  814.             list($xmin,$ymin) = $p->Min();
  815.             list($xmax,$ymax) = $p->Max();         
  816.             $min = Min($xmin,$min);
  817.             $max = Max($xmax,$max);
  818.         }
  819.         if( $this->y2axis != null ) {
  820.             foreach( $this->y2plots as $p ) {
  821.             list($xmin,$ymin) = $p->Min();
  822.             list($xmax,$ymax) = $p->Max();         
  823.             $min = Min($xmin,$min);
  824.             $max = Max($xmax,$max);
  825.             }          
  826.         }
  827.         $this->xscale->AutoScale($this->img,$min,$max,$this->img->plotwidth/$this->xtick_factor);
  828.         }
  829.            
  830.         //Adjust position of y-axis and y2-axis to minimum/maximum of x-scale
  831.         $this->yaxis->SetPos($this->xscale->GetMinVal());
  832.         if( $this->y2axis != null ) {
  833.         $this->y2axis->SetPos($this->xscale->GetMaxVal());
  834.         $this->y2axis->SetTitleSide(SIDE_RIGHT);
  835.         }
  836.     }      
  837.        
  838.     // If we have a negative values and x-axis position is at 0
  839.     // we need to supress the first and possible the last tick since
  840.     // they will be drawn on top of the y-axis (and possible y2 axis)
  841.     // The test below might seem strange the reasone being that if
  842.     // the user hasn't specified a value for position this will not
  843.     // be set until we do the stroke for the axis so as of now it
  844.     // is undefined.
  845.        
  846.     if( !$this->xaxis->pos && $this->yscale->GetMinVal() < 0 ) {
  847.         $this->yscale->ticks->SupressZeroLabel(false);
  848.         $this->xscale->ticks->SupressFirst();
  849.         if( $this->y2axis != null ) {
  850.         $this->xscale->ticks->SupressLast();
  851.         }
  852.     }
  853.        
  854.     $this->StrokePlotArea();
  855.        
  856.     // Stroke axis
  857.     $this->xaxis->Stroke($this->yscale);
  858.     $this->yaxis->Stroke($this->xscale);       
  859.  
  860.     // Stroke bands
  861.     if( $this->bands != null )
  862.         for($i=0; $i<count($this->bands); ++$i) {
  863.                 // Stroke all bands that asks to be in the background
  864.         if( $this->bands[$i]->depth == DEPTH_BACK )
  865.             $this->bands[$i]->Stroke($this->img,$this->xscale,$this->yscale);
  866.         }
  867.  
  868.     if( $this->grid_depth == DEPTH_BACK ) {
  869.         $this->ygrid->Stroke();
  870.         $this->xgrid->Stroke();
  871.     }
  872.                
  873.     // Stroke Y2-axis
  874.     if( $this->y2axis != null ) {      
  875.         $this->y2axis->Stroke($this->xscale);              
  876.         $this->y2grid->Stroke();
  877.     }
  878.        
  879.     $oldoff=$this->xscale->off;
  880.     if(substr($this->axtype,0,4)=="text") {
  881.         $this->xscale->off +=
  882.          ceil($this->xscale->scale_factor*$this->text_scale_off*$this->xscale->ticks->minor_step);
  883.     }
  884.  
  885.     // Stroke all plots for Y1 axis
  886.     for($i=0; $i<count($this->plots)    ; ++$i ) {
  887.         $this->plots[$i]->Stroke($this->img,$this->xscale,$this->yscale);
  888.         $this->plots[$i]->StrokeMargin($this->img);
  889.     }                      
  890.        
  891.     // Stroke all plots for Y2 axis
  892.     if( $this->y2scale != null )
  893.         for($i=0; $i< count($this->y2plots); ++$i ) {  
  894.         $this->y2plots[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
  895.         }      
  896.  
  897.     $this->xscale->off=$oldoff;
  898.        
  899.     if( $this->grid_depth == DEPTH_FRONT ) {
  900.         $this->ygrid->Stroke();
  901.         $this->xgrid->Stroke();
  902.     }
  903.  
  904.     // Stroke bands
  905.     if( $this->bands!= null )
  906.         for($i=0; $i<count($this->bands); ++$i) {
  907.                 // Stroke all bands that asks to be in the foreground
  908.         if( $this->bands[$i]->depth == DEPTH_FRONT )
  909.             $this->bands[$i]->Stroke($this->img,$this->xscale,$this->yscale);
  910.         }
  911.  
  912.     // Stroke any lines added
  913.     if( $this->lines != null ) {
  914.         for($i=0; $i<count($this->lines); ++$i) {
  915.         $this->lines[$i]->Stroke($this->img,$this->xscale,$this->yscale);
  916.         }
  917.     }
  918.        
  919.     // Finally draw the axis again since some plots may have nagged
  920.     // the axis in the edges.
  921.     $this->yaxis->Stroke($this->xscale);
  922.     $this->xaxis->Stroke($this->yscale);
  923.        
  924.     if( $this->y2scale != null)
  925.         $this->y2axis->Stroke($this->xscale);  
  926.        
  927.     $this->StrokePlotBox();
  928.        
  929.     // The titles and legends never gets rotated so make sure
  930.     // that the angle is 0 before stroking them            
  931.     $aa = $this->img->SetAngle(0);
  932.     $this->StrokeTitles();
  933.     $this->legend->Stroke($this->img);     
  934.     $this->StrokeTexts();  
  935.     $this->img->SetAngle($aa); 
  936.            
  937.     // Draw an outline around the image map
  938.     if(JPG_DEBUG)
  939.         $this->DisplayClientSideaImageMapAreas();      
  940.  
  941.     // Adjust the appearance of the image
  942.     $this->AdjustSaturationBrightnessContrast();
  943.        
  944.     // Finally stream the generated picture                
  945.     $this->cache->PutAndStream($this->img,$this->cache_name,$this->inline,$aStrokeFileName);       
  946.     }
  947.  
  948. //---------------
  949. // PRIVATE METHODS 
  950.     // Private helper function for backgound image
  951.     function LoadBkgImage($aImgFormat="png",$aBright=0,$aContr=0) {    
  952.     $f = "imagecreatefrom".$aImgFormat;
  953.     $imgtag = $aImgFormat;
  954.     if( $aImgFormat == "jpeg" )
  955.         $imgtag = "jpg";
  956.     if( !strstr($this->background_image,$imgtag) && strstr($this->background_image,".") )
  957.         JpGraphError::Raise(" Background image seems to be of different type (has different file extension)
  958.             than specified imagetype. <br>Specified: '".$aImgFormat."'<br>File: '".$this->background_image."'");
  959.     $img = $f($this->background_image);
  960.     if( !$img ) {
  961.         JpGraphError::Raise(" Can't read background image: '".$this->background_image."'");  
  962.     }
  963.     return $img;
  964.     }  
  965.  
  966.     // Private
  967.     // Stroke the plot area with either a solid color or a background image
  968.     function StrokePlotArea() {
  969.     // Copy in background image
  970.     if( $this->background_image != "" ) {
  971.         $bkgimg = $this->LoadBkgImage($this->background_image_format);
  972.         $this->img->_AdjBrightContrast($bkgimg,$this->background_image_bright,
  973.         $this->background_image_contr);                          
  974.         $this->img->_AdjSat($bkgimg,$this->background_image_sat);
  975.         $bw = ImageSX($bkgimg);
  976.         $bh = ImageSY($bkgimg);
  977.  
  978.         $aa = $this->img->SetAngle(0);
  979.        
  980.         switch( $this->background_image_type ) {
  981.         case BGIMG_FILLPLOT: // Resize to just fill the plotarea
  982.             $this->StrokeFrame();
  983.         $GLOBALS["copyfunc"]($this->img->img,$bkgimg,
  984.         $this->img->left_margin,$this->img->top_margin,
  985.         0,0,$this->img->plotwidth,$this->img->plotheight,
  986.         $bw,$bh);
  987.         break;
  988.         case BGIMG_FILLFRAME: // Fill the whole area from upper left corner, resize to just fit
  989.             $GLOBALS["copyfunc"]($this->img->img,$bkgimg,
  990.             0,0,0,0,
  991.             $this->img->width,$this->img->height,
  992.             $bw,$bh);
  993.         $this->StrokeFrame();
  994.         break;
  995.         case BGIMG_COPY: // Just copy the image from left corner, no resizing
  996.             $GLOBALS["copyfunc"]($this->img->img,$bkgimg,
  997.             0,0,0,0,
  998.             $bw,$bh,
  999.             $bw,$bh);
  1000.         $this->StrokeFrame();
  1001.         break;
  1002.         case BGIMG_CENTER: // Center original image in the plot area
  1003.             $centerx = round($this->img->plotwidth/2+$this->img->left_margin-$bw/2);
  1004.         $centery = round($this->img->plotheight/2+$this->img->top_margin-$bh/2);
  1005.         $GLOBALS["copyfunc"]($this->img->img,$bkgimg,
  1006.         $centerx,$centery,
  1007.         0,0,
  1008.         $bw,$bh,
  1009.         $bw,$bh);
  1010.         $this->StrokeFrame();
  1011.         break;
  1012.         default:
  1013.             JpGraphError::Raise(" Unknown background image layout");
  1014.         }          
  1015.         $this->img->SetAngle($aa);                                                 
  1016.     }
  1017.     else {             
  1018.         $aa = $this->img->SetAngle(0);
  1019.         $this->StrokeFrame();
  1020.         $this->img->SetAngle($aa);         
  1021.  
  1022.         $this->img->PushColor($this->plotarea_color);
  1023.  
  1024.         // Note: To be consistent we really should take a possible shadow
  1025.         // into account. However, that causes some problem for the LinearScale class
  1026.         // since in the current design it does not have any links to class Graph which
  1027.         // means it has no way of compensating for the adjusted plotarea in case of a
  1028.         // shadow. So, until I redesign LinearScale we can't compensate for this.
  1029.         // So just set the two adjustment parameters to zero for now.
  1030.         $boxadj = 0; //$this->doframe ? $this->frame_weight : 0 ;
  1031.         $adj = 0; //$this->doshadow ? $this->shadow_width : 0 ;
  1032.  
  1033.         $this->img->FilledRectangle($this->img->left_margin+$boxadj,
  1034.           $this->img->top_margin+$boxadj,
  1035.           $this->img->width-$this->img->right_margin-$adj-2*$boxadj,
  1036.           $this->img->height-$this->img->bottom_margin-$adj-2*$boxadj);
  1037.  
  1038.         $this->img->PopColor();
  1039.     }  
  1040.     $this->img->SetAngle($aa);
  1041.     }  
  1042.    
  1043.    
  1044.     function StrokePlotBox() {
  1045.     // Should we draw a box around the plot area?
  1046.     if( $this->boxed ) {
  1047.         $this->img->SetLineWeight($this->box_weight);
  1048.         $this->img->SetColor($this->box_color);
  1049.         $this->img->Rectangle(
  1050.         $this->img->left_margin,$this->img->top_margin,
  1051.         $this->img->width-$this->img->right_margin,
  1052.         $this->img->height-$this->img->bottom_margin);
  1053.     }                      
  1054.     }      
  1055.  
  1056.     function StrokeTitles() {
  1057.     // Stroke title
  1058.     $this->title->Center($this->img->left_margin,$this->img->width-$this->img->right_margin,5);
  1059.     $this->title->Stroke($this->img);
  1060.        
  1061.     // ... and subtitle
  1062.     $this->subtitle->Center($this->img->left_margin,$this->img->width-$this->img->right_margin,
  1063.     7+$this->title->GetFontHeight($this->img));
  1064.     $this->subtitle->Stroke($this->img);
  1065.     }
  1066.  
  1067.     function StrokeTexts() {
  1068.     // Stroke any user added text objects
  1069.     if( $this->texts != null ) {
  1070.         for($i=0; $i<count($this->texts); ++$i) {
  1071.         $this->texts[$i]->Stroke($this->img);
  1072.         }
  1073.     }
  1074.     }
  1075.  
  1076.     function DisplayClientSideaImageMapAreas() {
  1077.     // Debug stuff - display the outline of the image map areas
  1078.     foreach ($this->plots as $p) {
  1079.         $csim.= $p->GetCSIMareas();
  1080.     }
  1081.     $csim .= $this->legend->GetCSIMareas();
  1082.     if (preg_match_all("/area shape=\"(\w+)\" coords=\"([0-9\, ]+)\"/", $csim, $coords)) {
  1083.         $this->img->SetColor($this->csimcolor);
  1084.         for ($i=0; $i<count($coords[0]); $i++) {
  1085.         if ($coords[1][$i]=="poly") {
  1086.             preg_match_all('/\s*([0-9]+)\s*,\s*([0-9]+)\s*,*/',$coords[2][$i],$pts);
  1087.             $this->img->SetStartPoint($pts[1][count($pts[0])-1],$pts[2][count($pts[0])-1]);
  1088.             for ($j=0; $j<count($pts[0]); $j++) {
  1089.             $this->img->LineTo($pts[1][$j],$pts[2][$j]);
  1090.             }
  1091.         } else if ($coords[1][$i]=="rect") {
  1092.             $pts = preg_split('/,/', $coords[2][$i]);
  1093.             $this->img->SetStartPoint($pts[0],$pts[1]);
  1094.             $this->img->LineTo($pts[2],$pts[1]);
  1095.             $this->img->LineTo($pts[2],$pts[3]);
  1096.             $this->img->LineTo($pts[0],$pts[3]);
  1097.             $this->img->LineTo($pts[0],$pts[1]);                   
  1098.         }
  1099.         }
  1100.     }
  1101.     }
  1102.  
  1103.     function AdjustSaturationBrightnessContrast() {
  1104.     // Adjust the brightness and contrast of the image
  1105.     if( $this->image_contr || $this->image_bright )
  1106.         $this->img->AdjBrightContrast($this->image_bright,$this->image_contr);
  1107.     if( $this->image_sat )                                           
  1108.         $this->img->AdjSat($this->image_sat);
  1109.     }
  1110.  
  1111.     // Text scale offset in world coordinates
  1112.     function SetTextScaleOff($aOff) {
  1113.     $this->text_scale_off = $aOff;
  1114.     }
  1115.    
  1116.     // Get min and max values for all included plots
  1117.     function GetPlotsYMinMax(&$aPlots) {
  1118.     list($xmax,$max) = $aPlots[0]->Max();
  1119.     list($xmin,$min) = $aPlots[0]->Min();
  1120.     for($i=0; $i<count($aPlots); ++$i ) {
  1121.         list($xmax,$ymax)=$aPlots[$i]->Max();
  1122.         list($xmin,$ymin)=$aPlots[$i]->Min();
  1123.         if (!is_string($ymax) || $ymax != "") $max=max($max,$ymax);
  1124.         if (!is_string($ymin) || $ymin != "") $min=min($min,$ymin);
  1125.     }
  1126.     if( $min == "" ) $min = 0;
  1127.     if( $max == "" ) $max = 0;
  1128.     if( $min == 0 && $max == 0 ) {
  1129.         // Special case if all values are 0
  1130.         $min=0;$max=1;         
  1131.     }
  1132.     return array($min,$max);
  1133.     }
  1134.  
  1135.     // Draw a frame around the image
  1136.     function StrokeFrame() {
  1137.     if( !$this->doframe ) return;
  1138.     if( $this->doshadow ) {
  1139.         $this->img->SetColor($this->frame_color);          
  1140.         if( $this->background_image_type <= 1 )
  1141.         $c = $this->margin_color;
  1142.         else
  1143.         $c = false;
  1144.         $this->img->ShadowRectangle(0,0,$this->img->width,$this->img->height,
  1145.                     $c,$this->shadow_width);
  1146.     }
  1147.     else {
  1148.         $this->img->SetLineWeight($this->frame_weight);
  1149.         if( $this->background_image_type <= 1 ) {
  1150.         $this->img->SetColor($this->margin_color);
  1151.         $this->img->FilledRectangle(1,1,$this->img->width-2,$this->img->height-2);     
  1152.         }
  1153.         $this->img->SetColor($this->frame_color);
  1154.         $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);       
  1155.     }
  1156.     }
  1157. } // Class
  1158.  
  1159.  
  1160. //===================================================
  1161. // CLASS TTF
  1162. // Description: Handle TTF font names
  1163. //===================================================
  1164. class TTF {
  1165.     var $font_fam;
  1166. //---------------
  1167. // CONSTRUCTOR
  1168.     function TTF() {
  1169.     // Base file names for available fonts
  1170.     $this->font_fam=array(
  1171.         FF_COURIER => TTF_DIR."courier",
  1172.         FF_VERDANA => TTF_DIR."verdana",
  1173.         FF_TIMES => TTF_DIR."times",
  1174.         FF_HANDWRT => TTF_DIR."handwriting",
  1175.         FF_COMIC => TTF_DIR."comic",
  1176.         FF_ARIAL => TTF_DIR."arial",
  1177.         FF_BOOK => TTF_DIR."bookant");
  1178.     }
  1179.  
  1180. //---------------
  1181. // PUBLIC METHODS  
  1182.     // Create the TTF file from the font specification
  1183.     function File($fam,$style=FS_NORMAL) {
  1184.     $f=$this->font_fam[$fam];
  1185.     if( !$f ) JpGraphError::Raise(" Unknown TTF font family.");
  1186.     switch( $style ) {
  1187.         case FS_NORMAL:
  1188.         break;
  1189.         case FS_BOLD: $f .= "bd";
  1190.         break;
  1191.         case FS_ITALIC: $f .= "i";
  1192.         break;
  1193.         case FS_BOLDIT: $f .= "bi";
  1194.         break;
  1195.         default:
  1196.         JpGraphError::Raise(" Unknown TTF Style.");
  1197.     }
  1198.     $f .= ".ttf";
  1199.        
  1200.     // Check that file exist
  1201.     if( !file_exists($f) )
  1202.         JpGraphError::Raise(" Can't open font file \"$f\". Wrong directory?");
  1203.        
  1204.     return $f;
  1205.     }
  1206. } // Class
  1207.  
  1208. //===================================================
  1209. // CLASS LineProperty
  1210. // Description: Holds properties for a line
  1211. //===================================================
  1212. class LineProperty {
  1213.     var $iWeight=1, $iColor="black",$iStyle="solid";
  1214.     var $iShow=true;
  1215.    
  1216. //---------------
  1217. // PUBLIC METHODS  
  1218.     function SetColor($aColor) {
  1219.     $this->iColor = $aColor;
  1220.     }
  1221.    
  1222.     function SetWeight($aWeight) {
  1223.     $this->iWeight = $aWeight;
  1224.     }
  1225.    
  1226.     function SetStyle($aStyle) {
  1227.     $this->iStyle = $aStyle;
  1228.     }
  1229.        
  1230.     function Show($aShow=true) {
  1231.     $this->iShow=$aShow;
  1232.     }
  1233.    
  1234.     function Stroke($aImg,$aX1,$aY1,$aX2,$aY2) {
  1235.     if( $this->iShow ) {
  1236.         $aImg->SetColor($this->iColor);
  1237.         $aImg->SetLineWeight($this->iWeight);
  1238.         $aImg->SetLineStyle($this->iStyle);        
  1239.         $aImg->StyleLine($aX1,$aY1,$aX2,$aY2);
  1240.     }
  1241.     }
  1242. }
  1243.  
  1244.  
  1245.  
  1246. //===================================================
  1247. // CLASS Text
  1248. // Description: Arbitrary text object that can be added to the graph
  1249. //===================================================
  1250. class Text {
  1251.     var $t,$x=0,$y=0,$halign="left",$valign="top",$color=array(0,0,0);
  1252.     var $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12,$hide=false,$dir=0;
  1253.     var $boxed=false;   // Should the text be boxed
  1254.     var $paragraph_align="left";
  1255.  
  1256. //---------------
  1257. // CONSTRUCTOR
  1258.  
  1259.     // Create new text at absolute pixel coordinates
  1260.     function Text($aTxt="",$aXAbsPos=0,$aYAbsPos=0) {
  1261.     $this->t = $aTxt;
  1262.     $this->x = $aXAbsPos;
  1263.     $this->y = $aYAbsPos;
  1264.     }
  1265. //---------------
  1266. // PUBLIC METHODS  
  1267.     // Set the string in the text object
  1268.     function Set($aTxt) {
  1269.     $this->t = $aTxt;
  1270.     }
  1271.    
  1272.     // Specify the position and alignment for the text object
  1273.     function Pos($aXAbsPos=0,$aYAbsPos=0,$aHAlign="left",$aVAlign="top") {
  1274.     $this->x = $aXAbsPos;
  1275.     $this->y = $aYAbsPos;
  1276.     $this->halign = $aHAlign;
  1277.     $this->valign = $aVAlign;
  1278.     }
  1279.    
  1280.     // Specify alignment for the text
  1281.     function Align($aHAlign,$aVAlign="top") {
  1282.     $this->halign = $aHAlign;
  1283.     $this->valign = $aVAlign;
  1284.     }      
  1285.  
  1286.     // Specifies the alignment for a multi line text
  1287.     function ParagraphAlign($aAlign) {
  1288.     $this->paragraph_align = $aAlign;
  1289.     }
  1290.    
  1291.     // Specify that the text should be boxed. fcolor=frame color, bcolor=border color,
  1292.     // $shadow=drop shadow should be added around the text.
  1293.     function SetBox($aFrameColor=array(255,255,255),$aBorderColor=array(0,0,0),$aShadow=false) {
  1294.     if( $aFrameColor==false )
  1295.         $this->boxed=false;
  1296.     else
  1297.         $this->boxed=true;
  1298.     $this->fcolor=$aFrameColor;
  1299.     $this->bcolor=$aBorderColor;
  1300.     $this->shadow=$aShadow;
  1301.     }
  1302.    
  1303.     // Hide the text
  1304.     function Hide($aHide=true) {
  1305.     $this->hide=$aHide;
  1306.     }
  1307.    
  1308.     // This looks ugly since it's not a very orthogonal design
  1309.     // but I added this "inverse" of Hide() to harmonize
  1310.     // with some classes which I designed more recently (especially)
  1311.     // jpgraph_gantt
  1312.     function Show($aShow=true) {
  1313.     $this->hide=!$aShow;
  1314.     }
  1315.    
  1316.     // Specify font
  1317.     function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
  1318.     $this->font_family=$aFamily;
  1319.     $this->font_style=$aStyle;
  1320.     $this->font_size=$aSize;
  1321.     }
  1322.            
  1323.     // Center the text between $left and $right coordinates
  1324.     function Center($aLeft,$aRight,$aYAbsPos=false) {
  1325.     $this->x = $aLeft + ($aRight-$aLeft )/2;
  1326.     $this->halign = "center";
  1327.     if( is_numeric($aYAbsPos) )
  1328.         $this->y = $aYAbsPos;      
  1329.     }
  1330.    
  1331.     // Set text color
  1332.     function SetColor($aColor) {
  1333.     $this->color = $aColor;
  1334.     }
  1335.    
  1336.     // Orientation of text. Note only TTF fonts can have an arbitrary angle
  1337.     function SetOrientation($aDirection=0) {
  1338.     if( is_numeric($aDirection) )
  1339.         $this->dir=$aDirection;
  1340.     elseif( $aDirection=="h" )
  1341.         $this->dir = 0;
  1342.     elseif( $aDirection=="v" )
  1343.         $this->dir = 90;
  1344.     else JpGraphError::Raise(" Invalid direction specified for text.");
  1345.     }
  1346.    
  1347.     // Total width of text
  1348.     function GetWidth(&$aImg) {
  1349.     $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);     
  1350.     return $aImg->GetTextWidth($this->t);
  1351.     }
  1352.    
  1353.     // Hight of font
  1354.     function GetFontHeight(&$aImg) {
  1355.     $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);     
  1356.     return $aImg->GetFontHeight();
  1357.     }
  1358.  
  1359.     function GetTextHeight(&$aImg) {
  1360.     $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);     
  1361.     return $aImg->GetTextHeight($this->t);
  1362.     }
  1363.    
  1364.     // Display text in image
  1365.     function Stroke(&$aImg,$x=-1,$y=-1) {
  1366.     if( $x>-1 ) $this->x = $x;
  1367.     if( $y>-1 ) $this->y = $y;
  1368.  
  1369.     // If position been given as a fraction of the image size
  1370.     // calculate the absolute position
  1371.     if( $this->x < 1 ) $this->x *= $aImg->width;
  1372.     if( $this->y < 1 ) $this->y *= $aImg->height;
  1373.  
  1374.     $aImg->PushColor($this->color);
  1375.     $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  1376.     $aImg->SetTextAlign($this->halign,$this->valign);
  1377.     if( $this->boxed ) {
  1378.         if( $this->fcolor=="nofill" ) $this->fcolor=false;     
  1379.         $aImg->StrokeBoxedText($this->x,$this->y,$this->t,
  1380.         $this->dir,$this->fcolor,$this->bcolor,$this->shadow,
  1381.         $this->paragraph_align);
  1382.     }
  1383.     else {
  1384.         $aImg->StrokeText($this->x,$this->y,$this->t,$this->dir,
  1385.         $this->paragraph_align);
  1386.     }
  1387.     $aImg->PopColor($this->color); 
  1388.     }
  1389. } // Class
  1390.  
  1391. //===================================================
  1392. // CLASS Grid
  1393. // Description: responsible for drawing grid lines in graph
  1394. //===================================================
  1395. class Grid {
  1396.     var $img;
  1397.     var $scale;
  1398.     var $grid_color=array(196,196,196);
  1399.     var $type="solid";
  1400.     var $show=false, $showMinor=false,$weight=1;
  1401. //---------------
  1402. // CONSTRUCTOR
  1403.     function Grid(&$aAxis) {
  1404.     $this->scale = &$aAxis->scale;
  1405.     $this->img = &$aAxis->img;
  1406.     }
  1407. //---------------
  1408. // PUBLIC METHODS
  1409.     function SetColor($aColor) {
  1410.     $this->grid_color=$aColor;
  1411.     }
  1412.    
  1413.     function SetWeight($aWeight) {
  1414.     $this->weight=$aWeight;
  1415.     }
  1416.    
  1417.     // Specify if grid should be dashed, dotted or solid
  1418.     function SetLineStyle($aType) {
  1419.     $this->type = $aType;
  1420.     }
  1421.    
  1422.     // Decide if both major and minor grid should be displayed
  1423.     function Show($aShowMajor=true,$aShowMinor=false) {
  1424.     $this->show=$aShowMajor;
  1425.     $this->showMinor=$aShowMinor;
  1426.     }
  1427.    
  1428.     // Display the grid
  1429.     function Stroke() {
  1430.     if( $this->showMinor )
  1431.         $this->DoStroke($this->scale->ticks->ticks_pos);
  1432.     else
  1433.         $this->DoStroke($this->scale->ticks->maj_ticks_pos);
  1434.     }
  1435.    
  1436. //--------------
  1437. // Private methods 
  1438.     // Draw the grid
  1439.     function DoStroke(&$aTicksPos) {
  1440.     if( !$this->show )
  1441.         return;
  1442.     $this->img->SetColor($this->grid_color);
  1443.     $this->img->SetLineWeight($this->weight);
  1444.     $nbrgrids = count($aTicksPos);                 
  1445.     if( $this->scale->type=="y" ) {
  1446.         $xl=$this->img->left_margin;
  1447.         $xr=$this->img->width-$this->img->right_margin;
  1448.         for($i=0; $i<$nbrgrids; ++$i) {
  1449.         $y=$aTicksPos[$i];
  1450.         if( $this->type == "solid" )
  1451.             $this->img->Line($xl,$y,$xr,$y);
  1452.         elseif( $this->type == "dotted" )
  1453.             $this->img->DashedLine($xl,$y,$xr,$y,1,6);
  1454.         elseif( $this->type == "dashed" )
  1455.             $this->img->DashedLine($xl,$y,$xr,$y,2,4);
  1456.         elseif( $this->type == "longdashed" )
  1457.             $this->img->DashedLine($xl,$y,$xr,$y,8,6);
  1458.         }
  1459.     }
  1460.                
  1461.     if( $this->scale->type=="x" ) {
  1462.         $yu=$this->img->top_margin;
  1463.         $yl=$this->img->height-$this->img->bottom_margin;
  1464.         $x=$aTicksPos[0];
  1465.         $limit=$this->img->width-$this->img->right_margin;
  1466.         $i=0;
  1467.         // We must also test for limit since we might have
  1468.         // an offset and the number of ticks is calculated with
  1469.         // assumption offset==0 so we might end up drawing one
  1470.         // to many gridlines
  1471.         while( $x<=$limit && $i<count($aTicksPos)) {
  1472.         $x=$aTicksPos[$i];
  1473.         if( $this->type == "solid" )               
  1474.             $this->img->Line($x,$yl,$x,$yu);
  1475.         elseif( $this->type == "dotted" )
  1476.             $this->img->DashedLine($x,$yl,$x,$yu,1,6);
  1477.         elseif( $this->type == "dashed" )
  1478.             $this->img->DashedLine($x,$yl,$x,$yu,2,4);
  1479.         elseif( $this->type == "longdashed" )
  1480.             $this->img->DashedLine($x,$yl,$x,$yu,8,6);                                 
  1481.         ++$i;                                  
  1482.         }
  1483.     }      
  1484.     return true;
  1485.     }
  1486. } // Class
  1487.  
  1488. //===================================================
  1489. // CLASS Axis
  1490. // Description: Defines X and Y axis. Notes that at the
  1491. // moment the code is not really good since the axis on
  1492. // several occasion must know wheter it's an X or Y axis.
  1493. // This was a design decision to make the code easier to
  1494. // follow.
  1495. //===================================================
  1496. class Axis {
  1497.     var $pos = false;
  1498.     var $weight=1;
  1499.     var $color=array(0,0,0),$label_color=array(0,0,0);
  1500.     var $img=null,$scale=null;
  1501.     var $hide=false;
  1502.     var $ticks_label=false;
  1503.     var $show_first_label=true;
  1504.     var $label_step=1; // Used by a text axis to specify what multiple of major steps
  1505.     // should be labeled.
  1506.     var $tick_step=1;
  1507.     var $labelPos=0;   // Which side of the axis should the labels be?
  1508.     var $title=null,$title_adjust,$title_margin,$title_side=SIDE_LEFT;
  1509.     var $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12,$label_angle=0;
  1510.     var $tick_label_margin=6;
  1511. //---------------
  1512. // CONSTRUCTOR
  1513.     function Axis(&$img,&$aScale,$color=array(0,0,0)) {
  1514.     $this->img = &$img;
  1515.     $this->scale = &$aScale;
  1516.     $this->color = $color;
  1517.     $this->title=new Text("");
  1518.        
  1519.     if( $aScale->type=="y" ) {
  1520.         $this->title_margin = 25;
  1521.         $this->title_adjust="middle";
  1522.         $this->title->SetOrientation(90);
  1523.         $this->tick_label_margin=6;
  1524.         $this->labelPos=SIDE_LEFT;
  1525.     }
  1526.     else {
  1527.         $this->title_margin = 5;
  1528.         $this->title_adjust="high";
  1529.         $this->title->SetOrientation(0);           
  1530.         $this->tick_label_margin=3;
  1531.         $this->labelPos=SIDE_DOWN;
  1532.     }
  1533.     }
  1534. //---------------
  1535. // PUBLIC METHODS  
  1536.  
  1537.     // Utility function to set the direction for tick marks
  1538.     function SetTickDirection($aDir) {
  1539.     $this->scale->ticks->SetDirection($aDir);
  1540.     }
  1541.    
  1542.     function SetLabelFormatString($aFormStr) {
  1543.     $this->scale->ticks->SetLabelFormat($aFormStr);
  1544.     }
  1545.    
  1546.     function SetLabelFormatCallback($aFuncName) {
  1547.     $this->scale->ticks->SetFormatCallback($aFuncName);
  1548.     }
  1549.  
  1550.     // Don't display the first label
  1551.     function HideFirstTickLabel($aHide=false) {
  1552.     $this->show_first_label=$aHide;
  1553.     }
  1554.    
  1555.     // Hide the axis
  1556.     function Hide($aHide=true) {
  1557.     $this->hide=$aHide;
  1558.     }
  1559.  
  1560.     // Weight of axis
  1561.     function SetWeight($aWeight) {
  1562.     $this->weight = $aWeight;
  1563.     }
  1564.  
  1565.     // Axis color
  1566.     function SetColor($aColor,$aLabelColor=false) {
  1567.     $this->color = $aColor;
  1568.     if( !$aLabelColor ) $this->label_color = $aColor;
  1569.     else $this->label_color = $aLabelColor;
  1570.     }
  1571.    
  1572.     // Title on axis
  1573.     function SetTitle($aTitle,$aAdjustAlign="high") {
  1574.     $this->title->Set($aTitle);
  1575.     $this->title_adjust=$aAdjustAlign;
  1576.     }
  1577.    
  1578.     // Specify distance from the axis
  1579.     function SetTitleMargin($aMargin) {
  1580.     $this->title_margin=$aMargin;
  1581.     }
  1582.    
  1583.     // Specify text labels for the ticks. One label for each data point
  1584.     function SetTickLabels($aLabelArray) {
  1585.     $this->ticks_label = $aLabelArray;
  1586.     }
  1587.    
  1588.     // How far from the axis should the labels be drawn
  1589.     function SetTickLabelMargin($aMargin) {
  1590.     $this->tick_label_margin=$aMargin;
  1591.     }
  1592.    
  1593.     // Specify that every $step of the ticks should be displayed starting
  1594.     // at $start
  1595.     // DEPRECATED FUNCTION: USE SetTextTickInterval() INSTEAD
  1596.     function SetTextTicks($step,$start=0) {
  1597.     JpGraphError::Raise(" SetTextTicks() is deprecated. Use SetTextTickInterval() instead.");      
  1598.     }
  1599.  
  1600.     // Specify that every $step of the ticks should be displayed starting
  1601.     // at $start   
  1602.     function SetTextTickInterval($aStep,$aStart=0) {
  1603.     $this->scale->ticks->SetTextLabelStart($aStart);
  1604.     $this->tick_step=$aStep;
  1605.     }
  1606.    
  1607.     // Specify that every $step tick mark should have a label
  1608.     // should be displayed starting
  1609.     function SetTextLabelInterval($aStep) {
  1610.     if( $aStep < 1 )
  1611.         JpGraphError::Raise(" Text label interval must be specified >= 1.");
  1612.     $this->label_step=$aStep;
  1613.     }
  1614.    
  1615.    
  1616.     // Which side of the axis should the labels be on?
  1617.     function SetLabelPos($aSidePos) {
  1618.     $this->labelPos=$aSidePos;
  1619.     }
  1620.    
  1621.     // Set the font
  1622.     function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
  1623.     $this->font_family = $aFamily;
  1624.     $this->font_style = $aStyle;
  1625.     $this->font_size = $aSize;
  1626.     }
  1627.    
  1628.     // Which side of the axis should the axis title be?
  1629.     function SetTitleSide($aSideOfAxis) {
  1630.     $this->title_side = $aSideOfAxis;
  1631.     }
  1632.    
  1633.     // Stroke the axis.
  1634.     function Stroke($aOtherAxisScale) {    
  1635.     if( $this->hide ) return;      
  1636.     if( is_numeric($this->pos) ) {
  1637.         $pos=$aOtherAxisScale->Translate($this->pos);
  1638.     }
  1639.     else {  // Default to minimum of other scale if pos not set
  1640.         if( ($aOtherAxisScale->GetMinVal() >= 0 && $this->pos==false)|| $this->pos=="min" ) {
  1641.         $pos = $aOtherAxisScale->scale_abs[0];
  1642.         }
  1643.         elseif($this->pos == "max") {
  1644.         $pos = $aOtherAxisScale->scale_abs[1];
  1645.         }
  1646.         else { // If negative set x-axis at 0
  1647.         $this->pos=0;
  1648.         $pos=$aOtherAxisScale->Translate(0);
  1649.         }
  1650.     }  
  1651.     $this->img->SetLineWeight($this->weight);
  1652.     $this->img->SetColor($this->color);    
  1653.     $this->img->SetFont($this->font_family,$this->font_style,$this->font_size);
  1654.     if( $this->scale->type == "x" ) {
  1655.         $this->img->FilledRectangle($this->img->left_margin,$pos,
  1656.         $this->img->width-$this->img->right_margin,$pos+$this->weight-1);
  1657.         $y=$pos+$this->img->GetFontHeight()+$this->title_margin;   
  1658.         if( $this->title_adjust=="high" )
  1659.         $this->title->Pos($this->img->width-$this->img->right_margin,$y,"right","top");
  1660.         elseif($this->title_adjust=="middle")
  1661.         $this->title->Pos(($this->img->width-$this->img->left_margin-$this->img->right_margin)/2+$this->img->left_margin,$y,"center","top");
  1662.         elseif($this->title_adjust=="low")
  1663.         $this->title->Pos($this->img->left_margin,$y,"left","top");
  1664.     }
  1665.     elseif( $this->scale->type == "y" ) {
  1666.         // Add line weight to the height of the axis since
  1667.         // the x-axis could have a width>1 and we want the axis to fit nicely together.
  1668.         $this->img->FilledRectangle($pos-$this->weight+1,$this->img->top_margin,
  1669.         $pos,$this->img->height-$this->img->bottom_margin+$this->weight-1);
  1670.         $x=$pos ;
  1671.         if( $this->title_side == SIDE_LEFT ) {
  1672.         $x -= $this->title_margin;
  1673.         $halign="right";
  1674.         }
  1675.         else {
  1676.         $x += $this->title_margin;
  1677.         $halign="left";
  1678.         }
  1679.         if( $this->title_adjust=="high" )
  1680.         $this->title->Pos($x,$this->img->top_margin,$halign,"top");
  1681.         elseif($this->title_adjust=="middle" || $this->title_adjust=="center")  
  1682.         $this->title->Pos($x,($this->img->height-$this->img->top_margin-$this->img->bottom_margin)/2+$this->img->top_margin,$halign,"center");
  1683.         elseif($this->title_adjust=="low")
  1684.         $this->title->Pos($x,$this->img->height-$this->img->bottom_margin,$halign,"bottom");               
  1685.     }
  1686.     $this->scale->ticks->Stroke($this->img,$this->scale,$pos);
  1687.     $this->StrokeLabels($pos);
  1688.     $this->title->Stroke($this->img);
  1689.     }
  1690.  
  1691.     // Position for axis line on the "other" scale
  1692.     function SetPos($aPosOnOtherScale) {
  1693.     $this->pos=$aPosOnOtherScale;
  1694.     }
  1695.    
  1696.     // Specify the angle for the tick labels
  1697.     function SetLabelAngle($aAngle) {
  1698.     $this->label_angle = $aAngle;
  1699.     }
  1700.    
  1701. //---------------
  1702. // PRIVATE METHODS 
  1703.     // Draw all the tick labels on major tick marks
  1704.     function StrokeLabels($aPos,$aMinor=false) {
  1705.     $this->img->SetColor($this->label_color);
  1706.     $this->img->SetFont($this->font_family,$this->font_style,$this->font_size);
  1707.     $yoff=$this->img->GetFontHeight()/2;
  1708.  
  1709.     // Only draw labels at major tick marks
  1710.     $nbr = count($this->scale->ticks->maj_ticks_label);
  1711.  
  1712.     // We have the option to not-display the very first mark
  1713.     // (Usefull when the first label might interfere with another
  1714.     // axis.)
  1715.     if( $this->show_first_label ) $start=0;
  1716.     else $start=1;
  1717.            
  1718.     // Note. the $limit is only used for the x axis since we
  1719.     // might otherwise overshoot if the scale has been centered
  1720.     // This is due to us "loosing" the last tick mark if we center.
  1721.     //if( $this->scale->type=="x" )
  1722.     //  $limit=$this->img->width-$this->img->right_margin;
  1723.     //else
  1724.     //  $limit=$this->img->height;
  1725.                
  1726.     // $i holds the current index for the label
  1727.     $i=$start;
  1728.        
  1729.     // Now run through all labels making sure we don't overshoot the end
  1730.     // of the scale.   
  1731.     while( $i<$nbr ) {
  1732.         // $tpos holds the absolute text position for the label
  1733.         $tpos=$this->scale->ticks->maj_ticklabels_pos[$i];                                                         
  1734.         // we only draw every $label_step label
  1735.         if( ($i % $this->label_step)==0 ) {
  1736.                
  1737.                 // If the label has been specified use that and in other case
  1738.                 // just label the mark with the actual scale value
  1739.         $m=$this->scale->ticks->GetMajor();
  1740.                
  1741.                 // ticks_label has an entry for each data point
  1742.         if( isset($this->ticks_label[$i*$m]) )
  1743.             $label=$this->ticks_label[$i*$m];
  1744.         else
  1745.             $label=$this->scale->ticks->maj_ticks_label[$i];
  1746.                    
  1747.         if( $this->scale->type == "x" ) {
  1748.             if( $this->labelPos == SIDE_DOWN ) {
  1749.             if( $this->label_angle==0 || $this->label_angle==90 )
  1750.                 $this->img->SetTextAlign("center","top");
  1751.             else
  1752.                 $this->img->SetTextAlign("topanchor","top");
  1753.             $this->img->StrokeText($tpos,$aPos+$this->tick_label_margin,$label,$this->label_angle);
  1754.             }
  1755.             else {
  1756.             if( $this->label_angle==0 || $this->label_angle==90 )
  1757.                 $this->img->SetTextAlign("center","bottom");
  1758.             else
  1759.                 $this->img->SetTextAlign("topanchor","bottom");
  1760.             $this->img->StrokeText($tpos,$aPos-$this->tick_label_margin,$label,$this->label_angle);        
  1761.             }
  1762.         }
  1763.         else {
  1764.             // scale->type == "y"
  1765.             if( $this->label_angle!=0 )
  1766.             JpGraphError::Raise(" Labels at an angle are not supported on Y-axis");
  1767.             if( $this->labelPos == SIDE_LEFT ) { // To the left of y-axis                  
  1768.             $this->img->SetTextAlign("right","center");
  1769.             $this->img->StrokeText($aPos-$this->tick_label_margin,$tpos,$label);                   
  1770.             }
  1771.             else { // To the right of the y-axis
  1772.             $this->img->SetTextAlign("left","center");
  1773.             $this->img->StrokeText($aPos+$this->tick_label_margin,$tpos,$label);                   
  1774.             }
  1775.         }
  1776.         }
  1777.         ++$i;  
  1778.     }                              
  1779.     }          
  1780.  
  1781. } // Class
  1782.  
  1783. //===================================================
  1784. // CLASS Ticks
  1785. // Description: Abstract base class for drawing linear and logarithmic
  1786. // tick marks on axis
  1787. //===================================================
  1788. class Ticks {
  1789.     var $minor_abs_size=3, $major_abs_size=5;
  1790.     var $direction=1; // Should ticks be in(=1) the plot area or outside (=-1)?
  1791.     var $scale;
  1792.     var $is_set=false;
  1793.     var $precision=-1;
  1794.     var $supress_zerolabel=false,$supress_first=false;
  1795.     var $supress_last=false,$supress_tickmarks=false,$supress_minor_tickmarks=false;
  1796.     var $mincolor="",$majcolor="";
  1797.     var $weight=1;
  1798.     var $label_formatstr="";   // C-style format string to use for labels
  1799.     var $label_formfunc="";
  1800.  
  1801.  
  1802. //---------------
  1803. // CONSTRUCTOR
  1804.     function Ticks(&$aScale) {
  1805.     $this->scale=&$aScale;
  1806.     }
  1807.  
  1808. //---------------
  1809. // PUBLIC METHODS  
  1810.     // Set format string for automatic labels
  1811.     function SetLabelFormat($aFormatString) {
  1812.     $this->label_formatstr=$aFormatString;
  1813.     }
  1814.    
  1815.     function SetFormatCallback($aCallbackFuncName) {
  1816.     $this->label_formfunc = $aCallbackFuncName;
  1817.     }
  1818.    
  1819.     // Don't display the first zero label
  1820.     function SupressZeroLabel($aFlag=true) {
  1821.     $this->supress_zerolabel=$aFlag;
  1822.     }
  1823.    
  1824.     // Don't display minor tick marks
  1825.     function SupressMinorTickMarks($aHide=true) {
  1826.     $this->supress_minor_tickmarks=$aHide;
  1827.     }
  1828.    
  1829.     // Don't display major tick marks
  1830.     function SupressTickMarks($aHide=true) {
  1831.     $this->supress_tickmarks=$aHide;
  1832.     }
  1833.    
  1834.     // Hide the first tick mark
  1835.     function SupressFirst($aHide=true) {
  1836.     $this->supress_first=$aHide;
  1837.     }
  1838.    
  1839.     // Hide the last tick mark
  1840.     function SupressLast($aHide=true) {
  1841.     $this->supress_last=$aHide;
  1842.     }
  1843.  
  1844.     // Size (in pixels) of minor tick marks
  1845.     function GetMinTickAbsSize() {
  1846.     return $this->minor_abs_size;
  1847.     }
  1848.    
  1849.     // Size (in pixels) of major tick marks
  1850.     function GetMajTickAbsSize() {
  1851.     return $this->major_abs_size;      
  1852.     }
  1853.    
  1854.     // Have the ticks been specified
  1855.     function IsSpecified() {
  1856.     return $this->is_set;
  1857.     }
  1858.    
  1859.     // Set the distance between major and minor tick marks
  1860.     function Set($aMaj,$aMin) {
  1861.     // "Virtual method"
  1862.     // Should be implemented by the concrete subclass
  1863.     // if any action is wanted.
  1864.     }
  1865.    
  1866.     // Specify number of decimals in automtic labels
  1867.     // Deprecated from 1.4. Use SetFormatString() instead
  1868.     function SetPrecision($aPrecision) {
  1869.     $this->precision=$aPrecision;
  1870.     }
  1871.    
  1872.     // Which side of the axis should the ticks be on
  1873.     function SetDirection($aSide=SIDE_RIGHT) {
  1874.     $this->direction=$aSide;
  1875.     }
  1876.    
  1877.     // Set colors for major and minor tick marks
  1878.     function SetMarkColor($aMajorColor,$aMinorColor="") {
  1879.     $this->majcolor=$aMajorColor;
  1880.        
  1881.     // If not specified use same as major
  1882.     if( $aMinorColor=="" )
  1883.         $this->mincolor=$aMajorColor;
  1884.     else
  1885.         $this->mincolor=$aMinorColor;
  1886.     }
  1887.    
  1888.     function SetWeight($aWeight) {
  1889.     $this->weight=$aWeight;
  1890.     }
  1891.    
  1892. } // Class
  1893.  
  1894. //===================================================
  1895. // CLASS LinearTicks
  1896. // Description: Draw linear ticks on axis
  1897. //===================================================
  1898. class LinearTicks extends Ticks {
  1899.     var $minor_step=1, $major_step=2;
  1900.     var $xlabel_offset=0,$xtick_offset=0;
  1901.     var $label_offset=0; // What offset should the displayed label have
  1902.     // i.e should we display 0,1,2 or 1,2,3,4 or 2,3,4 etc
  1903.     var $text_label_start=0;
  1904. //---------------
  1905. // CONSTRUCTOR
  1906.     function LinearTicks() {
  1907.     // Empty
  1908.     }
  1909.  
  1910. //---------------
  1911. // PUBLIC METHODS  
  1912.    
  1913.    
  1914.     // Return major step size in world coordinates
  1915.     function GetMajor() {
  1916.     return $this->major_step;
  1917.     }
  1918.    
  1919.     // Return minor step size in world coordinates
  1920.     function GetMinor() {
  1921.     return $this->minor_step;
  1922.     }
  1923.    
  1924.     // Set Minor and Major ticks (in world coordinates)
  1925.     function Set($aMajStep,$aMinStep) {
  1926.     if( $aMajStep <= 0 || $aMinStep <= 0 ) {
  1927.         JpGraphError::Raise(" Minor or major step size is 0. Check that you haven't
  1928.                 got an accidental SetTextTicks(0) in your code.<p>
  1929.                 If this is not the case you might have stumbled upon a bug in JpGraph.
  1930.                 Please report this and if possible include the data that caused the
  1931.                 problem.");
  1932.     }
  1933.        
  1934.     $this->major_step=$aMajStep;
  1935.     $this->minor_step=$aMinStep;
  1936.     $this->is_set = true;
  1937.     }
  1938.  
  1939.     // Draw linear ticks
  1940.     function Stroke(&$img,&$scale,$pos) {
  1941.     $maj_step_abs = $scale->scale_factor*$this->major_step;    
  1942.     $min_step_abs = $scale->scale_factor*$this->minor_step;    
  1943.  
  1944.     if( $min_step_abs==0 || $maj_step_abs==0 )
  1945.         JpGraphError::Raise(" A plot has an illegal scale. This could for example be
  1946.             that you are trying to use text autoscaling to draw a line plot with only one point
  1947.             or similair abnormality (a line needs two points!).");
  1948.     $limit = $scale->scale_abs[1]; 
  1949.     $nbrmajticks=floor(1.000001*(($scale->GetMaxVal()-$scale->GetMinVal())/$this->major_step))+1;
  1950.     $first=0;
  1951.        
  1952.     // If precision hasn't been specified set it to a sensible value
  1953.     if( $this->precision==-1 ) {
  1954.         $t = log10($this->minor_step);
  1955.         if( $t > 0 )
  1956.         $precision = 0;
  1957.         else
  1958.         $precision = -floor($t);
  1959.     }
  1960.     else
  1961.         $precision = $this->precision;
  1962.            
  1963.     $img->SetLineWeight($this->weight);        
  1964.        
  1965.     // Handle ticks on X-axis
  1966.     if( $scale->type == "x" ) {
  1967.         // Draw the major tick marks
  1968.            
  1969.         $yu = $pos - $this->direction*$this->GetMajTickAbsSize();
  1970.            
  1971.         // TODO: Add logic to set label_offset for text labels
  1972.         $label = (float)$scale->GetMinVal()+$this->text_label_start+$this->label_offset;   
  1973.            
  1974.         $start_abs=$scale->scale_factor*$this->text_label_start;
  1975.            
  1976.         $nbrmajticks=ceil(($scale->GetMaxVal()-$scale->GetMinVal()-$this->text_label_start )/$this->major_step)+1;         
  1977.         for( $i=0; $label<=$scale->GetMaxVal()+$this->label_offset; ++$i ) {
  1978.         $x=$scale->scale_abs[0]+$start_abs+$i*$maj_step_abs+$this->xlabel_offset*$min_step_abs;            
  1979.         $this->maj_ticklabels_pos[$i]=ceil($x);            
  1980.                
  1981.                 // Apply format
  1982.         if( $this->label_formfunc != "" ) {
  1983.             $f=$this->label_formfunc;
  1984.             $l = $f($label);
  1985.         }  
  1986.         elseif( $this->label_formatstr != "" )
  1987.             $l = sprintf($this->label_formatstr,$label);
  1988.         else
  1989.             $l = sprintf("%01.".$precision."f",round($label,$precision));
  1990.                    
  1991.         if( ($this->supress_zerolabel && ($l + 0)==0) ||
  1992.             ($this->supress_first && $i==0) ||
  1993.             ($this->supress_last  && $i==$nbrmajticks-1) )
  1994.             $l="";                 
  1995.         $this->maj_ticks_label[$i]=$l;
  1996.         $label+=$this->major_step;
  1997.                
  1998.                 // The x-position of the tick marks can be different from the labels.
  1999.                 // Note that we record the tick position (not the label) so that the grid
  2000.                 // happen upon tick marks and not labels.
  2001.         $xtick=$scale->scale_abs[0]+$start_abs+$i*$maj_step_abs+$this->xtick_offset*$min_step_abs;
  2002.         $this->maj_ticks_pos[$i]=ceil($xtick);             
  2003.         if(!($this->xtick_offset > 0 && $i==$nbrmajticks-1) && !$this->supress_tickmarks) {
  2004.             if( $this->majcolor!="" ) $img->PushColor($this->majcolor);
  2005.             $img->Line($xtick,$pos,$xtick,$yu);
  2006.             if( $this->majcolor!="" ) $img->PopColor();
  2007.         }
  2008.         }
  2009.         // Draw the minor tick marks
  2010.            
  2011.         $yu = $pos - $this->direction*$this->GetMinTickAbsSize();
  2012.         $label = $scale->GetMinVal();                              
  2013.        
  2014.         $x=$scale->scale_abs[0];
  2015.         while( $x < $limit ) {     
  2016.             $this->ticks_pos[]=$x;
  2017.             $this->ticks_label[]=$label;
  2018.             $label+=$this->minor_step;
  2019.             if( !$this->supress_tickmarks && !$this->supress_minor_tickmarks)   {                      
  2020.                 if( $this->mincolor!="" ) $img->PushColor($this->mincolor);
  2021.                 $img->Line($x,$pos,$x,$yu);
  2022.                 if( $this->mincolor!="" ) $img->PopColor();
  2023.             }
  2024.             $x += $min_step_abs;
  2025.         }
  2026.     }
  2027.     elseif( $scale->type == "y" ) {
  2028.         // Draw the major tick marks
  2029.         $xr = $pos + $this->direction*$this->GetMajTickAbsSize();
  2030.         $label = $scale->GetMinVal();
  2031.         for( $i=0; $i<$nbrmajticks; ++$i) {
  2032.         $y=$scale->scale_abs[0]+$i*$maj_step_abs;              
  2033.                
  2034.                 // THe following two lines might seem to be unecessary but they are not!
  2035.                 // The reason being that for X-axis we separate the position of the labels
  2036.                 // and the tick marks which we don't do for the Y-axis.
  2037.                 // We therefore need to make sure both arrays are corcectly filled
  2038.                 // since Axis::StrokeLabels() uses the label positions and Grid::Stroke() uses
  2039.                 // the tick positions.
  2040.         $this->maj_ticklabels_pos[$i]=$y;
  2041.         $this->maj_ticks_pos[$i]=$y;
  2042.        
  2043.         if( $this->label_formfunc != "" ) {
  2044.             $f=$this->label_formfunc;
  2045.             $l = $f($label);
  2046.         }  
  2047.         elseif( $this->label_formatstr != "" )
  2048.             $l = sprintf($this->label_formatstr,$label);
  2049.         else
  2050.             $l = sprintf("%01.".$precision."f",round($label,$precision));
  2051.                        
  2052.         if( ($this->supress_zerolabel && ($l + 0)==0) ||
  2053.             ($this->supress_first && $i==0) ||
  2054.             ($this->supress_last  && $i==$nbrmajticks-1) )
  2055.             $l="";
  2056.         $this->maj_ticks_label[$i]=$l;
  2057.         $label+=$this->major_step; 
  2058.         if( !$this->supress_tickmarks ) {
  2059.             if( $this->majcolor!="" ) $img->PushColor($this->majcolor);
  2060.             $img->Line($pos,$y,$xr,$y);
  2061.             if( $this->majcolor!="" ) $img->PopColor();
  2062.         }
  2063.         }
  2064.         // Draw the minor tick marks
  2065.         $xr = $pos + $this->direction*$this->GetMinTickAbsSize();
  2066.         $label = $scale->GetMinVal();  
  2067.         for( $i=0,$y=$scale->scale_abs[0]; $y>=$limit; ) {
  2068.         $this->ticks_pos[$i]=$y;
  2069.         $this->ticks_label[$i]=$label;             
  2070.         $label+=$this->minor_step;             
  2071.         if( !$this->supress_tickmarks && !$this->supress_minor_tickmarks)   {
  2072.             if( $this->mincolor!="" ) $img->PushColor($this->mincolor);
  2073.             $img->Line($pos,$y,$xr,$y);
  2074.             if( $this->mincolor!="" ) $img->PopColor();
  2075.         }
  2076.         ++$i;
  2077.         $y=$scale->scale_abs[0]+$i*$min_step_abs;                              
  2078.         }  
  2079.     }  
  2080.     }
  2081. //---------------
  2082. // PRIVATE METHODS
  2083.     // Spoecify the offset of the displayed tick mark with the tick "space"
  2084.     // Legal values for $o is [0,1] used to adjust where the tick marks and label
  2085.     // should be positioned within the major tick-size
  2086.     // $lo specifies the label offset and $to specifies the tick offset
  2087.     // this comes in handy for example in bar graphs where we wont no offset for the
  2088.     // tick but have the labels displayed halfway under the bars.
  2089.     function SetXLabelOffset($aLabelOff,$aTickOff=-1) {
  2090.     $this->xlabel_offset=$aLabelOff;
  2091.     if( $aTickOff==-1 ) // Same as label offset
  2092.         $this->xtick_offset=$aLabelOff;
  2093.     else
  2094.         $this->xtick_offset=$aTickOff;
  2095.     if( $aLabelOff>0 )
  2096.         $this->SupressLast();   // The last tick wont fit
  2097.     }
  2098.  
  2099.     // Which tick label should we start with?
  2100.     function SetTextLabelStart($aTextLabelOff) {
  2101.     $this->text_label_start=$aTextLabelOff;
  2102.     }
  2103.    
  2104. } // Class
  2105.  
  2106. //===================================================
  2107. // CLASS LinearScale
  2108. // Description: Handle linear scaling between screen and world
  2109. //===================================================
  2110. class LinearScale {
  2111.     var $scale=array(0,0);
  2112.     var $scale_abs=array(0,0);
  2113.     var $scale_factor; // Scale factor between world and screen
  2114.     var $world_size;    // Plot area size in world coordinates
  2115.     var $world_abs_size; // Plot area size in pixels
  2116.     var $off; // Offset between image edge and plot area
  2117.     var $type; // is this x or y scale ?
  2118.     var $ticks=null; // Store ticks
  2119.     var $autoscale_min=false; // Forced minimum value, useful to let user force 0 as start and autoscale max
  2120.     var $gracetop=0,$gracebottom=0;
  2121.     var $intscale=false; // Restrict autoscale to integers
  2122. //---------------
  2123. // CONSTRUCTOR
  2124.     function LinearScale($aMin=0,$aMax=0,$aType="y") {
  2125.     assert($aType=="x" || $aType=="y" );
  2126.     assert($aMin<=$aMax);
  2127.        
  2128.     $this->type=$aType;
  2129.     $this->scale=array($aMin,$aMax);       
  2130.     $this->world_size=$aMax-$aMin; 
  2131.     $this->ticks = new LinearTicks();
  2132.     }
  2133.  
  2134. //---------------
  2135. // PUBLIC METHODS  
  2136.     // Second phase constructor
  2137.     function Init(&$aImg) {
  2138.     $this->InitConstants($aImg);   
  2139.     // We want image to notify us when the margins changes so we
  2140.     // can recalculate the constants.
  2141.     // PHP <= 4.04 BUGWARNING: IT IS IMPOSSIBLE TO DO THIS IN THE CONSTRUCTOR
  2142.     // SINCE (FOR SOME REASON) IT IS IMPOSSIBLE TO PASS A REFERENCE
  2143.     // TO 'this' INSTEAD IT WILL ADD AN ANONYMOUS COPY OF THIS OBJECT WHICH WILL
  2144.     // GET ALL THE NOTIFICATIONS. (This took a while to track down...)
  2145.        
  2146.     // Add us as an observer to class Image
  2147.     $aImg->AddObserver("InitConstants",$this);
  2148.     }
  2149.    
  2150.     // Check if scale is set or if we should autoscale
  2151.     // We should do this is either scale or ticks has not been set
  2152.     function IsSpecified() {
  2153.     if( $this->GetMinVal()==$this->GetMaxVal() ) {      // Scale not set
  2154.         return false;
  2155.     }
  2156.     return true;
  2157.     }
  2158.    
  2159.     // Set the minimum data value when the autoscaling is used.
  2160.     // Usefull if you want a fix minimum (like 0) but automtic maximum
  2161.     function SetAutoMin($aMin) {
  2162.     $this->autoscale_min=$aMin;
  2163.     }
  2164.    
  2165.     // Specify scale "grace" value (top and bottom)
  2166.     function SetGrace($aGraceTop,$aGraceBottom=0) {
  2167.     if( $aGraceTop<0 || $aGraceBottom < 0  )
  2168.         JpGraphError::Raise(" Grace must be larger then 0");
  2169.     $this->gracetop=$aGraceTop;
  2170.     $this->gracebottom=$aGraceBottom;
  2171.     }
  2172.    
  2173.     // Get the minimum value in the scale
  2174.     function GetMinVal() {
  2175.     return $this->scale[0];
  2176.     }
  2177.    
  2178.     // get maximum value for scale
  2179.     function GetMaxVal() {
  2180.     return $this->scale[1];
  2181.     }
  2182.        
  2183.     // Specify a new min/max value for sclae   
  2184.     function Update(&$aImg,$aMin,$aMax) {
  2185.     $this->scale=array($aMin,$aMax);       
  2186.     $this->world_size=$aMax-$aMin;     
  2187.     $this->InitConstants($aImg);                   
  2188.     }
  2189.    
  2190.     // Translate between world and screen
  2191.     function Translate($aCoord) {
  2192.     return $this->off+round(($aCoord*1.0 - $this->GetMinVal()) * $this->scale_factor,0);
  2193.     }
  2194.    
  2195.     // Relative translate (don't include offset) usefull when we just want
  2196.     // to know the relative position (in pixels) on the axis
  2197.     function RelTranslate($aCoord) {
  2198.     return round(($aCoord*1.0 - $this->GetMinVal()) * $this->scale_factor,0);
  2199.     }
  2200.    
  2201.     // Restrict autoscaling to only use integers
  2202.     function SetIntScale($aIntScale=true) {
  2203.     $this->intscale=$aIntScale;
  2204.     }
  2205.    
  2206.     // Calculate an integer autoscale
  2207.     function IntAutoScale(&$img,$min,$max,$maxsteps,$majend=true) {
  2208.     // Make sure limits are integers
  2209.     $min=floor($min);
  2210.     $max=ceil($max);
  2211.     if( abs($min-$max)==0 ) {
  2212.         --$min; ++$max;
  2213.     }
  2214.     $gracetop=ceil(($this->gracetop/100.0))*abs($max-$min);
  2215.     $gracebottom=ceil(($this->gracebottom/100.0))*abs($max-$min);
  2216.     if( is_numeric($this->autoscale_min) ) {
  2217.         $min = ceil($this->autoscale_min);
  2218.         if( abs($min-$max ) == 0 ) {
  2219.         ++$max;
  2220.         --$min;
  2221.         }
  2222.     }
  2223.        
  2224.     $min -= $gracebottom;
  2225.     $max += $gracetop;     
  2226.  
  2227.     // First get tickmarks as multiples of 1, 10, ...  
  2228.     list($num1steps,$adj1min,$adj1max,$maj1step) =
  2229.         $this->IntCalcTicks($maxsteps,$min,$max,1);
  2230.        
  2231.     // Then get tick marks as 2:s 2, 20, ...
  2232.     list($num2steps,$adj2min,$adj2max,$maj2step) =
  2233.         $this->IntCalcTicks($maxsteps,$min,$max,5);
  2234.        
  2235.     // Then get tickmarks as 5:s 5, 50, 500, ...
  2236.     list($num5steps,$adj5min,$adj5max,$maj5step) =
  2237.         $this->IntCalcTicks($maxsteps,$min,$max,2);    
  2238.  
  2239.     // Check to see whichof 1:s, 2:s or 5:s fit better with
  2240.     // the requested number of major ticks     
  2241.     $match1=abs($num1steps-$maxsteps);     
  2242.     $match2=abs($num2steps-$maxsteps);
  2243.     if( $maj5step > 1 )
  2244.         $match5=abs($num5steps-$maxsteps);
  2245.     else
  2246.         $match5=1000000;    // Dummy high value
  2247.        
  2248.     // Compare these three values and see which is the closest match
  2249.     // We use a 0.6 weight to gravitate towards multiple of 5:s
  2250.     if( $match1 < $match2 ) {
  2251.         if( $match1 < $match5 )
  2252.         $r=1;          
  2253.         else
  2254.         $r=3;
  2255.     }
  2256.     else {
  2257.         if( $match2 < $match5 )
  2258.         $r=2;          
  2259.         else
  2260.         $r=3;      
  2261.     }  
  2262.        
  2263.     // Minsteps are always the same as maxsteps for integer scale
  2264.     switch( $r ) {
  2265.         case 1:
  2266.         $this->Update($img,$adj1min,$adj1max);
  2267.         $this->ticks->Set($maj1step,$maj1step);
  2268.         break;         
  2269.         case 2:
  2270.         $this->Update($img,$adj2min,$adj2max);     
  2271.         $this->ticks->Set($maj2step,$maj2step);
  2272.         break;                                 
  2273.         case 3:
  2274.         $this->Update($img,$adj5min,$adj5max);
  2275.         $this->ticks->Set($maj5step,$maj2step);    
  2276.         break;         
  2277.     }      
  2278.     }
  2279.    
  2280.    
  2281.     // Calculate autoscale. Used if user hasn't given a scale and ticks
  2282.     // $maxsteps is the maximum number of major tickmarks allowed.
  2283.     function AutoScale(&$img,$min,$max,$maxsteps,$majend=true) {
  2284.     if( $this->intscale ) {
  2285.         $this->IntAutoScale($img,$min,$max,$maxsteps,$majend);
  2286.         return;
  2287.     }
  2288.     if( abs($min-$max) < 0.00001 ) {
  2289.         // We need some difference to be able to autoscale
  2290.         // make it 5% above and 5% below value
  2291.         if( $min==0 && $max==0 ) {      // Special case
  2292.         $min=-1; $max=1;
  2293.         }
  2294.         else {
  2295.         $delta = (abs($max)+abs($min))*0.005;
  2296.         $min -= $delta;
  2297.         $max += $delta;
  2298.         }
  2299.     }
  2300.        
  2301.     $gracetop=($this->gracetop/100.0)*abs($max-$min);
  2302.     $gracebottom=($this->gracebottom/100.0)*abs($max-$min);
  2303.     if( is_numeric($this->autoscale_min) ) {
  2304.         $min = $this->autoscale_min;
  2305.         if( abs($min-$max ) < 0.00001 )
  2306.         $max *= 1.05;
  2307.     }
  2308.     $min -= $gracebottom;
  2309.     $max += $gracetop;
  2310.  
  2311.     // First get tickmarks as multiples of 0.1, 1, 10, ... 
  2312.     list($num1steps,$adj1min,$adj1max,$min1step,$maj1step) =
  2313.         $this->CalcTicks($maxsteps,$min,$max,1,2);
  2314.        
  2315.     // Then get tick marks as 2:s 0.2, 2, 20, ...
  2316.     list($num2steps,$adj2min,$adj2max,$min2step,$maj2step) =
  2317.         $this->CalcTicks($maxsteps,$min,$max,5,2);
  2318.        
  2319.     // Then get tickmarks as 5:s 0.05, 0.5, 5, 50, ...
  2320.     list($num5steps,$adj5min,$adj5max,$min5step,$maj5step) =
  2321.         $this->CalcTicks($maxsteps,$min,$max,2,5);     
  2322.  
  2323.     // Check to see whichof 1:s, 2:s or 5:s fit better with
  2324.     // the requested number of major ticks     
  2325.     $match1=abs($num1steps-$maxsteps);     
  2326.     $match2=abs($num2steps-$maxsteps);
  2327.     $match5=abs($num5steps-$maxsteps);
  2328.     // Compare these three values and see which is the closest match
  2329.     // We use a 0.8 weight to gravitate towards multiple of 5:s
  2330.     $r=$this->MatchMin3($match1,$match2,$match5,0.8);
  2331.     switch( $r ) {
  2332.         case 1:
  2333.         $this->Update($img,$adj1min,$adj1max);
  2334.         $this->ticks->Set($maj1step,$min1step);
  2335.         break;         
  2336.         case 2:
  2337.         $this->Update($img,$adj2min,$adj2max);     
  2338.         $this->ticks->Set($maj2step,$min2step);
  2339.         break;                                 
  2340.         case 3:
  2341.         $this->Update($img,$adj5min,$adj5max);
  2342.         $this->ticks->Set($maj5step,$min5step);    
  2343.         break;         
  2344.     }
  2345.     }
  2346.  
  2347. //---------------
  2348. // PRIVATE METHODS 
  2349.  
  2350.     // This method recalculates all constants that are depending on the
  2351.     // margins in the image. If the margins in the image are changed
  2352.     // this method should be called for every scale that is registred with
  2353.     // that image. Should really be installed as an observer of that image.
  2354.     function InitConstants(&$img) {
  2355.     if( $this->type=="x" ) {
  2356.         $this->world_abs_size=$img->width - $img->left_margin - $img->right_margin;
  2357.         $this->off=$img->left_margin;
  2358.         $this->scale_factor = 0;
  2359.         if( $this->world_size > 0 )
  2360.         $this->scale_factor=$this->world_abs_size/($this->world_size*1.0);
  2361.     }
  2362.     else { // y scale
  2363.         $this->world_abs_size=$img->height - $img->top_margin - $img->bottom_margin;
  2364.         $this->off=$img->top_margin+$this->world_abs_size;         
  2365.         $this->scale_factor = 0;           
  2366.         if( $this->world_size > 0 )        
  2367.         $this->scale_factor=-$this->world_abs_size/($this->world_size*1.0);
  2368.     }
  2369.     $size = $this->world_size * $this->scale_factor;
  2370.     $this->scale_abs=array($this->off,$this->off + $size); 
  2371.     }
  2372.    
  2373.     // Initialize the conversion constants for this scale
  2374.     // This tries to pre-calculate as much as possible to speed up the
  2375.     // actual conversion (with Translate()) later on
  2376.     // $start   =scale start in absolute pixels (for x-scale this is an y-position
  2377.     //               and for an y-scale this is an x-position
  2378.     // $len         =absolute length in pixels of scale            
  2379.     function SetConstants($aStart,$aLen) {
  2380.     $this->world_abs_size=$aLen;
  2381.     $this->off=$aStart;
  2382.        
  2383.     if( $this->world_size<=0 ) {
  2384.         JpGraphError::Raise("<b>JpGraph Fatal Error</b>:<br>
  2385.          You have unfortunately stumbled upon a bug in JpGraph. <br>
  2386.          It seems like the scale range is ".$this->world_size." [for ".
  2387.          $this->type." scale] <br>
  2388.              Please report Bug #01 to jpgraph@aditus.nu and include the script
  2389.          that gave this error. <br>
  2390.          This problem could potentially be caused by trying to use \"illegal\"
  2391.          values in the input data arrays (like trying to send in strings or
  2392.          only NULL values) which causes the autoscaling to fail.");
  2393.     }
  2394.        
  2395.     // scale_factor = number of pixels per world unit
  2396.     $this->scale_factor=$this->world_abs_size/($this->world_size*1.0);
  2397.        
  2398.     // scale_abs = start and end points of scale in absolute pixels
  2399.     $this->scale_abs=array($this->off,$this->off+$this->world_size*$this->scale_factor);       
  2400.     }
  2401.    
  2402.    
  2403.     // Calculate number of ticks steps with a specific division
  2404.     // $a is the divisor of 10**x to generate the first maj tick intervall
  2405.     // $a=1, $b=2 give major ticks with multiple of 10, ...,0.1,1,10,...
  2406.     // $a=5, $b=2 give major ticks with multiple of 2:s ...,0.2,2,20,...
  2407.     // $a=2, $b=5 give major ticks with multiple of 5:s ...,0.5,5,50,...
  2408.     // We return a vector of
  2409.     //  [$numsteps,$adjmin,$adjmax,$minstep,$majstep]
  2410.     // If $majend==true then the first and last marks on the axis will be major
  2411.     // labeled tick marks otherwise it will be adjusted to the closest min tick mark
  2412.     function CalcTicks($maxsteps,$min,$max,$a,$b,$majend=true) {
  2413.     $diff=$max-$min;
  2414.     if( $diff==0 )
  2415.         $ld=0;
  2416.     else
  2417.         $ld=floor(log10($diff));
  2418.        
  2419.     // Gravitate min towards zero if we are close      
  2420.     if( $min>0 && $min < pow(10,$ld) ) $min=0;
  2421.        
  2422.     $majstep=pow(10,$ld-1)/$a;
  2423.     $minstep=$majstep/$b;
  2424.     $adjmax=ceil($max/$minstep)*$minstep;
  2425.     $adjmin=floor($min/$minstep)*$minstep; 
  2426.     $adjdiff = $adjmax-$adjmin;
  2427.     $numsteps=$adjdiff/$majstep;
  2428.     while( $numsteps>$maxsteps ) {
  2429.         $majstep=pow(10,$ld)/$a;
  2430.         $numsteps=$adjdiff/$majstep;
  2431.         ++$ld;
  2432.     }
  2433.        
  2434.     $minstep=$majstep/$b;
  2435.     $adjmin=floor($min/$minstep)*$minstep; 
  2436.     $adjdiff = $adjmax-$adjmin;    
  2437.     if( $majend ) {
  2438.         $adjmin = floor($min/$majstep)*$majstep;   
  2439.         $adjdiff = $adjmax-$adjmin;    
  2440.         $adjmax = ceil($adjdiff/$majstep)*$majstep+$adjmin;
  2441.     }
  2442.     else
  2443.         $adjmax=ceil($max/$minstep)*$minstep;
  2444.            
  2445.     return array($numsteps,$adjmin,$adjmax,$minstep,$majstep);
  2446.     }
  2447.    
  2448.     function IntCalcTicks($maxsteps,$min,$max,$a,$majend=true) {
  2449.     $diff=$max-$min;
  2450.     if( $diff==0 )
  2451.         $ld=0;
  2452.     else
  2453.         $ld=floor(log10($diff));
  2454.        
  2455.     // Gravitate min towards zero if we are close      
  2456.     if( $min>0 && $min < pow(10,$ld) ) $min=0;
  2457.        
  2458.     $majstep=pow(10,$ld-1)/$a;
  2459.     $adjmax=ceil($max/$majstep)*$majstep;
  2460.     $adjmin=floor($min/$majstep)*$majstep; 
  2461.     $adjdiff = $adjmax-$adjmin;
  2462.     $numsteps=$adjdiff/$majstep;
  2463.     while( $numsteps>$maxsteps ) {
  2464.         $majstep=pow(10,$ld)/$a;
  2465.         $numsteps=$adjdiff/$majstep;
  2466.         ++$ld;
  2467.     }
  2468.        
  2469.     $adjmin=floor($min/$majstep)*$majstep; 
  2470.     $adjdiff = $adjmax-$adjmin;    
  2471.     if( $majend ) {
  2472.         $adjmin = floor($min/$majstep)*$majstep;   
  2473.         $adjdiff = $adjmax-$adjmin;    
  2474.         $adjmax = ceil($adjdiff/$majstep)*$majstep+$adjmin;
  2475.     }
  2476.     else
  2477.         $adjmax=ceil($max/$majstep)*$majstep;
  2478.            
  2479.     return array($numsteps,$adjmin,$adjmax,$majstep);      
  2480.     }
  2481.  
  2482.  
  2483.    
  2484.     // Determine the minimum of three values witha  weight for last value
  2485.     function MatchMin3($a,$b,$c,$weight) {
  2486.     if( $a < $b ) {
  2487.         if( $a < ($c*$weight) )
  2488.         return 1; // $a smallest
  2489.         else
  2490.         return 3; // $c smallest
  2491.     }
  2492.     elseif( $b < ($c*$weight) )
  2493.         return 2; // $b smallest
  2494.     return 3; // $c smallest
  2495.     }
  2496. } // Class
  2497.  
  2498. //===================================================
  2499. // CLASS RGB
  2500. // Description: Color definitions as RGB triples
  2501. //===================================================
  2502. class RGB {
  2503.     var $rgb_table;
  2504.     var $img;
  2505.     function RGB(&$aImg) {
  2506.     $this->img = $aImg;
  2507.        
  2508.     // Conversion array between color names and RGB
  2509.     $this->rgb_table = array(
  2510.         "aqua"=> array(0,255,255),     
  2511.         "lime"=> array(0,255,0),       
  2512.         "teal"=> array(0,128,128),
  2513.         "whitesmoke"=>array(245,245,245),
  2514.         "gainsboro"=>array(220,220,220),
  2515.         "oldlace"=>array(253,245,230),
  2516.         "linen"=>array(250,240,230),
  2517.         "antiquewhite"=>array(250,235,215),
  2518.         "papayawhip"=>array(255,239,213),
  2519.         "blanchedalmond"=>array(255,235,205),
  2520.         "bisque"=>array(255,228,196),
  2521.         "peachpuff"=>array(255,218,185),
  2522.         "navajowhite"=>array(255,222,173),
  2523.         "moccasin"=>array(255,228,181),
  2524.         "cornsilk"=>array(255,248,220),
  2525.         "ivory"=>array(255,255,240),
  2526.         "lemonchiffon"=>array(255,250,205),
  2527.         "seashell"=>array(255,245,238),
  2528.         "mintcream"=>array(245,255,250),
  2529.         "azure"=>array(240,255,255),
  2530.         "aliceblue"=>array(240,248,255),
  2531.         "lavender"=>array(230,230,250),
  2532.         "lavenderblush"=>array(255,240,245),
  2533.         "mistyrose"=>array(255,228,225),
  2534.         "white"=>array(255,255,255),
  2535.         "black"=>array(0,0,0),
  2536.         "darkslategray"=>array(47,79,79),
  2537.         "dimgray"=>array(105,105,105),
  2538.         "slategray"=>array(112,128,144),
  2539.         "lightslategray"=>array(119,136,153),
  2540.         "gray"=>array(190,190,190),
  2541.         "lightgray"=>array(211,211,211),
  2542.         "midnightblue"=>array(25,25,112),
  2543.         "navy"=>array(0,0,128),
  2544.         "cornflowerblue"=>array(100,149,237),
  2545.         "darkslateblue"=>array(72,61,139),
  2546.         "slateblue"=>array(106,90,205),
  2547.         "mediumslateblue"=>array(123,104,238),
  2548.         "lightslateblue"=>array(132,112,255),
  2549.         "mediumblue"=>array(0,0,205),
  2550.         "royalblue"=>array(65,105,225),
  2551.         "blue"=>array(0,0,255),
  2552.         "dodgerblue"=>array(30,144,255),
  2553.         "deepskyblue"=>array(0,191,255),
  2554.         "skyblue"=>array(135,206,235),
  2555.         "lightskyblue"=>array(135,206,250),
  2556.         "steelblue"=>array(70,130,180),
  2557.         "lightred"=>array(211,167,168),
  2558.         "lightsteelblue"=>array(176,196,222),
  2559.         "lightblue"=>array(173,216,230),
  2560.         "powderblue"=>array(176,224,230),
  2561.         "paleturquoise"=>array(175,238,238),
  2562.         "darkturquoise"=>array(0,206,209),
  2563.         "mediumturquoise"=>array(72,209,204),
  2564.         "turquoise"=>array(64,224,208),
  2565.         "cyan"=>array(0,255,255),
  2566.         "lightcyan"=>array(224,255,255),
  2567.         "cadetblue"=>array(95,158,160),
  2568.         "mediumaquamarine"=>array(102,205,170),
  2569.         "aquamarine"=>array(127,255,212),
  2570.         "darkgreen"=>array(0,100,0),
  2571.         "darkolivegreen"=>array(85,107,47),
  2572.         "darkseagreen"=>array(143,188,143),
  2573.         "seagreen"=>array(46,139,87),
  2574.         "mediumseagreen"=>array(60,179,113),
  2575.         "lightseagreen"=>array(32,178,170),
  2576.         "palegreen"=>array(152,251,152),
  2577.         "springgreen"=>array(0,255,127),
  2578.         "lawngreen"=>array(124,252,0),
  2579.         "green"=>array(0,255,0),
  2580.         "chartreuse"=>array(127,255,0),
  2581.         "mediumspringgreen"=>array(0,250,154),
  2582.         "greenyellow"=>array(173,255,47),
  2583.         "limegreen"=>array(50,205,50),
  2584.         "yellowgreen"=>array(154,205,50),
  2585.         "forestgreen"=>array(34,139,34),
  2586.         "olivedrab"=>array(107,142,35),
  2587.         "darkkhaki"=>array(189,183,107),
  2588.         "khaki"=>array(240,230,140),
  2589.         "palegoldenrod"=>array(238,232,170),
  2590.         "lightgoldenrodyellow"=>array(250,250,210),
  2591.         "lightyellow"=>array(255,255,200),
  2592.         "yellow"=>array(255,255,0),
  2593.         "gold"=>array(255,215,0),
  2594.         "lightgoldenrod"=>array(238,221,130),
  2595.         "goldenrod"=>array(218,165,32),
  2596.         "darkgoldenrod"=>array(184,134,11),
  2597.         "rosybrown"=>array(188,143,143),
  2598.         "indianred"=>array(205,92,92),
  2599.         "saddlebrown"=>array(139,69,19),
  2600.         "sienna"=>array(160,82,45),
  2601.         "peru"=>array(205,133,63),
  2602.         "burlywood"=>array(222,184,135),
  2603.         "beige"=>array(245,245,220),
  2604.         "wheat"=>array(245,222,179),
  2605.         "sandybrown"=>array(244,164,96),
  2606.         "tan"=>array(210,180,140),
  2607.         "chocolate"=>array(210,105,30),
  2608.         "firebrick"=>array(178,34,34),
  2609.         "brown"=>array(165,42,42),
  2610.         "darksalmon"=>array(233,150,122),
  2611.         "salmon"=>array(250,128,114),
  2612.         "lightsalmon"=>array(255,160,122),
  2613.         "orange"=>array(255,165,0),
  2614.         "darkorange"=>array(255,140,0),
  2615.         "coral"=>array(255,127,80),
  2616.         "lightcoral"=>array(240,128,128),
  2617.         "tomato"=>array(255,99,71),
  2618.         "orangered"=>array(255,69,0),
  2619.         "red"=>array(255,0,0),
  2620.         "hotpink"=>array(255,105,180),
  2621.         "deeppink"=>array(255,20,147),
  2622.         "pink"=>array(255,192,203),
  2623.         "lightpink"=>array(255,182,193),
  2624.         "palevioletred"=>array(219,112,147),
  2625.         "maroon"=>array(176,48,96),
  2626.         "mediumvioletred"=>array(199,21,133),
  2627.         "violetred"=>array(208,32,144),
  2628.         "magenta"=>array(255,0,255),
  2629.         "violet"=>array(238,130,238),
  2630.         "plum"=>array(221,160,221),
  2631.         "orchid"=>array(218,112,214),
  2632.         "mediumorchid"=>array(186,85,211),
  2633.         "darkorchid"=>array(153,50,204),
  2634.         "darkviolet"=>array(148,0,211),
  2635.         "blueviolet"=>array(138,43,226),
  2636.         "purple"=>array(160,32,240),
  2637.         "mediumpurple"=>array(147,112,219),
  2638.         "thistle"=>array(216,191,216),
  2639.         "snow1"=>array(255,250,250),
  2640.         "snow2"=>array(238,233,233),
  2641.         "snow3"=>array(205,201,201),
  2642.         "snow4"=>array(139,137,137),
  2643.         "seashell1"=>array(255,245,238),
  2644.         "seashell2"=>array(238,229,222),
  2645.         "seashell3"=>array(205,197,191),
  2646.         "seashell4"=>array(139,134,130),
  2647.         "AntiqueWhite1"=>array(255,239,219),
  2648.         "AntiqueWhite2"=>array(238,223,204),
  2649.         "AntiqueWhite3"=>array(205,192,176),
  2650.         "AntiqueWhite4"=>array(139,131,120),
  2651.         "bisque1"=>array(255,228,196),
  2652.         "bisque2"=>array(238,213,183),
  2653.         "bisque3"=>array(205,183,158),
  2654.         "bisque4"=>array(139,125,107),
  2655.         "peachPuff1"=>array(255,218,185),
  2656.         "peachpuff2"=>array(238,203,173),
  2657.         "peachpuff3"=>array(205,175,149),
  2658.         "peachpuff4"=>array(139,119,101),
  2659.         "navajowhite1"=>array(255,222,173),
  2660.         "navajowhite2"=>array(238,207,161),
  2661.         "navajowhite3"=>array(205,179,139),
  2662.         "navajowhite4"=>array(139,121,94),
  2663.         "lemonchiffon1"=>array(255,250,205),
  2664.         "lemonchiffon2"=>array(238,233,191),
  2665.         "lemonchiffon3"=>array(205,201,165),
  2666.         "lemonchiffon4"=>array(139,137,112),
  2667.         "ivory1"=>array(255,255,240),
  2668.         "ivory2"=>array(238,238,224),
  2669.         "ivory3"=>array(205,205,193),
  2670.         "ivory4"=>array(139,139,131),
  2671.         "honeydew"=>array(193,205,193),
  2672.         "lavenderblush1"=>array(255,240,245),
  2673.         "lavenderblush2"=>array(238,224,229),
  2674.         "lavenderblush3"=>array(205,193,197),
  2675.         "lavenderblush4"=>array(139,131,134),
  2676.         "mistyrose1"=>array(255,228,225),
  2677.         "mistyrose2"=>array(238,213,210),
  2678.         "mistyrose3"=>array(205,183,181),
  2679.         "mistyrose4"=>array(139,125,123),
  2680.         "azure1"=>array(240,255,255),
  2681.         "azure2"=>array(224,238,238),
  2682.         "azure3"=>array(193,205,205),
  2683.         "azure4"=>array(131,139,139),
  2684.         "slateblue1"=>array(131,111,255),
  2685.         "slateblue2"=>array(122,103,238),
  2686.         "slateblue3"=>array(105,89,205),
  2687.         "slateblue4"=>array(71,60,139),
  2688.         "royalblue1"=>array(72,118,255),
  2689.         "royalblue2"=>array(67,110,238),
  2690.         "royalblue3"=>array(58,95,205),
  2691.         "royalblue4"=>array(39,64,139),
  2692.         "dodgerblue1"=>array(30,144,255),
  2693.         "dodgerblue2"=>array(28,134,238),
  2694.         "dodgerblue3"=>array(24,116,205),
  2695.         "dodgerblue4"=>array(16,78,139),
  2696.         "steelblue1"=>array(99,184,255),
  2697.         "steelblue2"=>array(92,172,238),
  2698.         "steelblue3"=>array(79,148,205),
  2699.         "steelblue4"=>array(54,100,139),
  2700.         "deepskyblue1"=>array(0,191,255),
  2701.         "deepskyblue2"=>array(0,178,238),
  2702.         "deepskyblue3"=>array(0,154,205),
  2703.         "deepskyblue4"=>array(0,104,139),
  2704.         "skyblue1"=>array(135,206,255),
  2705.         "skyblue2"=>array(126,192,238),
  2706.         "skyblue3"=>array(108,166,205),
  2707.         "skyblue4"=>array(74,112,139),
  2708.         "lightskyblue1"=>array(176,226,255),
  2709.         "lightskyblue2"=>array(164,211,238),
  2710.         "lightskyblue3"=>array(141,182,205),
  2711.         "lightskyblue4"=>array(96,123,139),
  2712.         "slategray1"=>array(198,226,255),
  2713.         "slategray2"=>array(185,211,238),
  2714.         "slategray3"=>array(159,182,205),
  2715.         "slategray4"=>array(108,123,139),
  2716.         "lightsteelblue1"=>array(202,225,255),
  2717.         "lightsteelblue2"=>array(188,210,238),
  2718.         "lightsteelblue3"=>array(162,181,205),
  2719.         "lightsteelblue4"=>array(110,123,139),
  2720.         "lightblue1"=>array(191,239,255),
  2721.         "lightblue2"=>array(178,223,238),
  2722.         "lightblue3"=>array(154,192,205),
  2723.         "lightblue4"=>array(104,131,139),
  2724.         "lightcyan1"=>array(224,255,255),
  2725.         "lightcyan2"=>array(209,238,238),
  2726.         "lightcyan3"=>array(180,205,205),
  2727.         "lightcyan4"=>array(122,139,139),
  2728.         "paleturquoise1"=>array(187,255,255),
  2729.         "paleturquoise2"=>array(174,238,238),
  2730.         "paleturquoise3"=>array(150,205,205),
  2731.         "paleturquoise4"=>array(102,139,139),
  2732.         "cadetblue1"=>array(152,245,255),
  2733.         "cadetblue2"=>array(142,229,238),
  2734.         "cadetblue3"=>array(122,197,205),
  2735.         "cadetblue4"=>array(83,134,139),
  2736.         "turquoise1"=>array(0,245,255),
  2737.         "turquoise2"=>array(0,229,238),
  2738.         "turquoise3"=>array(0,197,205),
  2739.         "turquoise4"=>array(0,134,139),
  2740.         "cyan1"=>array(0,255,255),
  2741.         "cyan2"=>array(0,238,238),
  2742.         "cyan3"=>array(0,205,205),
  2743.         "cyan4"=>array(0,139,139),
  2744.         "darkslategray1"=>array(151,255,255),
  2745.         "darkslategray2"=>array(141,238,238),
  2746.         "darkslategray3"=>array(121,205,205),
  2747.         "darkslategray4"=>array(82,139,139),
  2748.         "aquamarine1"=>array(127,255,212),
  2749.         "aquamarine2"=>array(118,238,198),
  2750.         "aquamarine3"=>array(102,205,170),
  2751.         "aquamarine4"=>array(69,139,116),
  2752.         "darkseagreen1"=>array(193,255,193),
  2753.         "darkseagreen2"=>array(180,238,180),
  2754.         "darkseagreen3"=>array(155,205,155),
  2755.         "darkseagreen4"=>array(105,139,105),
  2756.         "seagreen1"=>array(84,255,159),
  2757.         "seagreen2"=>array(78,238,148),
  2758.         "seagreen3"=>array(67,205,128),
  2759.         "seagreen4"=>array(46,139,87),
  2760.         "palegreen1"=>array(154,255,154),
  2761.         "palegreen2"=>array(144,238,144),
  2762.         "palegreen3"=>array(124,205,124),
  2763.         "palegreen4"=>array(84,139,84),
  2764.         "springgreen1"=>array(0,255,127),
  2765.         "springgreen2"=>array(0,238,118),
  2766.         "springgreen3"=>array(0,205,102),
  2767.         "springgreen4"=>array(0,139,69),
  2768.         "chartreuse1"=>array(127,255,0),
  2769.         "chartreuse2"=>array(118,238,0),
  2770.         "chartreuse3"=>array(102,205,0),
  2771.         "chartreuse4"=>array(69,139,0),
  2772.         "olivedrab1"=>array(192,255,62),
  2773.         "olivedrab2"=>array(179,238,58),
  2774.         "olivedrab3"=>array(154,205,50),
  2775.         "olivedrab4"=>array(105,139,34),
  2776.         "darkolivegreen1"=>array(202,255,112),
  2777.         "darkolivegreen2"=>array(188,238,104),
  2778.         "darkolivegreen3"=>array(162,205,90),
  2779.         "darkolivegreen4"=>array(110,139,61),
  2780.         "khaki1"=>array(255,246,143),
  2781.         "khaki2"=>array(238,230,133),
  2782.         "khaki3"=>array(205,198,115),
  2783.         "khaki4"=>array(139,134,78),
  2784.         "lightgoldenrod1"=>array(255,236,139),
  2785.         "lightgoldenrod2"=>array(238,220,130),
  2786.         "lightgoldenrod3"=>array(205,190,112),
  2787.         "lightgoldenrod4"=>array(139,129,76),
  2788.         "yellow1"=>array(255,255,0),
  2789.         "yellow2"=>array(238,238,0),
  2790.         "yellow3"=>array(205,205,0),
  2791.         "yellow4"=>array(139,139,0),
  2792.         "gold1"=>array(255,215,0),
  2793.         "gold2"=>array(238,201,0),
  2794.         "gold3"=>array(205,173,0),
  2795.         "gold4"=>array(139,117,0),
  2796.         "goldenrod1"=>array(255,193,37),
  2797.         "goldenrod2"=>array(238,180,34),
  2798.         "goldenrod3"=>array(205,155,29),
  2799.         "goldenrod4"=>array(139,105,20),
  2800.         "darkgoldenrod1"=>array(255,185,15),
  2801.         "darkgoldenrod2"=>array(238,173,14),
  2802.         "darkgoldenrod3"=>array(205,149,12),
  2803.         "darkgoldenrod4"=>array(139,101,8),
  2804.         "rosybrown1"=>array(255,193,193),
  2805.         "rosybrown2"=>array(238,180,180),
  2806.         "rosybrown3"=>array(205,155,155),
  2807.         "rosybrown4"=>array(139,105,105),
  2808.         "indianred1"=>array(255,106,106),
  2809.         "indianred2"=>array(238,99,99),
  2810.         "indianred3"=>array(205,85,85),
  2811.         "indianred4"=>array(139,58,58),
  2812.         "sienna1"=>array(255,130,71),
  2813.         "sienna2"=>array(238,121,66),
  2814.         "sienna3"=>array(205,104,57),
  2815.         "sienna4"=>array(139,71,38),
  2816.         "burlywood1"=>array(255,211,155),
  2817.         "burlywood2"=>array(238,197,145),
  2818.         "burlywood3"=>array(205,170,125),
  2819.         "burlywood4"=>array(139,115,85),
  2820.         "wheat1"=>array(255,231,186),
  2821.         "wheat2"=>array(238,216,174),
  2822.         "wheat3"=>array(205,186,150),
  2823.         "wheat4"=>array(139,126,102),
  2824.         "tan1"=>array(255,165,79),
  2825.         "tan2"=>array(238,154,73),
  2826.         "tan3"=>array(205,133,63),
  2827.         "tan4"=>array(139,90,43),
  2828.         "chocolate1"=>array(255,127,36),
  2829.         "chocolate2"=>array(238,118,33),
  2830.         "chocolate3"=>array(205,102,29),
  2831.         "chocolate4"=>array(139,69,19),
  2832.         "firebrick1"=>array(255,48,48),
  2833.         "firebrick2"=>array(238,44,44),
  2834.         "firebrick3"=>array(205,38,38),
  2835.         "firebrick4"=>array(139,26,26),
  2836.         "brown1"=>array(255,64,64),
  2837.         "brown2"=>array(238,59,59),
  2838.         "brown3"=>array(205,51,51),
  2839.         "brown4"=>array(139,35,35),
  2840.         "salmon1"=>array(255,140,105),
  2841.         "salmon2"=>array(238,130,98),
  2842.         "salmon3"=>array(205,112,84),
  2843.         "salmon4"=>array(139,76,57),
  2844.         "lightsalmon1"=>array(255,160,122),
  2845.         "lightsalmon2"=>array(238,149,114),
  2846.         "lightsalmon3"=>array(205,129,98),
  2847.         "lightsalmon4"=>array(139,87,66),
  2848.         "orange1"=>array(255,165,0),
  2849.         "orange2"=>array(238,154,0),
  2850.         "orange3"=>array(205,133,0),
  2851.         "orange4"=>array(139,90,0),
  2852.         "darkorange1"=>array(255,127,0),
  2853.         "darkorange2"=>array(238,118,0),
  2854.         "darkorange3"=>array(205,102,0),
  2855.         "darkorange4"=>array(139,69,0),
  2856.         "coral1"=>array(255,114,86),
  2857.         "coral2"=>array(238,106,80),
  2858.         "coral3"=>array(205,91,69),
  2859.         "coral4"=>array(139,62,47),
  2860.         "tomato1"=>array(255,99,71),
  2861.         "tomato2"=>array(238,92,66),
  2862.         "tomato3"=>array(205,79,57),
  2863.         "tomato4"=>array(139,54,38),
  2864.         "orangered1"=>array(255,69,0),
  2865.         "orangered2"=>array(238,64,0),
  2866.         "orangered3"=>array(205,55,0),
  2867.         "orangered4"=>array(139,37,0),
  2868.         "deeppink1"=>array(255,20,147),
  2869.         "deeppink2"=>array(238,18,137),
  2870.         "deeppink3"=>array(205,16,118),
  2871.         "deeppink4"=>array(139,10,80),
  2872.         "hotpink1"=>array(255,110,180),
  2873.         "hotpink2"=>array(238,106,167),
  2874.         "hotpink3"=>array(205,96,144),
  2875.         "hotpink4"=>array(139,58,98),
  2876.         "pink1"=>array(255,181,197),
  2877.         "pink2"=>array(238,169,184),
  2878.         "pink3"=>array(205,145,158),
  2879.         "pink4"=>array(139,99,108),
  2880.         "lightpink1"=>array(255,174,185),
  2881.         "lightpink2"=>array(238,162,173),
  2882.         "lightpink3"=>array(205,140,149),
  2883.         "lightpink4"=>array(139,95,101),
  2884.         "palevioletred1"=>array(255,130,171),
  2885.         "palevioletred2"=>array(238,121,159),
  2886.         "palevioletred3"=>array(205,104,137),
  2887.         "palevioletred4"=>array(139,71,93),
  2888.         "maroon1"=>array(255,52,179),
  2889.         "maroon2"=>array(238,48,167),
  2890.         "maroon3"=>array(205,41,144),
  2891.         "maroon4"=>array(139,28,98),
  2892.         "violetred1"=>array(255,62,150),
  2893.         "violetred2"=>array(238,58,140),
  2894.         "violetred3"=>array(205,50,120),
  2895.         "violetred4"=>array(139,34,82),
  2896.         "magenta1"=>array(255,0,255),
  2897.         "magenta2"=>array(238,0,238),
  2898.         "magenta3"=>array(205,0,205),
  2899.         "magenta4"=>array(139,0,139),
  2900.         "mediumred"=>array(140,34,34),        
  2901.         "orchid1"=>array(255,131,250),
  2902.         "orchid2"=>array(238,122,233),
  2903.         "orchid3"=>array(205,105,201),
  2904.         "orchid4"=>array(139,71,137),
  2905.         "plum1"=>array(255,187,255),
  2906.         "plum2"=>array(238,174,238),
  2907.         "plum3"=>array(205,150,205),
  2908.         "plum4"=>array(139,102,139),
  2909.         "mediumorchid1"=>array(224,102,255),
  2910.         "mediumorchid2"=>array(209,95,238),
  2911.         "mediumorchid3"=>array(180,82,205),
  2912.         "mediumorchid4"=>array(122,55,139),
  2913.         "darkorchid1"=>array(191,62,255),
  2914.         "darkorchid2"=>array(178,58,238),
  2915.         "darkorchid3"=>array(154,50,205),
  2916.         "darkorchid4"=>array(104,34,139),
  2917.         "purple1"=>array(155,48,255),
  2918.         "purple2"=>array(145,44,238),
  2919.         "purple3"=>array(125,38,205),
  2920.         "purple4"=>array(85,26,139),
  2921.         "mediumpurple1"=>array(171,130,255),
  2922.         "mediumpurple2"=>array(159,121,238),
  2923.         "mediumpurple3"=>array(137,104,205),
  2924.         "mediumpurple4"=>array(93,71,139),
  2925.         "thistle1"=>array(255,225,255),
  2926.         "thistle2"=>array(238,210,238),
  2927.         "thistle3"=>array(205,181,205),
  2928.         "thistle4"=>array(139,123,139),
  2929.         "gray1"=>array(10,10,10),
  2930.         "gray2"=>array(40,40,30),
  2931.         "gray3"=>array(70,70,70),
  2932.         "gray4"=>array(100,100,100),
  2933.         "gray5"=>array(130,130,130),
  2934.         "gray6"=>array(160,160,160),
  2935.         "gray7"=>array(190,190,190),
  2936.         "gray8"=>array(210,210,210),
  2937.         "gray9"=>array(240,240,240),
  2938.         "darkgray"=>array(100,100,100),
  2939.         "darkblue"=>array(0,0,139),
  2940.         "darkcyan"=>array(0,139,139),
  2941.         "darkmagenta"=>array(139,0,139),
  2942.         "darkred"=>array(139,0,0),
  2943.         "silver"=>array(192, 192, 192),
  2944.         "eggplant"=>array(144,176,168),
  2945.         "lightgreen"=>array(144,238,144));     
  2946.     }
  2947. //----------------
  2948. // PUBLIC METHODS
  2949.     // Colors can be specified as either
  2950.     // 1. #xxxxxx           HTML style
  2951.     // 2. "colorname"   as a named color
  2952.     // 3. array(r,g,b)  RGB triple
  2953.     // This function translates this to a native RGB format and returns an
  2954.     // RGB triple.
  2955.     function Color($aColor) {
  2956.     if (is_string($aColor)) {
  2957.  
  2958.        // Extract potential adjustment figure at end of color
  2959.        // specification
  2960.         $aColor = strtok($aColor,":");
  2961.         $adj = 0+strtok(":");
  2962.         if( $adj==0 ) $adj=1;
  2963.         if (substr($aColor, 0, 1) == "#") {
  2964.         return array($adj*hexdec(substr($aColor, 1, 2)),
  2965.                  $adj*hexdec(substr($aColor, 3, 2)),
  2966.                  $adj*hexdec(substr($aColor, 5, 2)));
  2967.         } else {
  2968.             if(!isset($this->rgb_table[$aColor]) )
  2969.             JpGraphError::Raise(" Unknown color: <strong>$aColor</strong>");
  2970.         $tmp=$this->rgb_table[$aColor];
  2971.         return array($adj*$tmp[0],$adj*$tmp[1],$adj*$tmp[2]);
  2972.         }
  2973.     } elseif( is_array($aColor) && (count($aColor)==3) ) {
  2974.         return $aColor;
  2975.     }
  2976.     else
  2977.         JpGraphError::Raise(" Unknown color specification: $aColor , size=".count($aColor));
  2978.     }
  2979.    
  2980.     // Compare two colors
  2981.     // return true if equal
  2982.     function Equal($aCol1,$aCol2) {
  2983.     $c1 = $this->Color($aCol1);
  2984.     $c2 = $this->Color($aCol2);
  2985.     if( $c1[0]==$c2[0] && $c1[1]==$c2[1] && $c1[2]==$c2[2] )
  2986.         return true;
  2987.     else
  2988.         return false;
  2989.     }
  2990.    
  2991.     // Allocate a new color in the current image
  2992.     // Return new color index, -1 if no more colors could be allocated
  2993.     function Allocate($aColor) {
  2994.     list ($r, $g, $b) = $this->color($aColor);
  2995.     if($GLOBALS['gd2']==true) {
  2996.         return imagecolorresolvealpha($this->img, $r, $g, $b, 0);
  2997.     } else {
  2998.         $index = imagecolorexact($this->img, $r, $g, $b);
  2999.         if ($index == -1) {
  3000.             $index = imagecolorallocate($this->img, $r, $g, $b);
  3001.             if( USE_APPROX_COLORS && $index == -1 )
  3002.             $index = imagecolorresolve($this->img, $r, $g, $b);
  3003.         }
  3004.         return $index;
  3005.     }
  3006.     }
  3007. } // Class
  3008.  
  3009.    
  3010. //===================================================
  3011. // CLASS Image
  3012. // Description: Wrapper class with some goodies to form the
  3013. // Interface to low level image drawing routines.
  3014. //===================================================
  3015. class Image {
  3016.     var $img_format;
  3017.     var $expired=false;
  3018.     var $img;
  3019.     var $left_margin=30,$right_margin=30,$top_margin=20,$bottom_margin=30;
  3020.     var $plotwidth,$plotheight;
  3021.     var $rgb;
  3022.     var $current_color,$current_color_name;
  3023.     var $lastx=0, $lasty=0;
  3024.     var $width, $height;
  3025.     var $line_weight=1;
  3026.     var $line_style=1;  // Default line style is solid
  3027.     var $obs_list=array();
  3028.     var $font_size=12,$font_family=FF_FONT1, $font_style=FS_NORMAL;
  3029.     var $text_halign="left",$text_valign="bottom";
  3030.     var $ttf=null;
  3031.     var $use_anti_aliasing=false;
  3032.     var $quality=null;
  3033.     var $colorstack=array(),$colorstackidx=0;
  3034.     //---------------
  3035.     // CONSTRUCTOR
  3036.     function Image($aWidth,$aHeight,$aFormat=DEFAULT_GFORMAT) {
  3037.     $this->CreateImgCanvas($aWidth,$aHeight);
  3038.     if( !$this->SetImgFormat($aFormat) ) {
  3039.         JpGraphError::Raise("JpGraph: Selected graphic format is either not supported or unknown [$aFormat]");
  3040.     }
  3041.     $this->ttf = new TTF();
  3042.     }
  3043.    
  3044.     function SetAutoMargin() { 
  3045.     $min_bm=10;
  3046.     if( BRAND_TIMING )
  3047.         $min_bm=15;    
  3048.     $lm = max(12,$this->width/7);
  3049.     $rm = max(12,$this->width/10);
  3050.     $tm = max(24,$this->height/7);
  3051.     $bm = max($min_bm,$this->height/7);
  3052.     $this->SetMargin($lm,$rm,$tm,$bm);     
  3053.     }
  3054.    
  3055.     function CreateImgCanvas($aWidth=-1,$aHeight=-1) {
  3056.     $this->width=$aWidth;
  3057.     $this->height=$aHeight;    
  3058.  
  3059.     $this->SetAutoMargin();
  3060.  
  3061.     if( $aWidth==-1 || $aHeight==-1 ) {
  3062.         // We will set the final size later.
  3063.         // Note: The size must be specified before any other
  3064.         // img routines that stroke anything are called.
  3065.         $this->img = null;
  3066.         $this->rgb = null;
  3067.         return;
  3068.     }
  3069.        
  3070.     if( $GLOBALS['gd2']==true && USE_TRUECOLOR ) {
  3071.         $this->img = imagecreatetruecolor($aWidth, $aHeight);  
  3072.         imagefilledrectangle($this->img, 0, 0, $aWidth, $aHeight, 0xffffff);
  3073.     } else {
  3074.         $this->img = imagecreate($aWidth, $aHeight);   
  3075.     }      
  3076.     assert($this->img != 0);       
  3077.     $this->rgb = new RGB($this->img);              
  3078.        
  3079.     // First index is background so this will be white
  3080.     $this->SetColor("white");
  3081.     }
  3082.    
  3083.                
  3084.     //---------------
  3085.     // PUBLIC METHODS  
  3086.  
  3087.     // Add observer. The observer will be notified when
  3088.     // the margin changes
  3089.     function AddObserver($aMethod,&$aObject) {
  3090.     $this->obs_list[]=array($aMethod,&$aObject);
  3091.     }
  3092.    
  3093.     // Call all observers
  3094.     function NotifyObservers() {
  3095.     //  foreach($this->obs_list as $o)
  3096.     //      $o[1]->$o[0]($this);
  3097.     for($i=0; $i < count($this->obs_list); ++$i) {
  3098.         $obj = & $this->obs_list[$i][1];
  3099.         $method = $this->obs_list[$i][0];
  3100.         $obj->$method($this);
  3101.     }
  3102.     }  
  3103.    
  3104.     function SetFont($family,$style=FS_NORMAL,$size=10) {
  3105.     if($family==FONT1_BOLD || $family==FONT2_BOLD || $family==FONT0 || $family==FONT1 || $family==FONT2 )
  3106.         JpGraphError::Raise(" Usage of FONT0, FONT1, FONT2 is deprecated. Use FF_xxx instead.");
  3107.        
  3108.     $this->font_family=$family;
  3109.     $this->font_style=$style;
  3110.     $this->font_size=$size;
  3111.     if( ($this->font_family==FF_FONT1 || $this->font_family==FF_FONT2) && $this->font_style==FS_BOLD ){
  3112.         ++$this->font_family;
  3113.     }
  3114.     }
  3115.    
  3116.     // Get the specific height for a text string
  3117.     function GetTextHeight($txt="",$angle=0) {
  3118.     // Builtin font?
  3119.     $tmp = split("\n",$txt);
  3120.     $n = count($tmp);
  3121.     $m=0;
  3122.     for($i=0; $i<count($tmp); ++$i)
  3123.         $m = max($m,strlen($tmp[$i]));
  3124.  
  3125.     if( $this->font_family <= FF_FONT2+1 ) {
  3126.         if( $angle==0 )
  3127.         return $n*imagefontheight($this->font_family);
  3128.         else
  3129.         return $m*imagefontwidth($this->font_family);
  3130.     }
  3131.     else {
  3132.         $file = $this->ttf->File($this->font_family,$this->font_style);        
  3133.         $bbox = ImageTTFBBox($this->font_size,$angle,$file,"XXOOMM"/*$txt*/);
  3134.         return $n*(abs($bbox[5])+abs($bbox[1])); // upper_right_y - lower_left_y           
  3135.     }
  3136.     }
  3137.    
  3138.     // Estimate font height
  3139.     function GetFontHeight($txt="XMg",$angle=0) {
  3140.     $tmp = split("\n",$txt);
  3141.     return $this->GetTextHeight($tmp[0],$angle);
  3142.     }
  3143.    
  3144.     // Approximate font width with width of letter "O"
  3145.     function GetFontWidth($txt="O",$angle=0) {
  3146.     return $this->GetTextWidth($txt,$angle);
  3147.     }
  3148.    
  3149.     // Get actual width of text in absolute pixels
  3150.     function GetTextWidth($txt,$angle=0) {
  3151.     // Builtin font?
  3152.     $tmp = split("\n",$txt);
  3153.     $n = count($tmp);
  3154.     $m=0;
  3155.     for($i=0; $i<count($tmp); ++$i)
  3156.         $m = max($m,strlen($tmp[$i]));
  3157.  
  3158.     if( $this->font_family <= FF_FONT2+1 ) {
  3159.         if( $angle==0 ) {
  3160.         $width=$m*imagefontwidth($this->font_family);
  3161.         return $width;
  3162.         }
  3163.         else
  3164.         return $n*imagefontheight($this->font_family); // 90 degrees internal so height become width
  3165.     }
  3166.     else {
  3167.         $file = $this->ttf->File($this->font_family,$this->font_style);        
  3168.         $bbox = ImageTTFBBox($this->font_size,$angle,$file,$txt);
  3169.         return $n*(abs($bbox[2]-$bbox[6]));
  3170.     }
  3171.     }
  3172.    
  3173.     // Draw text with a box around it
  3174.     function StrokeBoxedText($x,$y,$txt,$dir=0,$fcolor="white",$bcolor="black",
  3175.     $shadow=false,$paragraph_align="left") {
  3176.  
  3177.     if( !is_numeric($dir) ) {
  3178.         if( $dir=="h" ) $dir=0;
  3179.         elseif( $dir=="v" ) $dir=90;
  3180.         else JpGraphError::Raise(" Unknown direction specified in call to StrokeBoxedText() [$dir]");
  3181.     }
  3182.        
  3183.     $width=$this->GetTextWidth($txt,$dir);
  3184.     $height=$this->GetTextHeight($txt,$dir);
  3185.  
  3186.     if( $this->font_family<=FF_FONT2+1 ) {
  3187.         $xmarg=3;  
  3188.         $ymarg=3;
  3189.     }
  3190.     else {
  3191.         $xmarg=6;  
  3192.         $ymarg=6;
  3193.     }      
  3194.     $height += 2*$ymarg;
  3195.     $width += 2*$xmarg;
  3196.     if( $this->text_halign=="right" ) $x -= $width;
  3197.     elseif( $this->text_halign=="center" ) $x -= $width/2;
  3198.     if( $this->text_valign=="bottom" ) $y -= $height;
  3199.     elseif( $this->text_valign=="center" ) $y -= $height/2;
  3200.    
  3201.     if( $shadow ) {
  3202.         $oc=$this->current_color;
  3203.         $this->SetColor($bcolor);
  3204.         $this->ShadowRectangle($x,$y,$x+$width+2,$y+$height+2,$fcolor,2);
  3205.         $this->current_color=$oc;
  3206.     }
  3207.     else {
  3208.         if( $fcolor ) {
  3209.         $oc=$this->current_color;
  3210.         $this->SetColor($fcolor);
  3211.         $this->FilledRectangle($x,$y,$x+$width,$y+$height);
  3212.         $this->current_color=$oc;
  3213.         }
  3214.         if( $bcolor ) {
  3215.         $oc=$this->current_color;
  3216.         $this->SetColor($bcolor);          
  3217.         $this->Rectangle($x,$y,$x+$width,$y+$height);
  3218.         $this->current_color=$oc;          
  3219.         }
  3220.     }
  3221.        
  3222.     $h=$this->text_halign;
  3223.     $v=$this->text_valign;
  3224.     $this->SetTextAlign("left","top");
  3225.     $this->StrokeText($x+$xmarg, $y+$ymarg, $txt, $dir, $paragraph_align);
  3226.     $this->SetTextAlign($h,$v);
  3227.     }
  3228.  
  3229.     // Set text alignment  
  3230.     function SetTextAlign($halign,$valign="bottom") {
  3231.     $this->text_halign=$halign;
  3232.     $this->text_valign=$valign;
  3233.     }
  3234.    
  3235.     // Should we use anti-aliasing. Note: This really slows down graphics!
  3236.     function SetAntiAliasing() {
  3237.     $this->use_anti_aliasing=true;
  3238.     }
  3239.    
  3240.     function StrokeText($x,$y,$txt,$dir=0,$paragraph_align="left") {
  3241.  
  3242.     // Do special language encoding
  3243.     if( LANGUAGE_CYRILLIC )
  3244.         $txt = LanguageConv::ToCyrillic($txt);
  3245.  
  3246.     if( !is_numeric($dir) )
  3247.         JpGraphError::Raise(" Direction for text most be given as an angle between 0 and 90.");
  3248.            
  3249.     if( $this->font_family >= FF_FONT0 && $this->font_family <= FF_FONT2+1) {   // Internal font
  3250.         if( is_numeric($dir) && $dir!=90 && $dir!=0)
  3251.         JpGraphError::Raise(" Internal font does not support drawing text at arbitrary angle. Use TTF fonts instead.");
  3252.  
  3253.         $h=$this->GetTextHeight($txt);
  3254.         $fh=$this->GetFontHeight($txt);
  3255.         $w=$this->GetTextWidth($txt);
  3256.  
  3257.         if( $this->text_halign=="right")               
  3258.         $x -= $dir==0 ? $w : $h;
  3259.         elseif( $this->text_halign=="center" )
  3260.         $x -= $dir==0 ? $w/2 : $h/2;
  3261.                
  3262.         if( $this->text_valign=="top" )
  3263.         $y +=   $dir==0 ? $h : $w;
  3264.         elseif( $this->text_valign=="center" )             
  3265.         $y +=   $dir==0 ? $h/2 : $w/2;
  3266.                
  3267.         if( $dir==90 )
  3268.         imagestringup($this->img,$this->font_family,$x,$y,$txt,$this->current_color);
  3269.         else    {
  3270.         if (ereg("\n",$txt)) {
  3271.             $tmp = split("\n",$txt);
  3272.             for($i=0; $i<count($tmp); ++$i) {
  3273.             $w1 = $this->GetTextWidth($tmp[$i]);
  3274.             if( $paragraph_align=="left" ) {
  3275.                 imagestring($this->img,$this->font_family,$x,$y-$h+1+$i*$fh,$tmp[$i],$this->current_color);
  3276.             }
  3277.             elseif( $paragraph_align=="right" ) {
  3278.                 imagestring($this->img,$this->font_family,$x+($w-$w1),
  3279.                 $y-$h+1+$i*$fh,$tmp[$i],$this->current_color);
  3280.             }
  3281.             else {
  3282.                 imagestring($this->img,$this->font_family,$x+$w/2-$w1/2,
  3283.                 $y-$h+1+$i*$fh,$tmp[$i],$this->current_color);
  3284.             }
  3285.             }
  3286.         }else{
  3287.             //Put the text
  3288.             imagestring($this->img,$this->font_family,$x,$y-$h+1,$txt,$this->current_color);
  3289.         }
  3290.         }
  3291.     }
  3292.     elseif($this->font_family >= FF_COURIER && $this->font_family <= FF_BOOK)  { // TTF font
  3293.         $file = $this->ttf->File($this->font_family,$this->font_style);        
  3294.         $angle=$dir;
  3295.         $bbox=ImageTTFBBox($this->font_size,$angle,$file,$txt);
  3296.         if( $this->text_halign=="right" ) $x -= $bbox[2]-$bbox[0];
  3297.         elseif( $this->text_halign=="center" )  $x -= ($bbox[4]-$bbox[0])/2;
  3298.         elseif( $this->text_halign=="topanchor" ) $x -= $bbox[4]-$bbox[0];
  3299.         elseif( $this->text_halign=="left" ) $x += -($bbox[6]-$bbox[0]);
  3300.            
  3301.         if( $this->text_valign=="top" ) $y -= $bbox[5];
  3302.         elseif( $this->text_valign=="center" )  $y -= ($bbox[5]-$bbox[1])/2;
  3303.         elseif( $this->text_valign=="bottom" ) $y -= $bbox[1];
  3304.                
  3305.         // Use lower left of bbox as fix-point, not the default baselinepoint.             
  3306.         $x -= $bbox[0];
  3307.         if($GLOBALS['gd2']) {
  3308.         $old = ImageAlphaBlending($this->img, true);
  3309.         }
  3310.         ImageTTFText ($this->img, $this->font_size, $angle, $x, $y,
  3311.         $this->current_color,$file,$txt);
  3312.         if($GLOBALS['gd2']) {
  3313.         ImageAlphaBlending($this->img, $old);
  3314.         }
  3315.     }
  3316.     else
  3317.         JpGraphError::Raise(" Unknown font font family specification. ");
  3318.     }
  3319.    
  3320.     function SetMargin($lm,$rm,$tm,$bm) {
  3321.     $this->left_margin=$lm;
  3322.     $this->right_margin=$rm;
  3323.     $this->top_margin=$tm;
  3324.     $this->bottom_margin=$bm;
  3325.     $this->plotwidth=$this->width - $this->left_margin-$this->right_margin ;
  3326.     $this->plotheight=$this->height - $this->top_margin-$this->bottom_margin ; 
  3327.     $this->NotifyObservers();
  3328.     }
  3329.  
  3330.     function SetTransparent($color) {
  3331.     imagecolortransparent ($this->img,$this->rgb->allocate($color));
  3332.     }
  3333.    
  3334.     function SetColor($color) {
  3335.     $this->current_color_name = $color;
  3336.     $this->current_color=$this->rgb->allocate($color);
  3337.     if( $this->current_color == -1 ) {
  3338.         $tc=imagecolorstotal($this->img);
  3339.         JpGraphError::Raise("<b> Can't allocate any more colors.</b><br>
  3340.                 Image has already allocated maximum of <b>$tc colors</b>.
  3341.                 This might happen if you have anti-aliasing turned on
  3342.                 together with a background image or perhaps gradient fill
  3343.                 since this requires many, many colors. Try to turn off
  3344.                 anti-aliasing.<p>
  3345.                 If there is still a problem try downgrading the quality of
  3346.                 the background image to use a smaller pallete to leave some
  3347.                 entries for your graphs. You should try to limit the number
  3348.                 of colors in your background image to 64.<p>
  3349.                 If there is still problem set the constant
  3350. <pre>
  3351. DEFINE(\"USE_APPROX_COLORS\",true);
  3352. </pre>
  3353.                 in jpgraph.php This will use approximative colors
  3354.                 when the palette is full.
  3355.                 <p>
  3356.                 Unfortunately there is not much JpGraph can do about this
  3357.                 since the palette size is a limitation of current graphic format and
  3358.                 what the underlying GD library suppports.");
  3359.     }
  3360.     return $this->current_color;
  3361.     }
  3362.    
  3363.     function PushColor($color) {
  3364.     if( $color != "" ) {
  3365.         $this->colorstack[$this->colorstackidx]=$this->current_color_name;
  3366.         $this->colorstack[$this->colorstackidx+1]=$this->current_color;
  3367.         $this->colorstackidx+=2;
  3368.         $this->SetColor($color);
  3369.     }
  3370.     else {
  3371.         JpGraphError::Raise("Color specified as empty string in PushColor().");
  3372.     }
  3373.     }
  3374.    
  3375.     function PopColor() {
  3376.     if($this->colorstackidx<1)
  3377.         JpGraphError::Raise(" Negative Color stack index. Unmatched call to PopColor()");
  3378.     $this->current_color=$this->colorstack[--$this->colorstackidx];
  3379.     $this->current_color_name=$this->colorstack[--$this->colorstackidx];
  3380.     }
  3381.    
  3382.    
  3383.     // Why this duplication? Because this way we can call this method
  3384.     // for any image and not only the current objsct
  3385.     function AdjSat($sat) { $this->_AdjSat($this->img,$sat);    }  
  3386.    
  3387.     function _AdjSat($img,$sat) {
  3388.     $nbr = imagecolorstotal ($img);
  3389.     for( $i=0; $i<$nbr; ++$i ) {
  3390.         $colarr = imagecolorsforindex ($img,$i);
  3391.         $rgb[0]=$colarr["red"];
  3392.         $rgb[1]=$colarr["green"];
  3393.         $rgb[2]=$colarr["blue"];
  3394.         $rgb = $this->AdjRGBSat($rgb,$sat);
  3395.         imagecolorset ($img, $i, $rgb[0], $rgb[1], $rgb[2]);
  3396.     }
  3397.     }
  3398.    
  3399.     function AdjBrightContrast($bright,$contr=0) {
  3400.     $this->_AdjBrightContrast($this->img,$bright,$contr);
  3401.     }
  3402.     function _AdjBrightContrast($img,$bright,$contr=0) {
  3403.     if( $bright < -1 || $bright > 1 || $contr < -1 || $contr > 1 )
  3404.         JpGraphError::Raise(" Parameters for brightness and Contrast out of range [-1,1]");    
  3405.     $nbr = imagecolorstotal ($img);
  3406.     for( $i=0; $i<$nbr; ++$i ) {
  3407.         $colarr = imagecolorsforindex ($img,$i);
  3408.         $r = $this->AdjRGBBrightContrast($colarr["red"],$bright,$contr);
  3409.         $g = $this->AdjRGBBrightContrast($colarr["green"],$bright,$contr);
  3410.         $b = $this->AdjRGBBrightContrast($colarr["blue"],$bright,$contr);      
  3411.         imagecolorset ($img, $i, $r, $g, $b);
  3412.     }
  3413.     }
  3414.    
  3415.     // Private helper function for adj sat
  3416.     // Adjust saturation for RGB array $u. $sat is a value between -1 and 1
  3417.     // Note: Due to GD inability to handle true color the RGB values are only between
  3418.     // 8 bit. This makes saturation quite sensitive for small increases in parameter sat.
  3419.     //
  3420.     // Tip: To get a grayscale picture set sat=-100, values <-100 changes the colors
  3421.     // to it's complement.
  3422.     //
  3423.     // Implementation note: The saturation is implemented directly in the RGB space
  3424.     // by adjusting the perpendicular distance between the RGB point and the "grey"
  3425.     // line (1,1,1). Setting $sat>0 moves the point away from the line along the perp.
  3426.     // distance and a negative value moves the point closer to the line.
  3427.     // The values are truncated when the color point hits the bounding box along the
  3428.     // RGB axis.
  3429.     // DISCLAIMER: I'm not 100% sure this is he correct way to implement a color
  3430.     // saturation function in RGB space. However, it looks ok and has the expected effect.
  3431.     function AdjRGBSat($rgb,$sat) {
  3432.     // TODO: Should be moved to the RGB class
  3433.     // Grey vector
  3434.     $v=array(1,1,1);
  3435.  
  3436.     // Dot product
  3437.     $dot = $rgb[0]*$v[0]+$rgb[1]*$v[1]+$rgb[2]*$v[2];
  3438.  
  3439.     // Normalize dot product
  3440.     $normdot = $dot/3;  // dot/|v|^2
  3441.  
  3442.     // Direction vector between $u and its projection onto $v
  3443.     for($i=0; $i<3; ++$i)
  3444.         $r[$i] = $rgb[$i] - $normdot*$v[$i];
  3445.  
  3446.     // Adjustment factor so that sat==1 sets the highest RGB value to 255
  3447.     if( $sat > 0 ) {
  3448.         $m=0;
  3449.         for( $i=0; $i<3; ++$i) {
  3450.         if( sign($r[$i]) == 1 && $r[$i]>0)
  3451.             $m=max($m,(255-$rgb[$i])/$r[$i]);
  3452.         }
  3453.         $tadj=$m;
  3454.     }
  3455.     else
  3456.         $tadj=1;
  3457.        
  3458.     $tadj = $tadj*$sat;
  3459.     for($i=0; $i<3; ++$i) {
  3460.         $un[$i] = round($rgb[$i] + $tadj*$r[$i]);      
  3461.         if( $un[$i]<0 ) $un[$i]=0;      // Truncate color when they reach 0
  3462.         if( $un[$i]>255 ) $un[$i]=255;// Avoid potential rounding error
  3463.     }      
  3464.     return $un;
  3465.     }  
  3466.  
  3467.     // Private helper function for AdjBrightContrast
  3468.     function AdjRGBBrightContrast($rgb,$bright,$contr) {
  3469.     // TODO: Should be moved to the RGB class
  3470.     // First handle contrast, i.e change the dynamic range around grey
  3471.     if( $contr <= 0 ) {
  3472.         // Decrease contrast
  3473.         $adj = abs($rgb-128) * (-$contr);
  3474.         if( $rgb < 128 ) $rgb += $adj;
  3475.         else $rgb -= $adj;
  3476.     }
  3477.     else { // $contr > 0
  3478.         // Increase contrast
  3479.         if( $rgb < 128 ) $rgb = $rgb - ($rgb * $contr);
  3480.         else $rgb = $rgb + ((255-$rgb) * $contr);
  3481.     }
  3482.    
  3483.     // Add (or remove) various amount of white
  3484.     $rgb += $bright*255;   
  3485.     $rgb=min($rgb,255);
  3486.     $rgb=max($rgb,0);
  3487.     return $rgb;   
  3488.     }
  3489.    
  3490.     function SetLineWeight($weight) {
  3491.     $this->line_weight = $weight;
  3492.     }
  3493.    
  3494.     function SetStartPoint($x,$y) {
  3495.     $this->lastx=$x;
  3496.     $this->lasty=$y;
  3497.     }
  3498.    
  3499.     function Arc($cx,$cy,$w,$h,$s,$e) {
  3500.     imagearc($this->img,$cx,$cy,$w,$h,$s,$e,$this->current_color);
  3501.     }
  3502.    
  3503.     function FilledArc($xc,$yc,$w,$h,$s,$e,$style="") {
  3504.     if( $GLOBALS['gd2'] ) {
  3505.         if( $style=="" ) $style=IMG_ARC_PIE;
  3506.         imagefilledarc($this->img,$xc,$yc,$w,$h,$s,$e,$this->current_color,$style);
  3507.         return;
  3508.     }
  3509.  
  3510.     // In GD 1.x we have to do it ourself interesting enough there is surprisingly
  3511.     // little diffrence in time between doing it PHP and using the optimised GD
  3512.     // library (roughly ~20%) I had expected it to be at least 100% slower doing it
  3513.     // manually with a polygon approximation in PHP.....
  3514.     $fillcolor = $this->current_color_name;
  3515.  
  3516.     $w /= 2; // We use radius in our calculations instead
  3517.     $h /= 2;
  3518.  
  3519.     // Setup the angles so we have the same conventions as the builtin
  3520.     // FilledArc() which is a little bit strange if you ask me....
  3521.  
  3522.     $s = 360-$s;
  3523.     $e = 360-$e;
  3524.  
  3525.     if( $e > $s ) {
  3526.         $e = $e - 360;
  3527.         $da = $s - $e;
  3528.     }
  3529.     $da = $s-$e;
  3530.  
  3531.     // We use radians
  3532.     $s *= M_PI/180;
  3533.     $e *= M_PI/180;
  3534.     $da *= M_PI/180;
  3535.  
  3536.     // Calculate a polygon approximation
  3537.     $p[0] = $xc;
  3538.     $p[1] = $yc;
  3539.  
  3540.     // Heuristic on how many polygons we need to make the
  3541.     // arc look good
  3542.     $numsteps = round(8 * abs($da) * ($w+$h)*($w+$h)/1500);
  3543.     //echo "da=$da, w=$w, h=$h, numsteps = $numsteps<br>\n";
  3544.     if( $numsteps == 0 ) return;
  3545.     if( $numsteps < 10 ) $numsteps=10;
  3546.     $delta = abs($da)/$numsteps;
  3547.    
  3548.     $pa=array();
  3549.     $a = $s;
  3550.     for($i=1; $i<=$numsteps; ++$i ) {
  3551.         $p[2*$i] = round($xc + $w*cos($a));
  3552.         $p[2*$i+1] = round($yc - $h*sin($a));
  3553.         //$a = $s + $i*$delta;
  3554.         $a -= $delta;
  3555.         $pa[2*($i-1)] = $p[2*$i];
  3556.         $pa[2*($i-1)+1] = $p[2*$i+1];
  3557.     }
  3558.  
  3559.     // Get the last point at the exact ending angle to avoid
  3560.     // any rounding errors.
  3561.     $p[2*$i] = round($xc + $w*cos($e));
  3562.     $p[2*$i+1] = round($yc - $h*sin($e));
  3563.     $pa[2*($i-1)] = $p[2*$i];
  3564.     $pa[2*($i-1)+1] = $p[2*$i+1];
  3565.     $i++;
  3566.  
  3567.     $p[2*$i] = $xc;
  3568.         $p[2*$i+1] = $yc;
  3569.     if( $fillcolor != "" ) {
  3570.         $this->PushColor($fillcolor);
  3571.         imagefilledpolygon($this->img,$p,count($p)/2,$this->current_color);
  3572.         //$this->FilledPolygon($p);
  3573.         $this->PopColor();
  3574.     }
  3575.     }
  3576.  
  3577.     function FilledCakeSlice($cx,$cy,$w,$h,$s,$e) {
  3578.     $this->CakeSlice($cx,$cy,$w,$h,$s,$e,$this->current_color_name);
  3579.     }
  3580.  
  3581.     function CakeSlice($xc,$yc,$w,$h,$s,$e,$fillcolor="",$arccolor="") {
  3582.     $this->PushColor($fillcolor);
  3583.     $this->FilledArc($xc,$yc,2*$w,2*$h,$s,$e);
  3584.     $this->PopColor();
  3585.  
  3586.     if( $arccolor != "" ) {
  3587.         $this->PushColor($arccolor);
  3588.         $this->Arc($xc,$yc,2*$w,2*$h,$s,$e);
  3589.         $xx = $w * cos(2*M_PI - $s*M_PI/180) + $xc;
  3590.         $yy = $yc - $h * sin(2*M_PI - $s*M_PI/180);
  3591.         $this->Line($xc,$yc,$xx,$yy);
  3592.         $xx = $w * cos(2*M_PI - $e*M_PI/180) + $xc;
  3593.         $yy = $yc - $h * sin(2*M_PI - $e*M_PI/180);
  3594.         $this->Line($xc,$yc,$xx,$yy);
  3595.         $this->PopColor();
  3596.     }
  3597.  
  3598.     // if( $arccolor != "" ) {
  3599.         //$this->PushColor($arccolor);
  3600.         // Since IMG_ARC_NOFILL | IMG_ARC_EDGED does not work as described in the PHP manual
  3601.         // I have to do the edges manually with some potential rounding errors since I can't
  3602.         // be sure may endpoints gets calculated with the same accuracy as the builtin
  3603.         // Arc() function in GD
  3604.         //$this->FilledArc($cx,$cy,2*$w,2*$h,$s,$e, IMG_ARC_NOFILL | IMG_ARC_EDGED );
  3605.         //$this->PopColor();
  3606.     // }
  3607.     }
  3608.  
  3609.     function Ellipse($xc,$yc,$w,$h) {
  3610.     $this->Arc($xc,$yc,$w,$h,0,360);
  3611.     }
  3612.    
  3613.     // Breseham circle gives visually better result then using GD
  3614.     // built in arc(). It takes some more time but gives better
  3615.     // accuracy.
  3616.     function BresenhamCircle($xc,$yc,$r) {
  3617.     $d = 3-2*$r;
  3618.     $x = 0;
  3619.     $y = $r;
  3620.     while($x<=$y) {
  3621.         $this->Point($xc+$x,$yc+$y);           
  3622.         $this->Point($xc+$x,$yc-$y);
  3623.         $this->Point($xc-$x,$yc+$y);
  3624.         $this->Point($xc-$x,$yc-$y);
  3625.            
  3626.         $this->Point($xc+$y,$yc+$x);
  3627.         $this->Point($xc+$y,$yc-$x);
  3628.         $this->Point($xc-$y,$yc+$x);
  3629.         $this->Point($xc-$y,$yc-$x);
  3630.            
  3631.         if( $d<0 ) $d += 4*$x+6;
  3632.         else {
  3633.         $d += 4*($x-$y)+10;    
  3634.         --$y;
  3635.         }
  3636.         ++$x;
  3637.     }
  3638.     }
  3639.            
  3640.     function Circle($xc,$yc,$r) {
  3641.     if( USE_BRESENHAM )
  3642.         $this->BresenhamCircle($xc,$yc,$r);
  3643.     else {
  3644.         $this->Arc($xc,$yc,$r*2,$r*2,0,360);       
  3645.         // For some reason imageellipse() isn't in GD 2.0.1, PHP 4.1.1
  3646.         //imageellipse($this->img,$xc,$yc,$r,$r,$this->current_color);
  3647.     }
  3648.     }
  3649.    
  3650.     function FilledCircle($xc,$yc,$r) {
  3651.     if( $GLOBALS['gd2'] )
  3652.         imagefilledellipse($this->img,$xc,$yc,2*$r,2*$r,$this->current_color);
  3653.     else {
  3654.         for( $i=1; $i<2*$r; ++$i )
  3655.         $this->Arc($xc,$yc,$i,$i,0,360);
  3656.     }  
  3657.     }
  3658.    
  3659.     // Linear Color InterPolation
  3660.     function lip($f,$t,$p) {
  3661.     $p = round($p,1);
  3662.     $r = $f[0] + ($t[0]-$f[0])*$p;
  3663.     $g = $f[1] + ($t[1]-$f[1])*$p;
  3664.     $b = $f[2] + ($t[2]-$f[2])*$p;
  3665.     return array($r,$g,$b);
  3666.     }
  3667.  
  3668.     // Anti-aliased line.
  3669.     // Note that this is roughly 8 times slower then a normal line!
  3670.     function WuLine($x1,$y1,$x2,$y2) {
  3671.     // Get foreground line color
  3672.     $lc = imagecolorsforindex($this->img,$this->current_color);
  3673.     $lc = array($lc["red"],$lc["green"],$lc["blue"]);
  3674.  
  3675.     $dx = $x2-$x1;
  3676.     $dy = $y2-$y1;
  3677.    
  3678.     if( abs($dx) > abs($dy) ) {
  3679.         if( $dx<0 ) {
  3680.         $dx = -$dx;$dy = -$dy;
  3681.         $tmp=$x2;$x2=$x1;$x1=$tmp;
  3682.         $tmp=$y2;$y2=$y1;$y1=$tmp;
  3683.         }
  3684.         $x=$x1<<16; $y=$y1<<16;
  3685.         $yinc = ($dy*65535)/$dx;
  3686.         while( ($x >> 16) < $x2 ) {
  3687.                
  3688.         $bc = imagecolorsforindex($this->img,imagecolorat($this->img,$x>>16,$y>>16));
  3689.         $bc=array($bc["red"],$bc["green"],$bc["blue"]);
  3690.                
  3691.         $this->SetColor($this->lip($lc,$bc,($y & 0xFFFF)/65535));
  3692.         imagesetpixel($this->img,$x>>16,$y>>16,$this->current_color);
  3693.         $this->SetColor($this->lip($lc,$bc,(~$y & 0xFFFF)/65535));
  3694.         imagesetpixel($this->img,$x>>16,($y>>16)+1,$this->current_color);
  3695.         $x += 65536; $y += $yinc;
  3696.         }
  3697.     }
  3698.     else {
  3699.         if( $dy<0 ) {
  3700.         $dx = -$dx;$dy = -$dy;
  3701.         $tmp=$x2;$x2=$x1;$x1=$tmp;
  3702.         $tmp=$y2;$y2=$y1;$y1=$tmp;
  3703.         }
  3704.         $x=$x1<<16; $y=$y1<<16;
  3705.         $xinc = ($dx*65535)/$dy;   
  3706.         while( ($y >> 16) < $y2 ) {
  3707.                
  3708.         $bc = imagecolorsforindex($this->img,imagecolorat($this->img,$x>>16,$y>>16));
  3709.         $bc=array($bc["red"],$bc["green"],$bc["blue"]);            
  3710.                
  3711.         $this->SetColor($this->lip($lc,$bc,($x & 0xFFFF)/65535));
  3712.         imagesetpixel($this->img,$x>>16,$y>>16,$this->current_color);
  3713.         $this->SetColor($this->lip($lc,$bc,(~$x & 0xFFFF)/65535));
  3714.         imagesetpixel($this->img,($x>>16)+1,$y>>16,$this->current_color);
  3715.         $y += 65536; $x += $xinc;
  3716.         }
  3717.     }
  3718.     $this->SetColor($lc);
  3719.     imagesetpixel($this->img,$x2,$y2,$this->current_color);    
  3720.     imagesetpixel($this->img,$x1,$y1,$this->current_color);        
  3721.     }
  3722.  
  3723.     // Set line style dashed, dotted etc
  3724.     function SetLineStyle($s) {
  3725.     if( is_numeric($s) ) {
  3726.         if( $s<1 || $s>4 )
  3727.         JpGraphError::Raise(" Illegal numeric argument to SetLineStyle(): $s");
  3728.     }
  3729.     elseif( is_string($s) ) {
  3730.         if( $s == "solid" ) $s=1;
  3731.         elseif( $s == "dotted" ) $s=2;
  3732.         elseif( $s == "dashed" ) $s=3;
  3733.         elseif( $s == "longdashed" ) $s=4;
  3734.         else JpGraphError::Raise(" Illegal string argument to SetLineStyle(): $s");
  3735.     }
  3736.     else JpGraphError::Raise(" Illegal argument to SetLineStyle $s");
  3737.     $this->line_style=$s;
  3738.     }
  3739.    
  3740.     // Same as Line but take the line_style into account
  3741.     function StyleLine($x1,$y1,$x2,$y2) {
  3742.     switch( $this->line_style ) {
  3743.         case 1:// Solid
  3744.         $this->Line($x1,$y1,$x2,$y2);
  3745.         break;
  3746.         case 2: // Dotted
  3747.         $this->DashedLine($x1,$y1,$x2,$y2,1,6);
  3748.         break;
  3749.         case 3: // Dashed
  3750.         $this->DashedLine($x1,$y1,$x2,$y2,2,4);
  3751.         break;
  3752.         case 4: // Longdashes
  3753.         $this->DashedLine($x1,$y1,$x2,$y2,8,6);
  3754.         break;
  3755.         default:
  3756.         JpGraphError::Raise(" Unknown line style: $this->line_style ");
  3757.         break;
  3758.     }
  3759.     }
  3760.  
  3761.     function Line($x1,$y1,$x2,$y2) {
  3762.     if( $this->line_weight==0 ) return;
  3763.     if( $this->use_anti_aliasing ) {
  3764.         $dx = $x2-$x1;
  3765.         $dy = $y2-$y1;
  3766.         // Vertical, Horizontal or 45 lines don't need anti-aliasing
  3767.         if( $dx!=0 && $dy!=0 && $dx!=$dy ) {
  3768.         $this->WuLine($x1,$y1,$x2,$y2);
  3769.         return;
  3770.         }
  3771.     }
  3772.     if( $this->line_weight==1 )
  3773.         imageline($this->img,$x1,$y1,$x2,$y2,$this->current_color);
  3774.     elseif( $x1==$x2 ) {        // Special case for vertical lines
  3775.         imageline($this->img,$x1,$y1,$x2,$y2,$this->current_color);
  3776.         $w1=floor($this->line_weight/2);
  3777.         $w2=floor(($this->line_weight-1)/2);
  3778.         for($i=1; $i<=$w1; ++$i)
  3779.         imageline($this->img,$x1+$i,$y1,$x2+$i,$y2,$this->current_color);
  3780.         for($i=1; $i<=$w2; ++$i)
  3781.         imageline($this->img,$x1-$i,$y1,$x2-$i,$y2,$this->current_color);
  3782.     }
  3783.     elseif( $y1==$y2 ) {        // Special case for horizontal lines
  3784.         imageline($this->img,$x1,$y1,$x2,$y2,$this->current_color);
  3785.         $w1=floor($this->line_weight/2);
  3786.         $w2=floor(($this->line_weight-1)/2);
  3787.         for($i=1; $i<=$w1; ++$i)
  3788.         imageline($this->img,$x1,$y1+$i,$x2,$y2+$i,$this->current_color);
  3789.         for($i=1; $i<=$w2; ++$i)
  3790.         imageline($this->img,$x1,$y1-$i,$x2,$y2-$i,$this->current_color);      
  3791.     }
  3792.     else {  // General case with a line at an angle
  3793.         $a = atan2($y1-$y2,$x2-$x1);
  3794.         // Now establish some offsets from the center. This gets a little
  3795.         // bit involved since we are dealing with integer functions and we
  3796.         // want the apperance to be as smooth as possible and never be thicker
  3797.         // then the specified width.
  3798.            
  3799.         // We do the trig stuff to make sure that the endpoints of the line
  3800.         // are perpendicular to the line itself.
  3801.         $dx=(sin($a)*$this->line_weight/2);
  3802.         $dy=(cos($a)*$this->line_weight/2);
  3803.  
  3804.         $pnts = array($x2+$dx,$y2+$dy,$x2-$dx,$y2-$dy,$x1-$dx,$y1-$dy,$x1+$dx,$y1+$dy);
  3805.         imagefilledpolygon($this->img,$pnts,count($pnts)/2,$this->current_color);
  3806.     }      
  3807.     $this->lastx=$x2; $this->lasty=$y2;    
  3808.     }
  3809.    
  3810.     function Polygon($p) {
  3811.     if( $this->line_weight==0 ) return;
  3812.     $n=count($p)/2;
  3813.     for( $i=0; $i<$n; ++$i ) {
  3814.         $j=($i+1)%$n;
  3815.         $this->Line($p[$i*2],$p[$i*2+1],$p[$j*2],$p[$j*2+1]);
  3816.     }
  3817.     }
  3818.    
  3819.     function FilledPolygon($pts) {
  3820.     imagefilledpolygon($this->img,$pts,count($pts)/2,$this->current_color);
  3821.     }
  3822.    
  3823.     function Rectangle($xl,$yu,$xr,$yl) {
  3824.     $this->Polygon(array($xl,$yu,$xr,$yu,$xr,$yl,$xl,$yl));
  3825.     }
  3826.    
  3827.     function FilledRectangle($xl,$yu,$xr,$yl) {
  3828.     $this->FilledPolygon(array($xl,$yu,$xr,$yu,$xr,$yl,$xl,$yl));
  3829.     }
  3830.  
  3831.     function ShadowRectangle($xl,$yu,$xr,$yl,$fcolor=false,$shadow_width=3,$shadow_color=array(102,102,102)) {
  3832.     $this->PushColor($shadow_color);
  3833.     $this->FilledRectangle($xr-$shadow_width,$yu+$shadow_width,$xr,$yl);
  3834.     $this->FilledRectangle($xl+$shadow_width,$yl-$shadow_width,$xr,$yl);
  3835.     $this->PopColor();
  3836.     if( $fcolor==false )
  3837.         $this->Rectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1);
  3838.     else {     
  3839.         $this->PushColor($fcolor);
  3840.         $this->FilledRectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1);
  3841.         $this->PopColor();
  3842.         $this->Rectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1);                         
  3843.     }
  3844.     }
  3845.  
  3846.     function StyleLineTo($x,$y) {
  3847.     $this->StyleLine($this->lastx,$this->lasty,$x,$y);
  3848.     $this->lastx=$x;
  3849.     $this->lasty=$y;
  3850.     }
  3851.    
  3852.     function LineTo($x,$y) {
  3853.     $this->Line($this->lastx,$this->lasty,$x,$y);
  3854.     $this->lastx=$x;
  3855.     $this->lasty=$y;
  3856.     }
  3857.    
  3858.     function Point($x,$y) {
  3859.     imagesetpixel($this->img,$x,$y,$this->current_color);
  3860.     }
  3861.    
  3862.     function Fill($x,$y) {
  3863.     imagefill($this->img,$x,$y,$this->current_color);
  3864.     }
  3865.    
  3866.     function DashedLine($x1,$y1,$x2,$y2,$dash_length=1,$dash_space=4) {
  3867.     // Code based on, but not identical to, work by Ariel Garza and James Pine
  3868.     $line_length = ceil (sqrt(pow(($x2 - $x1),2) + pow(($y2 - $y1),2)) );
  3869.     $dx = ($x2 - $x1) / $line_length;
  3870.     $dy = ($y2 - $y1) / $line_length;
  3871.     $lastx = $x1; $lasty = $y1;
  3872.     $xmax = max($x1,$x2);
  3873.     $xmin = min($x1,$x2);
  3874.     $ymax = max($y1,$y2);
  3875.     $ymin = min($y1,$y2);
  3876.     for ($i = 0; $i < $line_length; $i += ($dash_length + $dash_space)) {
  3877.         $x = ($dash_length * $dx) + $lastx;
  3878.         $y = ($dash_length * $dy) + $lasty;
  3879.            
  3880.         // The last section might overshoot so we must take a computational hit
  3881.         // and check this.
  3882.         if( $x>$xmax ) $x=$xmax;
  3883.         if( $y>$ymax ) $y=$ymax;
  3884.            
  3885.         if( $x<$xmin ) $x=$xmin;
  3886.         if( $y<$ymin ) $y=$ymin;
  3887.  
  3888.         $this->Line($lastx,$lasty,$x,$y);
  3889.         $lastx = $x + ($dash_space * $dx);
  3890.         $lasty = $y + ($dash_space * $dy);
  3891.     }
  3892.     }
  3893.    
  3894.     // Generate image header
  3895.     function Headers() {
  3896.     if ($this->expired) {
  3897.         header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
  3898.         header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT");
  3899.         header("Cache-Control: no-cache, must-revalidate");
  3900.         header("Pragma: no-cache");
  3901.     }
  3902.     header("Content-type: image/$this->img_format");
  3903.     }
  3904.  
  3905.     // Adjust image quality for formats that allow this
  3906.     function SetQuality($q) {
  3907.     $this->quality = $q;
  3908.     }
  3909.    
  3910.     // Stream image to browser or to file
  3911.     function Stream($aFile="") {
  3912.     $func="image".$this->img_format;
  3913.     if( $this->img_format=="jpeg" && $this->quality != null ) {
  3914.         $res = @$func($this->img,$aFile,$this->quality);
  3915.     }
  3916.     else {
  3917.         if( $aFile != "" ) {
  3918.         $res = @$func($this->img,$aFile);
  3919.         }
  3920.         else
  3921.         $res = @$func($this->img);
  3922.     }
  3923.     if( !$res )
  3924.         JpGraphError::Raise("Can't create or stream image to file $aFile Check that PHP has enough permission to write a file to the current directory.");
  3925.     }
  3926.        
  3927.     // Clear resource tide up by image
  3928.     function Destroy() {
  3929.     imagedestroy($this->img);
  3930.     }
  3931.    
  3932.     // Specify image format. Note depending on your installation
  3933.     // of PHP not all formats may be supported.
  3934.     function SetImgFormat($aFormat) {      
  3935.     $aFormat = strtolower($aFormat);
  3936.     $tst = true;
  3937.     $supported = imagetypes();
  3938.     if( $aFormat=="auto" ) {
  3939.         if( $supported & IMG_PNG )
  3940.         $this->img_format="png";
  3941.         elseif( $supported & IMG_JPG )
  3942.         $this->img_format="jpeg";
  3943.         elseif( $supported & IMG_GIF )
  3944.         $this->img_format="gif";
  3945.         else
  3946.         JpGraphError::Raise(" Your PHP (and GD-lib) installation does not appear to support any known graphic formats.".
  3947.             "You need to first make sure GD is compiled as a module to PHP. If you also want to use JPEG images".
  3948.             "you must get the JPEG library. Please see the PHP docs for details.");
  3949.                
  3950.         return true;
  3951.     }
  3952.     else {
  3953.         if( $aFormat=="jpeg" || $aFormat=="png" || $aFormat=="gif" ) {
  3954.         if( $aFormat=="jpeg" && !($supported & IMG_JPG) )
  3955.             $tst=false;
  3956.         elseif( $aFormat=="png" && !($supported & IMG_PNG) )
  3957.             $tst=false;
  3958.         elseif( $aFormat=="gif" && !($supported & IMG_GIF) )    
  3959.             $tst=false;
  3960.         else {
  3961.             $this->img_format=$aFormat;
  3962.             return true;
  3963.         }
  3964.         }
  3965.         else
  3966.         $tst=false;
  3967.         if( !$tst )
  3968.         JpGraphError::Raise(" Your PHP installation does not support the chosen graphic format: $aFormat");
  3969.     }
  3970.     }  
  3971. } // CLASS
  3972.  
  3973. //===================================================
  3974. // CLASS RotImage
  3975. // Description: Exactly as Image but draws the image at
  3976. // a specified angle around a specified rotation point.
  3977. //===================================================
  3978. class RotImage extends Image {
  3979.     var $m=array();
  3980.     var $a=0;
  3981.     var $dx=0,$dy=0,$transx=0,$transy=0;
  3982.    
  3983.     function RotImage($aWidth,$aHeight,$a=0,$aFormat=DEFAULT_GFORMAT) {
  3984.     $this->Image($aWidth,$aHeight,$aFormat);
  3985.     $this->dx=$this->left_margin+$this->plotwidth/2;
  3986.     $this->dy=$this->top_margin+$this->plotheight/2;
  3987.     $this->SetAngle($a);   
  3988.     }
  3989.    
  3990.     function SetCenter($dx,$dy) {
  3991.     $old_dx = $this->dx;
  3992.     $old_dy = $this->dy;
  3993.     $this->dx=$dx;
  3994.     $this->dy=$dy;
  3995.     return array($old_dx,$old_dy);
  3996.     }
  3997.    
  3998.     function SetTranslation($dx,$dy) {
  3999.     $old = array($this->transx,$this->transy);
  4000.     $this->transx = $dx;
  4001.     $this->transy = $dy;
  4002.     return $old;
  4003.     }
  4004.  
  4005.     function SetAngle($a) {
  4006.     $tmp = $this->a;
  4007.     $this->a = $a;
  4008.     $a *= M_PI/180;
  4009.     $sa=sin($a); $ca=cos($a);
  4010.        
  4011.     // Create the rotation matrix
  4012.     $this->m[0][0] = $ca;
  4013.     $this->m[0][1] = -$sa;
  4014.     $this->m[0][2] = $this->dx*(1-$ca) + $sa*$this->dy ;
  4015.     $this->m[1][0] = $sa;
  4016.     $this->m[1][1] = $ca;
  4017.     $this->m[1][2] = $this->dy*(1-$ca) - $sa*$this->dx ;
  4018.     return $tmp;
  4019.     }
  4020.  
  4021.     function Circle($xc,$yc,$r) {
  4022.     list($xc,$yc) = $this->Rotate($xc,$yc);
  4023.     parent::Circle($xc,$yc,$r);
  4024.     }
  4025.  
  4026.     function FilledCircle($xc,$yc,$r) {
  4027.     list($xc,$yc) = $this->Rotate($xc,$yc);
  4028.     parent::FilledCircle($xc,$yc,$r);
  4029.     }
  4030.  
  4031.    
  4032.     function Arc($xc,$yc,$w,$h,$s,$e) {
  4033.     list($xc,$yc) = $this->Rotate($xc,$yc);
  4034.     parent::Arc($xc,$yc,$w,$h,$s,$e);
  4035.     }
  4036.  
  4037.     function FilledArc($xc,$yc,$w,$h,$s,$e) {
  4038.     list($xc,$yc) = $this->Rotate($xc,$yc);
  4039.     parent::FilledArc($xc,$yc,$w,$h,$s,$e);
  4040.     }
  4041.  
  4042.     function SetMargin($lm,$rm,$tm,$bm) {  
  4043.     parent::SetMargin($lm,$rm,$tm,$bm);
  4044.     $this->SetAngle($this->a);
  4045.     }
  4046.    
  4047.     function Rotate($x,$y) {
  4048.     $x1=round($this->m[0][0]*$x + $this->m[0][1]*$y + $this->m[0][2] + $this->transx);
  4049.     $y1=round($this->m[1][0]*$x + $this->m[1][1]*$y + $this->m[1][2] + $this->transy);
  4050.     return array($x1,$y1);
  4051.     }
  4052.    
  4053.     function ArrRotate($pnts) {
  4054.     for($i=0; $i < count($pnts)-1; $i+=2)
  4055.         list($pnts[$i],$pnts[$i+1]) = $this->Rotate($pnts[$i],$pnts[$i+1]);
  4056.     return $pnts;
  4057.     }
  4058.    
  4059.     function Line($x1,$y1,$x2,$y2) {
  4060.     list($x1,$y1) = $this->Rotate($x1,$y1);
  4061.     list($x2,$y2) = $this->Rotate($x2,$y2);
  4062.     parent::Line($x1,$y1,$x2,$y2);
  4063.     }
  4064.    
  4065.     function Rectangle($x1,$y1,$x2,$y2) {
  4066.     $this->Polygon(array($x1,$y1,$x2,$y1,$x2,$y2,$x1,$y2));
  4067.     }
  4068.    
  4069.     function FilledRectangle($x1,$y1,$x2,$y2) {
  4070.     if( $y1==$y2 || $x1==$x2 )
  4071.         $this->Line($x1,$y1,$x2,$y2);
  4072.     else
  4073.         $this->FilledPolygon(array($x1,$y1,$x2,$y1,$x2,$y2,$x1,$y2));
  4074.     }
  4075.    
  4076.     function Polygon($pnts) {
  4077.     //Polygon uses Line() so it will be rotated through that call
  4078.     parent::Polygon($pnts);
  4079.     }
  4080.    
  4081.     function FilledPolygon($pnts) {
  4082.     parent::FilledPolygon($this->ArrRotate($pnts));
  4083.     }
  4084.    
  4085.     function Point($x,$y) {
  4086.     list($xp,$yp) = $this->Rotate($x,$y);
  4087.     parent::Point($xp,$yp);
  4088.     }
  4089.    
  4090.     function DashedLine($x1,$y1,$x2,$y2,$length=1,$space=4) {
  4091.     list($x1,$y1) = $this->Rotate($x1,$y1);
  4092.     list($x2,$y2) = $this->Rotate($x2,$y2);
  4093.     parent::DashedLine($x1,$y1,$x2,$y2,$length,$space);
  4094.     }
  4095.    
  4096.     function StrokeText($x,$y,$txt,$dir=0,$paragraph_align="left") {
  4097.     list($xp,$yp) = $this->Rotate($x,$y);
  4098.     parent::StrokeText($xp,$yp,$txt,$dir,$paragraph_align);
  4099.     }
  4100. }
  4101.  
  4102. //===================================================
  4103. // CLASS ImgStreamCache
  4104. // Description: Handle caching of graphs to files
  4105. //===================================================
  4106. class ImgStreamCache {
  4107.     var $cache_dir;
  4108.     var $img=null;
  4109.     var $timeout=0;     // Infinite timeout
  4110.     //---------------
  4111.     // CONSTRUCTOR
  4112.     function ImgStreamCache(&$aImg, $aCacheDir=CACHE_DIR) {
  4113.     $this->img = &$aImg;
  4114.     $this->cache_dir = $aCacheDir;
  4115.     }
  4116.  
  4117. //---------------
  4118. // PUBLIC METHODS  
  4119.  
  4120.     // Specify a timeout (in minutes) for the file. If the file is older then the
  4121.     // timeout value it will be overwritten with a newer version.
  4122.     // If timeout is set to 0 this is the same as infinite large timeout and if
  4123.     // timeout is set to -1 this is the same as infinite small timeout
  4124.     function SetTimeout($aTimeout) {
  4125.     $this->timeout=$aTimeout;  
  4126.     }
  4127.    
  4128.     // Output image to browser and also write it to the cache
  4129.     function PutAndStream(&$aImage,$aCacheFileName,$aInline,$aStrokeFileName) {
  4130.     // Some debugging code to brand the image with numbe of colors
  4131.     // used
  4132.  
  4133.     if( JPG_DEBUG ) {
  4134.         $c=$aImage->SetColor("black");
  4135.         $t=imagecolorstotal($this->img->img);
  4136.         imagestring($this->img->img,2,5,$this->img->height-20,$t,$c);                  
  4137.     }
  4138.        
  4139.     if( BRAND_TIMING ) {
  4140.         global $tim;
  4141.         $t=$tim->Pop()/1000.0;
  4142.         $c=$aImage->SetColor("black");
  4143.         $t=sprintf(BRAND_TIME_FORMAT,round($t,3));
  4144.         imagestring($this->img->img,2,5,$this->img->height-20,$t,$c);          
  4145.     }
  4146.  
  4147.     // Check if we should stroke the image to an arbitrary file
  4148.     if( $aStrokeFileName!="" ) {
  4149.         if( $aStrokeFileName == "auto" )
  4150.         $aStrokeFileName = GenImgName();
  4151.         if( file_exists($aStrokeFileName) ) {
  4152.                 // Delete the old file
  4153.         if( !@unlink($aStrokeFileName) )
  4154.             JpGraphError::Raise(" Can't delete cached image $aStrokeFileName. Permission problem?");
  4155.         }
  4156.         $aImage->Stream($aStrokeFileName);
  4157.         return;
  4158.     }
  4159.  
  4160.     if( $aCacheFileName != "" && USE_CACHE) {
  4161.  
  4162.         $aCacheFileName = $this->cache_dir . $aCacheFileName;
  4163.         if( file_exists($aCacheFileName) ) {
  4164.         if( !$aInline ) {
  4165.             // If we are generating image off-line (just writing to the cache)
  4166.             // and the file exists and is still valid (no timeout)
  4167.             // then do nothing, just return.
  4168.             $diff=time()-filemtime($aCacheFileName);
  4169.             if( $diff < 0 )
  4170.             JpGraphError::Raise(" Cached imagefile ($aCacheFileName) has file date in the future!!");
  4171.             if( $this->timeout>0 && ($diff <= $this->timeout*60) )
  4172.             return;    
  4173.         }          
  4174.         if( !@unlink($aCacheFileName) )
  4175.             JpGraphError::Raise(" Can't delete cached image $aStrokeFileName. Permission problem?");
  4176.         $aImage->Stream($aCacheFileName);  
  4177.         }
  4178.         else {
  4179.         $this->_MakeDirs(dirname($aCacheFileName));
  4180.         $aImage->Stream($aCacheFileName);
  4181.         }
  4182.            
  4183.         $res=true;
  4184.         // Set group to specified
  4185.         if( CACHE_FILE_GROUP != "" )
  4186.         $res = @chgrp($aCacheFileName,CACHE_FILE_GROUP);
  4187.         if( CACHE_FILE_MOD != "" )
  4188.         $res = @chmod($aCacheFileName,CACHE_FILE_MOD);
  4189.         if( !$res )
  4190.         JpGraphError::Raise(" Can't set permission for cached image $aStrokeFileName. Permission problem?");
  4191.            
  4192.         $aImage->Destroy();
  4193.         if( $aInline ) {
  4194.         if ($fh = @fopen($aCacheFileName, "rb") ) {
  4195.             $this->img->Headers();
  4196.             fpassthru($fh);
  4197.             return;
  4198.         }
  4199.         else
  4200.             JpGraphError::Raise(" Cant open file from cache [$aFile]");
  4201.         }
  4202.     }
  4203.     elseif( $aInline ) {
  4204.         $this->img->Headers();         
  4205.         $aImage->Stream(); 
  4206.         return;
  4207.     }
  4208.     }
  4209.    
  4210.     // Check if a given image is in cache and in that case
  4211.     // pass it directly on to web browser. Return false if the
  4212.     // image file doesn't exist or exists but is to old
  4213.     function GetAndStream($aCacheFileName) {
  4214.     $aCacheFileName = $this->cache_dir.$aCacheFileName;      
  4215.     if ( USE_CACHE && file_exists($aCacheFileName) && $this->timeout>=0 ) {
  4216.         $diff=time()-filemtime($aCacheFileName);
  4217.         if( $this->timeout>0 && ($diff > $this->timeout*60) ) {
  4218.         return false;      
  4219.         }
  4220.         else {
  4221.         if ($fh = @fopen($aCacheFileName, "rb")) {
  4222.             $this->img->Headers();
  4223.             fpassthru($fh);
  4224.             fclose($fh);
  4225.             return true;
  4226.         }
  4227.         else
  4228.             JpGraphError::Raise(" Can't open cached image \"$aCacheFileName\" for reading.");
  4229.         }
  4230.     }
  4231.     return false;
  4232.     }
  4233.    
  4234.     //---------------
  4235.     // PRIVATE METHODS 
  4236.     // Create all necessary directories in a path
  4237.     function _MakeDirs($aFile) {
  4238.     $dirs = array();
  4239.     while (! (file_exists($aFile))) {
  4240.         $dirs[] = $aFile;
  4241.         $aFile = dirname($aFile);
  4242.     }
  4243.     for ($i = sizeof($dirs)-1; $i>=0; $i--) {
  4244.         if(! @mkdir($dirs[$i],0777) )
  4245.         JpGraphError::Raise(" Can't create directory in $aFile. Permission problems?");
  4246.                
  4247.         // We also specify mode here after we have changed group.
  4248.         // This is necessary if Apache user doesn't belong the
  4249.         // default group and hence can't specify group permission
  4250.         // in the previous mkdir() call
  4251.         if( CACHE_FILE_GROUP != "" ) {
  4252.         $res=true;
  4253.         $res =@chgrp($dirs[$i],CACHE_FILE_GROUP);
  4254.         $res &= @chmod($dirs[$i],0777);
  4255.         if( !$res )
  4256.             JpGraphError::Raise(" Can't set permissions for $aFile. Permission problems?");
  4257.         }
  4258.     }
  4259.     return true;
  4260.     }  
  4261. } // CLASS Cache
  4262.    
  4263. //===================================================
  4264. // CLASS Legend
  4265. // Description: Responsible for drawing the box containing
  4266. // all the legend text for the graph
  4267. //===================================================
  4268. class Legend {
  4269.     var $color=array(0,0,0); // Default fram color
  4270.     var $fill_color=array(235,235,235); // Default fill color
  4271.     var $shadow=true; // Shadow around legend "box"
  4272.     var $txtcol=array();
  4273.     var $mark_abs_size=10,$xmargin=5,$ymargin=5,$shadow_width=2;
  4274.     var $xpos=0.05, $ypos=0.15, $halign="right", $valign="top";
  4275.     var $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12;
  4276.     var $hide=false,$layout=LEGEND_VERT;
  4277.     var $weight=1;
  4278. //---------------
  4279. // CONSTRUCTOR
  4280.     function Legend() {
  4281.     // Empty
  4282.     }
  4283. //---------------
  4284. // PUBLIC METHODS  
  4285.     function Hide($aHide=true) {
  4286.     $this->hide=$aHide;
  4287.     }
  4288.    
  4289.     function SetShadow($aShow=true,$aWidth=2) {
  4290.     $this->shadow=$aShow;
  4291.     $this->shadow_width=$aWidth;
  4292.     }
  4293.    
  4294.     function SetLineWeight($aWeight) {
  4295.     $this->weight = $aWeight;
  4296.     }
  4297.    
  4298.     function SetLayout($aDirection=LEGEND_VERT) {
  4299.     $this->layout=$aDirection;
  4300.     }
  4301.    
  4302.     // Set color on frame around box
  4303.     function SetColor($aColor) {
  4304.     $this->color=$aColor;
  4305.     }
  4306.    
  4307.     function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
  4308.     $this->font_family = $aFamily;
  4309.     $this->font_style = $aStyle;
  4310.     $this->font_size = $aSize;
  4311.     }
  4312.    
  4313.     function Pos($aX,$aY,$aHAlign="right",$aVAlign="top") {
  4314.     if( !($aX<1 && $aY<1) )
  4315.         JpGraphError::Raise(" Position for legend must be given as percentage in range 0-1");
  4316.     $this->xpos=$aX;
  4317.     $this->ypos=$aY;
  4318.     $this->halign=$aHAlign;
  4319.     $this->valign=$aVAlign;
  4320.     }
  4321.  
  4322.     function SetBackground($aDummy) {
  4323.     JpGraphError::Raise(" Deprecated function Legend::SetBackground() use Legend::SetFillColor() instead.");
  4324.     }
  4325.  
  4326.     function SetFillColor($aColor) {
  4327.     $this->fill_color=$aColor;
  4328.     }
  4329.    
  4330.     function Add($aTxt,$aColor,$aPlotmark="",$aLinestyle=1) {
  4331.     $this->txtcol[]=array($aTxt,$aColor,$aPlotmark,$aLinestyle);
  4332.     }
  4333.    
  4334.     function Stroke(&$aImg) {
  4335.     if( $this->hide ) return;
  4336.  
  4337.     $nbrplots=count($this->txtcol);
  4338.     if( $nbrplots==0 ) return;
  4339.        
  4340.     $aImg->SetFont($this->font_family,$this->font_style,$this->font_size); 
  4341.     if( $this->layout==LEGEND_VERT )       
  4342.         $abs_height=$aImg->GetFontHeight() + $this->mark_abs_size*$nbrplots +
  4343.         $this->ymargin*($nbrplots-1);
  4344.     else
  4345.         $abs_height=2*$this->mark_abs_size+$this->ymargin;
  4346.                        
  4347.     if( $this->shadow ) $abs_height += $this->shadow_width;
  4348.     $mtw=0;
  4349.     foreach($this->txtcol as $p) {
  4350.         if( $this->layout==LEGEND_VERT )
  4351.         $mtw=max($mtw,$aImg->GetTextWidth($p[0]));
  4352.         else
  4353.         $mtw+=$aImg->GetTextWidth($p[0])+$this->mark_abs_size+$this->xmargin;
  4354.     }
  4355.     $abs_width=$mtw+2*$this->mark_abs_size+2*$this->xmargin;
  4356.     if( $this->halign=="left" )
  4357.         $xp=$this->xpos*$aImg->width;
  4358.     elseif( $this->halign=="center" )
  4359.         $xp=$this->xpos*$aImg->width - $abs_width/2;
  4360.     else  
  4361.         $xp = $aImg->width - $this->xpos*$aImg->width - $abs_width;
  4362.     $yp=$this->ypos*$aImg->height;
  4363.     if( $this->valign=="center" )
  4364.         $yp-=$abs_height/2;
  4365.     elseif( $this->valign=="bottom" )
  4366.         $yp-=$abs_height;
  4367.            
  4368.     $aImg->SetColor($this->color);             
  4369.     $aImg->SetLineWeight($this->weight);
  4370.     if( $this->shadow )
  4371.         $aImg->ShadowRectangle($xp,$yp,$xp+$abs_width,$yp+$abs_height,$this->fill_color,$this->shadow_width);
  4372.     else {
  4373.         $aImg->SetColor($this->fill_color);            
  4374.         $aImg->FilledRectangle($xp,$yp,$xp+$abs_width,$yp+$abs_height);
  4375.         $aImg->SetColor($this->color);                         
  4376.         $aImg->Rectangle($xp,$yp,$xp+$abs_width,$yp+$abs_height);
  4377.     }
  4378.     $aImg->SetLineWeight(1);
  4379.     $x1=$xp+$this->mark_abs_size/2;
  4380.     $y1=$yp+$aImg->GetFontHeight()*0.5;
  4381.     foreach($this->txtcol as $p) {
  4382.         $aImg->SetColor($p[1]);
  4383.         if ( $p[2] != "" && $p[2]->GetType() > -1 ) {
  4384.         $p[2]->Stroke($aImg,$x1+$this->mark_abs_size/2,$y1+$aImg->GetFontHeight()/2);
  4385.         }
  4386.         elseif ( $p[2] != "" ) {
  4387.         $aImg->SetLineStyle($p[3]);
  4388.         $aImg->StyleLine($x1,$y1+$aImg->GetFontHeight()/2,$x1+$this->mark_abs_size,$y1+$aImg->GetFontHeight()/2);
  4389.         $aImg->StyleLine($x1,$y1+$aImg->GetFontHeight()/2-1,$x1+$this->mark_abs_size,$y1+$aImg->GetFontHeight()/2-1);
  4390.         }
  4391.         else {
  4392.         $aImg->FilledRectangle($x1,$y1,$x1+$this->mark_abs_size,$y1+$this->mark_abs_size);
  4393.         $aImg->SetColor($this->color);
  4394.         $aImg->Rectangle($x1,$y1,$x1+$this->mark_abs_size,$y1+$this->mark_abs_size);
  4395.         }
  4396.         $aImg->SetColor($this->color);
  4397.         $aImg->SetTextAlign("left");           
  4398.         $aImg->StrokeText($x1+$this->mark_abs_size+$this->xmargin,$y1+$this->mark_abs_size,$p[0]);
  4399.         if( $this->layout==LEGEND_VERT )
  4400.         $y1 += $this->ymargin+$this->mark_abs_size;
  4401.         else
  4402.         $x1 += 2*$this->ymargin+$this->mark_abs_size+$aImg->GetTextWidth($p[0]);
  4403.     }                                                                            
  4404.     }
  4405. } // Class
  4406.    
  4407.  
  4408. //===================================================
  4409. // CLASS DisplayValue
  4410. // Description: Used to print data values at data points
  4411. //===================================================
  4412. class DisplayValue {    
  4413.     var $show=false,$format="%.1f",$negformat="";
  4414.     var $angle=0;
  4415.     var $ff=FF_FONT1,$fs=FS_NORMAL,$fsize=10;
  4416.     var $color="navy",$negcolor="";
  4417.     var $margin=5,$valign="",$halign="center";
  4418.  
  4419.     function Show($f=true) {
  4420.     $this->show=$f;
  4421.     }
  4422.  
  4423.     function SetColor($color,$negcolor="") {
  4424.     $this->color = $color;
  4425.     $this->negcolor = $negcolor;
  4426.     }
  4427.  
  4428.     function SetFont($ff,$fs=FS_NORMAL,$fsize=10) {
  4429.     $this->ff=$ff;
  4430.     $this->fs=$fs;
  4431.     $this->fsize=$fsize;
  4432.     }
  4433.  
  4434.     function SetMargin($m) {
  4435.     $this->margin = $m;
  4436.     }
  4437.  
  4438.     function SetAngle($a) {
  4439.     $this->angle = $a;
  4440.     }
  4441.  
  4442.     function SetVAlign($aVAlign) {
  4443.     $this->valign = $aVAlign;
  4444.     }
  4445.  
  4446.     function SetFormat($format,$negformat="") {
  4447.     $this->format= $format;
  4448.     $this->negformat= $negformat;
  4449.     }
  4450.  
  4451.     function Stroke($img,$aVal,$x,$y) {
  4452.     if( $this->show )
  4453.     {
  4454.         if( $this->negformat=="" ) $this->negformat=$this->format;
  4455.         if( $this->negcolor=="" ) $this->negcolor=$this->color;
  4456.  
  4457.         if( $aVal==NULL || (is_string($aVal) && ($aVal=="" || $aVal=="-" || $aVal=="x" ) ) )
  4458.         return;
  4459.         if( $aVal >= 0 )
  4460.         $sval=sprintf($this->format,$aVal);
  4461.         else
  4462.         $sval=sprintf($this->negformat,$aVal);
  4463.         $txt = new Text($sval,$x,$y-sign($aVal)*$this->margin);
  4464.         $txt->SetFont($this->ff,$this->fs,$this->fsize);
  4465.         if( $this->valign == "" ) {
  4466.         if( $aVal >= 0 )
  4467.             $valign = "bottom";
  4468.         else
  4469.             $valign = "top";
  4470.         }
  4471.         else
  4472.         $valign = $this->valign;
  4473.         $txt->Align($this->halign,$valign);
  4474.         $txt->SetOrientation($this->angle);
  4475.         if( $aVal > 0 )
  4476.         $txt->SetColor($this->color);
  4477.         else
  4478.         $txt->SetColor($this->negcolor);
  4479.         $txt->Stroke($img);
  4480.     }
  4481.     }
  4482. }
  4483.  
  4484.  
  4485. //===================================================
  4486. // CLASS Plot
  4487. // Description: Abstract base class for all concrete plot classes
  4488. //===================================================
  4489. class Plot {
  4490.     var $line_weight=1;
  4491.     var $coords=array();
  4492.     var $legend="";
  4493.     var $csimtargets=array();   // Array of targets for CSIM
  4494.     var $csimareas="";          // Resultant CSIM area tags
  4495.     var $csimalts=null;         // ALT:s for corresponding target
  4496.     var $color="black";
  4497.     var $numpoints=0;
  4498.     var $weight=1; 
  4499.     var $value;
  4500. //---------------
  4501. // CONSTRUCTOR
  4502.     function Plot(&$aDatay,$aDatax=false) {
  4503.     $this->numpoints = count($aDatay);
  4504.     if( $this->numpoints==0 )
  4505.         JpGraphError::Raise(" Empty data array specified for plot. Must have at least one data point.");
  4506.     $this->coords[0]=$aDatay;
  4507.     if( is_array($aDatax) )
  4508.         $this->coords[1]=$aDatax;
  4509.     $this->value = new DisplayValue();
  4510.     }
  4511.  
  4512. //---------------
  4513. // PUBLIC METHODS  
  4514.  
  4515.     // Stroke the plot
  4516.     // "virtual" function which must be implemented by
  4517.     // the subclasses
  4518.     function Stroke(&$aImg,&$aXScale,&$aYScale) {
  4519.     JpGraphError::Raise("JpGraph: Stroke() must be implemented by concrete subclass to class Plot");
  4520.     }
  4521.  
  4522.     function StrokeDataValue($img,$aVal,$x,$y) {
  4523.     $this->value->Stroke($img,$aVal,$x,$y);
  4524.     }
  4525.    
  4526.     // Set href targets for CSIM   
  4527.     function SetCSIMTargets(&$aTargets,$aAlts=null) {
  4528.     $this->csimtargets=$aTargets;
  4529.     $this->csimalts=$aAlts;    
  4530.     }
  4531.    
  4532.     // Get all created areas
  4533.     function GetCSIMareas() {
  4534.     return $this->csimareas;
  4535.     }  
  4536.    
  4537.     // "Virtual" function which gets called before any scale
  4538.     // or axis are stroked used to do any plot specific adjustment
  4539.     function PreStrokeAdjust(&$aGraph) {
  4540.     if( substr($aGraph->axtype,0,4) == "text" && (isset($this->coords[1])) )
  4541.         JpGraphError::Raise("JpGraph: You can't use a text X-scale with specified X-coords. Use a \"int\" or \"lin\" scale instead.");
  4542.     return true;   
  4543.     }
  4544.    
  4545.     function SetWeight($aWeight) {
  4546.     $this->weight=$aWeight;
  4547.     }
  4548.    
  4549.     // Get minimum values in plot
  4550.     function Min() {
  4551.     if( isset($this->coords[1]) )
  4552.         $x=$this->coords[1];
  4553.     else
  4554.         $x="";
  4555.     if( $x != "" && count($x) > 0 )
  4556.         $xm=min($x);
  4557.     else
  4558.         $xm=0;
  4559.     $y=$this->coords[0];
  4560.     if( count($y) > 0 ) {
  4561.         $ym = $y[0];
  4562.         $cnt = count($y);
  4563.         $i=0;
  4564.         while( $i<$cnt && !is_numeric($ym=$y[$i]) )
  4565.         $i++;
  4566.         while( $i < $cnt) {
  4567.         if( is_numeric($y[$i]) )
  4568.             $ym=min($ym,$y[$i]);
  4569.         ++$i;
  4570.         }          
  4571.     }
  4572.     else
  4573.         $ym="";
  4574.     return array($xm,$ym);
  4575.     }
  4576.    
  4577.     // Get maximum value in plot
  4578.     function Max() {
  4579.     if( isset($this->coords[1]) )
  4580.         $x=$this->coords[1];
  4581.     else
  4582.         $x="";
  4583.  
  4584.     if( $x!="" && count($x) > 0 )
  4585.         $xm=max($x);
  4586.     else
  4587.         $xm=count($this->coords[0])-1;  // We count from 0..(n-1)
  4588.     $y=$this->coords[0];
  4589.     if( count($y) > 0 ) {
  4590.         if( !isset($y[0]) ) {
  4591.         $y[0] = 0;
  4592. // Change in 1.5.1 Don't treat this as an error any more. Just silently concert to 0
  4593. //      JpGraphError::Raise(" You have not specified a y[0] value!!");
  4594.         }
  4595.         $cnt = count($y);
  4596.         $i=0;
  4597.         while( $i<$cnt && !is_numeric($ym=$y[$i]) )
  4598.         $i++;              
  4599.         while( $i < $cnt ) {
  4600.         if( is_numeric($y[$i]) ) $ym=max($ym,$y[$i]);
  4601.         ++$i;
  4602.         }
  4603.     }
  4604.     else
  4605.         $ym="";
  4606.     return array($xm,$ym);
  4607.     }
  4608.    
  4609.     function SetColor($aColor) {
  4610.     $this->color=$aColor;
  4611.     }
  4612.    
  4613.     function SetLegend($aLegend) {
  4614.     $this->legend = $aLegend;
  4615.     }
  4616.    
  4617.     function SetLineWeight($aWeight=1) {
  4618.     $this->line_weight=$aWeight;
  4619.     }
  4620.    
  4621.     // This method gets called by Graph class to plot anything that should go
  4622.     // into the margin after the margin color has been set.
  4623.     function StrokeMargin(&$aImg) {
  4624.     return true;
  4625.     }
  4626.  
  4627.     // Framework function the chance for each plot class to set a legend
  4628.     function Legend(&$aGraph) {
  4629.     if( $this->legend!="" )
  4630.         $aGraph->legend->Add($this->legend,$this->color);      
  4631.     }
  4632.    
  4633. } // Class
  4634.  
  4635.  
  4636. //===================================================
  4637. // CLASS PlotMark
  4638. // Description: Handles the plot marks in graphs
  4639. // mostly used in line and scatter plots.
  4640. //===================================================
  4641. class PlotMark {
  4642.     var $title, $show=true;
  4643.     var $type=-1, $weight=1;
  4644.     var $color="black", $width=5, $fill_color="blue";
  4645.     var $value,$csimtarget,$csimalt,$csimareas;
  4646. //  --------------
  4647. // CONSTRUCTOR
  4648.     function PlotMark() {
  4649.     $this->title = new Text();
  4650.     $this->title->Hide();
  4651.     $this->csimareas = '';
  4652.     }
  4653. //---------------
  4654. // PUBLIC METHODS  
  4655.     function SetType($t) {
  4656.     $this->type = $t;
  4657.     }
  4658.    
  4659.     function GetType() {
  4660.     return $this->type;
  4661.     }
  4662.    
  4663.     function SetColor($c) {
  4664.     $this->color=$c;
  4665.     }
  4666.    
  4667.     function SetFillColor($c) {
  4668.     $this->fill_color = $c;
  4669.     }
  4670.    
  4671.     function SetWidth($w) {
  4672.     $this->width=$w;
  4673.     }
  4674.    
  4675.     function GetWidth() {
  4676.     return $this->width;
  4677.     }
  4678.    
  4679.     function Hide($aHide=true) {
  4680.     $this->show = !$aHide;
  4681.     }
  4682.    
  4683.     function Show($aShow=true) {
  4684.     $this->show = $aShow;
  4685.     }
  4686.  
  4687.     function SetCSIMAltVal($aVal) {
  4688.         $this->value=$aVal;
  4689.     }
  4690.    
  4691.     function SetCSIMTarget($aTarget) {
  4692.         $this->csimtarget=$aTarget;
  4693.     }
  4694.    
  4695.     function SetCSIMAlt($aAlt) {
  4696.         $this->csimalt=$aAlt;
  4697.     }
  4698.    
  4699.     function GetCSIMAreas(){
  4700.         return $this->csimareas;
  4701.     }
  4702.        
  4703.     function AddCSIMPoly($aPts) {
  4704.         $coords = round($aPts[0]).", ".round($aPts[1]);
  4705.         $n = count($aPts)/2;
  4706.         for( $i=1; $i < $n; ++$i){
  4707.             $coords .= ", ".round($aPts[2*$i]).", ".round($aPts[2*$i+1]);
  4708.         }
  4709.         $this->csimareas="";    
  4710.         if( !empty($this->csimtarget) ) {
  4711.             $this->csimareas .= "<area shape=\"poly\" coords=\"$coords\" href=\"".$this->csimtarget."\"";
  4712.             if( !empty($this->csimalt) ) {                                     
  4713.                 $tmp=sprintf($this->csimalt,$this->value);
  4714.                 $this->csimareas .= " alt=\"$tmp\" title=\"$tmp\"";
  4715.             }
  4716.             $this->csimareas .= ">\r\n";
  4717.         }
  4718.     }
  4719.    
  4720.     function AddCSIMCircle($x,$y,$r) {
  4721.         $x = round($x); $y=round($y); $r=round($r);
  4722.         $this->csimareas="";    
  4723.         if( !empty($this->csimtarget) ) {
  4724.             $this->csimareas .= "<area shape=\"circle\" coords=\"$x,$y,$r\" href=\"".$this->csimtarget."\"";
  4725.             if( !empty($this->csimalt) ) {                                     
  4726.                 $tmp=sprintf($this->csimalt,$this->value);
  4727.                 $this->csimareas .= " alt=\"$tmp\" title=\"$tmp\"";
  4728.             }
  4729.             $this->csimareas .= ">\r\n";        
  4730.         }
  4731.     }
  4732.        
  4733.     function Stroke(&$img,$x,$y) {
  4734.     if( !$this->show ) return;
  4735.     $dx=round($this->width/2,0);
  4736.     $dy=round($this->width/2,0);
  4737.     $pts=0;    
  4738.     switch( $this->type ) {
  4739.         case MARK_SQUARE:
  4740.         $c[]=$x-$dx;$c[]=$y-$dy;
  4741.         $c[]=$x+$dx;$c[]=$y-$dy;
  4742.         $c[]=$x+$dx;$c[]=$y+$dy;
  4743.         $c[]=$x-$dx;$c[]=$y+$dy;
  4744.         $pts=4;
  4745.         break;
  4746.         case MARK_UTRIANGLE:
  4747.         ++$dx;++$dy;
  4748.         $c[]=$x-$dx;$c[]=$y+0.87*$dy;   // tan(60)/2*$dx
  4749.         $c[]=$x;$c[]=$y-0.87*$dy;
  4750.         $c[]=$x+$dx;$c[]=$y+0.87*$dy;
  4751.         $pts=3;
  4752.         break;
  4753.         case MARK_DTRIANGLE:
  4754.         ++$dx;++$dy;           
  4755.         $c[]=$x;$c[]=$y+0.87*$dy;   // tan(60)/2*$dx
  4756.         $c[]=$x-$dx;$c[]=$y-0.87*$dy;
  4757.         $c[]=$x+$dx;$c[]=$y-0.87*$dy;
  4758.         $pts=3;
  4759.         break;             
  4760.         case MARK_DIAMOND:
  4761.         $c[]=$x;$c[]=$y+$dy;
  4762.         $c[]=$x-$dx;$c[]=$y;
  4763.         $c[]=$x;$c[]=$y-$dy;
  4764.         $c[]=$x+$dx;$c[]=$y;
  4765.         $pts=4;
  4766.         break;             
  4767.     }
  4768.     if( $pts>0 ) {
  4769.         $this->AddCSIMPoly($c);
  4770.         $img->SetLineWeight($this->weight);
  4771.         $img->SetColor($this->fill_color);                             
  4772.         $img->FilledPolygon($c);
  4773.         $img->SetColor($this->color);                  
  4774.         $img->Polygon($c);
  4775.     }
  4776.     elseif( $this->type==MARK_CIRCLE ) {
  4777.         $img->SetColor($this->color);                  
  4778.         $img->Circle($x,$y,$this->width);
  4779.         $this->AddCSIMCircle($x,$y,$this->width);
  4780.     }
  4781.     elseif( $this->type==MARK_FILLEDCIRCLE ) {
  4782.         $img->SetColor($this->fill_color);     
  4783.         $img->FilledCircle($x,$y,$this->width);
  4784.         $img->SetColor($this->color);      
  4785.         $img->Circle($x,$y,$this->width);
  4786.         $this->AddCSIMCircle($x,$y,$this->width);      
  4787.     }
  4788.     elseif( $this->type==MARK_CROSS ) {
  4789.         // Oversize by a pixel to match the X
  4790.         $img->SetColor($this->color);
  4791.         $img->SetLineWeight($this->weight);
  4792.         $img->Line($x,$y+$dy+1,$x,$y-$dy-1);
  4793.         $img->Line($x-$dx-1,$y,$x+$dx+1,$y);
  4794.         $this->AddCSIMCircle($x,$y,$dx);       
  4795.     }
  4796.     elseif( $this->type==MARK_X ) {
  4797.         $img->SetColor($this->color);
  4798.         $img->SetLineWeight($this->weight);
  4799.         $img->Line($x+$dx,$y+$dy,$x-$dx,$y-$dy);
  4800.         $img->Line($x-$dx,$y+$dy,$x+$dx,$y-$dy);       
  4801.         $this->AddCSIMCircle($x,$y,$dx+$dy);               
  4802.     }          
  4803.     elseif( $this->type==MARK_STAR ) {
  4804.         $img->SetColor($this->color);
  4805.         $img->SetLineWeight($this->weight);
  4806.         $img->Line($x+$dx,$y+$dy,$x-$dx,$y-$dy);
  4807.         $img->Line($x-$dx,$y+$dy,$x+$dx,$y-$dy);
  4808.         // Oversize by a pixel to match the X              
  4809.         $img->Line($x,$y+$dy+1,$x,$y-$dy-1);
  4810.         $img->Line($x-$dx-1,$y,$x+$dx+1,$y);
  4811.         $this->AddCSIMCircle($x,$y,$dx+$dy);       
  4812.     }
  4813.        
  4814.     // Stroke title
  4815.     $this->title->Align("center","center");
  4816.     $this->title->Stroke($img,$x,$y);          
  4817.     }
  4818. } // Class
  4819.  
  4820. //==============================================================================
  4821. // The following section contains classes to implement the "band" functionality
  4822. //==============================================================================
  4823.  
  4824. // Utility class to hold coordinates for a rectangle
  4825. class Rectangle {
  4826.     var $x,$y,$w,$h;
  4827.     var $xe, $ye;
  4828.     function Rectangle($aX,$aY,$aWidth,$aHeight) {
  4829.     $this->x=$aX;
  4830.     $this->y=$aY;
  4831.     $this->w=$aWidth;
  4832.     $this->h=$aHeight;
  4833.     $this->xe=$aX+$aWidth-1;
  4834.     $this->ye=$aY+$aHeight-1;
  4835.     }
  4836. }
  4837.  
  4838. //=====================================================================
  4839. // Class RectPattern
  4840. // Base class for pattern hierarchi that is used to display patterned
  4841. // bands on the graph. Any subclass that doesn't override Stroke()
  4842. // must at least implement method DoPattern(&$aImg) which is responsible
  4843. // for drawing the pattern onto the graph.
  4844. //=====================================================================
  4845. class RectPattern {
  4846.     var $color;
  4847.     var $weight;
  4848.     var $rect=null;
  4849.     var $doframe=true;
  4850.     var $linespacing;   // Line spacing in pixels
  4851.     var $iBackgroundColor=-1;  // Default is no background fill
  4852.    
  4853.     function RectPattern($aColor,$aWeight=1) {
  4854.     $this->color = $aColor;
  4855.     $this->weight = $aWeight;      
  4856.     }
  4857.  
  4858.     function SetBackground($aBackgroundColor) {
  4859.     $this->iBackgroundColor=$aBackgroundColor;
  4860.     }
  4861.  
  4862.     function SetPos(&$aRect) {
  4863.     $this->rect = $aRect;
  4864.     }
  4865.    
  4866.     function ShowFrame($aShow=true) {
  4867.     $this->doframe=$aShow;
  4868.     }
  4869.  
  4870.     function SetDensity($aDens) {
  4871.     if( $aDens <1 || $aDens > 100 )
  4872.         JpGraphError::Raise(" Desity for pattern must be between 1 and 100. (You tried $aDens)");
  4873.     // 1% corresponds to linespacing=50
  4874.     // 100 % corresponds to linespacing 1
  4875.     $this->linespacing = floor(((100-$aDens)/100.0)*50)+1;
  4876.  
  4877.     }
  4878.  
  4879.     function Stroke(&$aImg) {
  4880.     if( $this->rect == null )
  4881.         JpGraphError::Raise(" No positions specified for pattern.");
  4882.  
  4883.     if( !(is_numeric($this->iBackgroundColor) && $this->iBackgroundColor==-1) ) {
  4884.         $aImg->SetColor($this->iBackgroundColor);
  4885.         $aImg->FilledRectangle($this->rect->x,$this->rect->y,$this->rect->xe,$this->rect->ye);
  4886.     }
  4887.  
  4888.     $aImg->SetColor($this->color);
  4889.     $aImg->SetLineWeight($this->weight);
  4890.  
  4891.     // Virtual function implemented by subclass
  4892.     $this->DoPattern($aImg);
  4893.  
  4894.     // Frame around the pattern area
  4895.     if( $this->doframe )
  4896.         $aImg->Rectangle($this->rect->x,$this->rect->y,$this->rect->xe,$this->rect->ye);
  4897.     }
  4898.  
  4899. }
  4900.  
  4901.  
  4902. //=====================================================================
  4903. // Class RectPatternSolid
  4904. // Implements a solid band
  4905. //=====================================================================
  4906. class RectPatternSolid extends RectPattern {
  4907.  
  4908.     function RectPatternSolid($aColor="black",$aWeight=1) {
  4909.     parent::RectPattern($aColor,$aWeight);
  4910.     }
  4911.  
  4912.     function Stroke(&$aImg) {
  4913.     $aImg->SetColor($this->color);
  4914.     $aImg->FilledRectangle($this->rect->x,$this->rect->y,
  4915.     $this->rect->xe,$this->rect->ye);
  4916.     }
  4917. }
  4918.  
  4919. //=====================================================================
  4920. // Class RectPatternHor
  4921. // Implements horizontal line pattern
  4922. //=====================================================================
  4923. class RectPatternHor extends RectPattern {
  4924.        
  4925.     function RectPatternHor($aColor="black",$aWeight=1,$aLineSpacing=7) {
  4926.     parent::RectPattern($aColor,$aWeight);
  4927.     $this->linespacing = $aLineSpacing;
  4928.     }
  4929.        
  4930.     function DoPattern(&$aImg) {
  4931.     $x0 = $this->rect->x;      
  4932.     $x1 = $this->rect->xe;
  4933.     $y = $this->rect->y;
  4934.     while( $y < $this->rect->ye ) {
  4935.         $aImg->Line($x0,$y,$x1,$y);
  4936.         $y += $this->linespacing;
  4937.     }
  4938.     }
  4939. }
  4940.  
  4941. //=====================================================================
  4942. // Class RectPatternVert
  4943. // Implements vertical line pattern
  4944. //=====================================================================
  4945. class RectPatternVert extends RectPattern {
  4946.     var $linespacing=10;    // Line spacing in pixels
  4947.        
  4948.     function RectPatternVert($aColor="black",$aWeight=1,$aLineSpacing=7) {
  4949.     parent::RectPattern($aColor,$aWeight);
  4950.     $this->linespacing = $aLineSpacing;
  4951.     }
  4952.  
  4953.     //--------------------
  4954.     // Private methods
  4955.     //
  4956.     function DoPattern(&$aImg) {
  4957.     $x = $this->rect->x;       
  4958.     $y0 = $this->rect->y;
  4959.     $y1 = $this->rect->ye;
  4960.     while( $x < $this->rect->xe ) {
  4961.         $aImg->Line($x,$y0,$x,$y1);
  4962.         $x += $this->linespacing;
  4963.     }
  4964.     }
  4965. }
  4966.  
  4967.  
  4968. //=====================================================================
  4969. // Class RectPatternRDiag
  4970. // Implements right diagonal pattern
  4971. //=====================================================================
  4972. class RectPatternRDiag extends RectPattern {
  4973.     var $linespacing;   // Line spacing in pixels
  4974.        
  4975.     function RectPatternRDiag($aColor="black",$aWeight=1,$aLineSpacing=12) {
  4976.     parent::RectPattern($aColor,$aWeight);
  4977.     $this->linespacing = $aLineSpacing;
  4978.     }
  4979.  
  4980.     function DoPattern(&$aImg) {
  4981.     //  --------------------
  4982.     //  | /   /   /   /   /|
  4983.     //  |/   /   /   /   / |
  4984.     //  |   /   /   /   /  |
  4985.     //  --------------------
  4986.     $xe = $this->rect->xe;
  4987.     $ye = $this->rect->ye;
  4988.     $x0 = $this->rect->x + round($this->linespacing/2);
  4989.     $y0 = $this->rect->y;
  4990.     $x1 = $this->rect->x;
  4991.     $y1 = $this->rect->y + round($this->linespacing/2);
  4992.  
  4993.     while($x0<=$xe && $y1<=$ye) {
  4994.         $aImg->Line($x0,$y0,$x1,$y1);
  4995.         $x0 += $this->linespacing;
  4996.         $y1 += $this->linespacing;
  4997.     }
  4998.  
  4999.     $x1 = $this->rect->x + ($y1-$ye);
  5000.     //$x1 = $this->rect->x +$this->linespacing;
  5001.     $y0=$this->rect->y; $y1=$ye;
  5002.     while( $x0 <= $xe ) {
  5003.         $aImg->Line($x0,$y0,$x1,$y1);
  5004.         $x0 += $this->linespacing;
  5005.         $x1 += $this->linespacing;
  5006.     }
  5007.  
  5008.     $y0=$this->rect->y + ($x0-$xe);
  5009.     $x0=$xe;
  5010.     while( $y0 <= $ye ) {
  5011.         $aImg->Line($x0,$y0,$x1,$y1);
  5012.         $y0 += $this->linespacing;
  5013.         $x1 += $this->linespacing;
  5014.     }
  5015.     }
  5016.  
  5017. }
  5018.  
  5019. //=====================================================================
  5020. // Class RectPatternLDiag
  5021. // Implements left diagonal pattern
  5022. //=====================================================================
  5023. class RectPatternLDiag extends RectPattern {
  5024.     var $linespacing;   // Line spacing in pixels
  5025.        
  5026.     function RectPatternLDiag($aColor="black",$aWeight=1,$aLineSpacing=12) {
  5027.     $this->linespacing = $aLineSpacing;
  5028.     parent::RectPattern($aColor,$aWeight);
  5029.     }
  5030.  
  5031.     function DoPattern(&$aImg) {
  5032.     //  --------------------
  5033.     //  |\   \   \   \   \ |
  5034.     //  | \   \   \   \   \|
  5035.     //  |  \   \   \   \   |
  5036.     //  |------------------|
  5037.     $xe = $this->rect->xe;
  5038.     $ye = $this->rect->ye;
  5039.     $x0 = $this->rect->x + round($this->linespacing/2);
  5040.     $y0 = $this->rect->ye;
  5041.     $x1 = $this->rect->x;
  5042.     $y1 = $this->rect->ye - round($this->linespacing/2);
  5043.  
  5044.     while($x0<=$xe && $y1>=$this->rect->y) {
  5045.         $aImg->Line($x0,$y0,$x1,$y1);
  5046.         $x0 += $this->linespacing;
  5047.         $y1 -= $this->linespacing;
  5048.     }
  5049.  
  5050.     $x1 = $this->rect->x + ($this->rect->y-$y1);
  5051.     $y0=$ye; $y1=$this->rect->y;
  5052.     while( $x0 <= $xe ) {
  5053.         $aImg->Line($x0,$y0,$x1,$y1);
  5054.         $x0 += $this->linespacing;
  5055.         $x1 += $this->linespacing;
  5056.     }
  5057.  
  5058.     $y0=$this->rect->ye - ($x0-$xe);
  5059.     $x0=$xe;
  5060.     while( $y0 >= $this->rect->y ) {
  5061.         $aImg->Line($x0,$y0,$x1,$y1);
  5062.         $y0 -= $this->linespacing;
  5063.         $x1 += $this->linespacing;
  5064.     }
  5065.     }
  5066. }
  5067.  
  5068. //=====================================================================
  5069. // Class RectPattern3DPlane
  5070. // Implements "3D" plane pattern
  5071. //=====================================================================
  5072. class RectPattern3DPlane extends RectPattern {
  5073.     var $alpha=50;  // Parameter that specifies the distance
  5074.     // to "simulated" horizon in pixel from the
  5075.     // top of the band. Specifies how fast the lines
  5076.     // converge.
  5077.  
  5078.     function RectPattern3DPlane($aColor="black",$aWeight=1) {
  5079.     parent::RectPattern($aColor,$aWeight);
  5080.     $this->SetDensity(10);  // Slightly larger default
  5081.     }
  5082.  
  5083.     function SetHorizon($aHorizon) {
  5084.     $this->alpha=$aHorizon;
  5085.     }
  5086.    
  5087.     function DoPattern(&$aImg) {
  5088.     // "Fake" a nice 3D grid-effect.
  5089.     $x0 = $this->rect->x + $this->rect->w/2;
  5090.     $y0 = $this->rect->y;
  5091.     $x1 = $x0;
  5092.     $y1 = $this->rect->ye;
  5093.     $x0_right = $x0;
  5094.     $x1_right = $x1;
  5095.  
  5096.     // BTW "apa" means monkey in Swedish but is really a shortform for
  5097.     // "alpha+a" which was the labels I used on paper when I derived the
  5098.     // geometric to get the 3D perspective right.
  5099.     // $apa is the height of the bounding rectangle plus the distance to the
  5100.     // artifical horizon (alpha)
  5101.     $apa = $this->rect->h + $this->alpha;
  5102.  
  5103.     // Three cases and three loops
  5104.     // 1) The endpoint of the line ends on the bottom line
  5105.     // 2) The endpoint ends on the side
  5106.     // 3) Horizontal lines
  5107.  
  5108.     // Endpoint falls on bottom line
  5109.     $middle=$this->rect->x + $this->rect->w/2;
  5110.     $dist=$this->linespacing;
  5111.     $factor=$this->alpha /($apa);
  5112.     while($x1>$this->rect->x) {
  5113.         $aImg->Line($x0,$y0,$x1,$y1);
  5114.         $aImg->Line($x0_right,$y0,$x1_right,$y1);
  5115.         $x1 = $middle - $dist;
  5116.         $x0 = $middle - $dist * $factor;
  5117.         $x1_right = $middle + $dist;
  5118.         $x0_right =  $middle + $dist * $factor;
  5119.         $dist += $this->linespacing;
  5120.     }
  5121.  
  5122.     // Endpoint falls on sides
  5123.     $dist -= $this->linespacing;
  5124.     $d=$this->rect->w/2;
  5125.     $c = $apa - $d*$apa/$dist;
  5126.     while( $x0>$this->rect->x ) {
  5127.         $aImg->Line($x0,$y0,$this->rect->x,$this->rect->ye-$c);
  5128.         $aImg->Line($x0_right,$y0,$this->rect->xe,$this->rect->ye-$c);
  5129.         $dist += $this->linespacing;           
  5130.         $x0 = $middle - $dist * $factor;
  5131.         $x1 = $middle - $dist;
  5132.         $x0_right =  $middle + $dist * $factor;        
  5133.         $c = $apa - $d*$apa/$dist;
  5134.     }      
  5135.        
  5136.     // Horizontal lines
  5137.     // They need some serious consideration since they are a function
  5138.     // of perspective depth (alpha) and density (linespacing)
  5139.     $x0=$this->rect->x;
  5140.     $x1=$this->rect->xe;
  5141.     $y=$this->rect->ye;
  5142.        
  5143.     // The first line is drawn directly. Makes the loop below slightly
  5144.     // more readable.
  5145.     $aImg->Line($x0,$y,$x1,$y);
  5146.     $hls = $this->linespacing;
  5147.        
  5148.     // A correction factor for vertical "brick" line spacing to account for
  5149.     // a) the difference in number of pixels hor vs vert
  5150.     // b) visual apperance to make the first layer of "bricks" look more
  5151.     // square.
  5152.     $vls = $this->linespacing*0.6;
  5153.        
  5154.     $ds = $hls*($apa-$vls)/$apa;
  5155.     // Get the slope for the "perspective line" going from bottom right
  5156.     // corner to top left corner of the "first" brick.
  5157.        
  5158.     // Uncomment the following lines if you want to get a visual understanding
  5159.     // of what this helpline does. BTW this mimics the way you would get the
  5160.     // perspective right when drawing on paper.
  5161.     /*
  5162.       $x0 = $middle;
  5163.       $y0 = $this->rect->ye;
  5164.       $len=floor(($this->rect->ye-$this->rect->y)/$vls);
  5165.       $x1 = $middle-round($len*$ds);
  5166.       $y1 = $this->rect->ye-$len*$vls;
  5167.       $aImg->PushColor("red");
  5168.       $aImg->Line($x0,$y0,$x1,$y1);
  5169.       $aImg->PopColor();
  5170.     */
  5171.        
  5172.     $y -= $vls;    
  5173.     $k=($this->rect->ye-($this->rect->ye-$vls))/($middle-($middle-$ds));
  5174.     $dist = $hls;
  5175.     while( $y>$this->rect->y ) {
  5176.         $aImg->Line($this->rect->x,$y,$this->rect->xe,$y);
  5177.         $adj = $k*$dist/(1+$dist*$k/$apa);
  5178.         if( $adj < 2 ) $adj=2;
  5179.         $y = $this->rect->ye - round($adj);
  5180.         $dist += $hls;
  5181.     }
  5182.     }
  5183. }
  5184.  
  5185. //=====================================================================
  5186. // Class RectPatternCross
  5187. // Vert/Hor crosses
  5188. //=====================================================================
  5189. class RectPatternCross extends RectPattern {
  5190.     var $vert=null;
  5191.     var $hor=null;
  5192.     function RectPatternCross($aColor="black",$aWeight=1) {
  5193.     parent::RectPattern($aColor,$aWeight);
  5194.     $this->vert = new RectPatternVert($aColor,$aWeight);
  5195.     $this->hor  = new RectPatternHor($aColor,$aWeight);
  5196.     }
  5197.  
  5198.     function SetOrder($aDepth) {
  5199.     $this->vert->SetOrder($aDepth);
  5200.     $this->hor->SetOrder($aDepth);
  5201.     }
  5202.  
  5203.     function SetPos(&$aRect) {
  5204.     parent::SetPos($aRect);
  5205.     $this->vert->SetPos($aRect);
  5206.     $this->hor->SetPos($aRect);
  5207.     }
  5208.  
  5209.     function SetDensity($aDens) {
  5210.     $this->vert->SetDensity($aDens);
  5211.     $this->hor->SetDensity($aDens);
  5212.     }
  5213.  
  5214.     function DoPattern(&$aImg) {
  5215.     $this->vert->DoPattern($aImg);
  5216.     $this->hor->DoPattern($aImg);
  5217.     }
  5218. }
  5219.  
  5220. //=====================================================================
  5221. // Class RectPatternDiagCross
  5222. // Vert/Hor crosses
  5223. //=====================================================================
  5224.  
  5225. class RectPatternDiagCross extends RectPattern {
  5226.     var $left=null;
  5227.     var $right=null;
  5228.     function RectPatternDiagCross($aColor="black",$aWeight=1) {
  5229.     parent::RectPattern($aColor,$aWeight);
  5230.     $this->right = new RectPatternRDiag($aColor,$aWeight);
  5231.     $this->left  = new RectPatternLDiag($aColor,$aWeight);
  5232.     }
  5233.  
  5234.     function SetOrder($aDepth) {
  5235.     $this->left->SetOrder($aDepth);
  5236.     $this->right->SetOrder($aDepth);
  5237.     }
  5238.  
  5239.     function SetPos(&$aRect) {
  5240.     parent::SetPos($aRect);
  5241.     $this->left->SetPos($aRect);
  5242.     $this->right->SetPos($aRect);
  5243.     }
  5244.  
  5245.     function SetDensity($aDens) {
  5246.     $this->left->SetDensity($aDens);
  5247.     $this->right->SetDensity($aDens);
  5248.     }
  5249.  
  5250.     function DoPattern(&$aImg) {
  5251.     $this->left->DoPattern($aImg);
  5252.     $this->right->DoPattern($aImg);
  5253.     }
  5254.  
  5255. }
  5256.  
  5257. //=====================================================================
  5258. // Class RectPatternFactory
  5259. // Factory class for rectangular pattern
  5260. //=====================================================================
  5261. class RectPatternFactory {
  5262.     function RectPatternFactory() {
  5263.     // Empty
  5264.     }
  5265.     function Create($aPattern,$aColor,$aWeight=1) {
  5266.     switch($aPattern) {
  5267.         case BAND_RDIAG:
  5268.         $obj =  new RectPatternRDiag($aColor,$aWeight);
  5269.         break;
  5270.         case BAND_LDIAG:
  5271.         $obj =  new RectPatternLDiag($aColor,$aWeight);
  5272.         break;
  5273.         case BAND_SOLID:
  5274.         $obj =  new RectPatternSolid($aColor,$aWeight);
  5275.         break;
  5276.         case BAND_LVERT:
  5277.         $obj =  new RectPatternVert($aColor,$aWeight);
  5278.         break;
  5279.         case BAND_LHOR:
  5280.         $obj =  new RectPatternHor($aColor,$aWeight);
  5281.         break;
  5282.         case BAND_3DPLANE:
  5283.         $obj =  new RectPattern3DPlane($aColor,$aWeight);
  5284.         break;
  5285.         case BAND_HVCROSS:
  5286.         $obj =  new RectPatternCross($aColor,$aWeight);
  5287.         break;
  5288.         case BAND_DIAGCROSS:
  5289.         $obj =  new RectPatternDiagCross($aColor,$aWeight);
  5290.         break;
  5291.         default:
  5292.         JpGraphError::Raise(" Unknown pattern specification ($aPattern)");
  5293.     }
  5294.     return $obj;
  5295.     }
  5296. }
  5297.  
  5298.  
  5299. //=====================================================================
  5300. // Class PlotBand
  5301. // Factory class which is used by the client.
  5302. // It is reposnsible for factoring the corresponding pattern
  5303. // concrete class.
  5304. //=====================================================================
  5305. class PlotBand {
  5306.     var $prect=null;
  5307.     var $depth;
  5308.  
  5309.     function PlotBand($aDir,$aPattern,$aMin,$aMax,$aColor="black",$aWeight=1,$aDepth=DEPTH_BACK) {
  5310.     $f =  new RectPatternFactory();
  5311.     $this->prect = $f->Create($aPattern,$aColor,$aWeight);
  5312.     $this->dir = $aDir;
  5313.     $this->min = $aMin;
  5314.     $this->max = $aMax;
  5315.     $this->depth=$aDepth;
  5316.     }
  5317.    
  5318.     // Set position. aRect contains absolute image coordinates
  5319.     function SetPos(&$aRect) {
  5320.     assert( $this->prect != null ) ;
  5321.     $this->prect->SetPos($aRect);
  5322.     }
  5323.    
  5324.     function ShowFrame($aFlag=true) {
  5325.     $this->prect->ShowFrame($aFlag);
  5326.     }
  5327.  
  5328.     // Set z-order. In front of pplot or in the back
  5329.     function SetOrder($aDepth) {
  5330.     $this->depth=$aDepth;
  5331.     }
  5332.    
  5333.     function SetDensity($aDens) {
  5334.     $this->prect->SetDensity($aDens);
  5335.     }
  5336.    
  5337.     function GetDir() {
  5338.     return $this->dir;
  5339.     }
  5340.    
  5341.     function GetMin() {
  5342.     return $this->min;
  5343.     }
  5344.    
  5345.     function GetMax() {
  5346.     return $this->max;
  5347.     }
  5348.    
  5349.     // Display band
  5350.     function Stroke(&$aImg,&$aXScale,&$aYScale) {
  5351.     assert( $this->prect != null ) ;
  5352.     if( $this->dir == HORIZONTAL ) {
  5353.         if( !is_numeric($this->min) && $this->min == "min" ) $this->min = $aYScale->GetMinVal();
  5354.         if( !is_numeric($this->max) && $this->max == "max" ) $this->max = $aYScale->GetMaxVal();
  5355.         $x=$aXScale->scale_abs[0];
  5356.         $y=$aYScale->Translate($this->max);
  5357.         $width=$aXScale->scale_abs[1]-$aXScale->scale_abs[0]+1;
  5358.         $height=abs($y-$aYScale->Translate($this->min))+1;
  5359.         $this->prect->SetPos(new Rectangle($x,$y,$width,$height));
  5360.     }
  5361.     else {  // VERTICAL
  5362.         if( !is_numeric($this->min) && $this->min == "min" ) $this->min = $aXScale->GetMinVal();
  5363.         if( !is_numeric($this->max) && $this->max == "max" ) $this->max = $aXScale->GetMaxVal();
  5364.         $y=$aYScale->scale_abs[1];
  5365.         $x=$aXScale->Translate($this->min);
  5366.         $height=abs($aYScale->scale_abs[1]-$aYScale->scale_abs[0]);
  5367.         $width=abs($x-$aXScale->Translate($this->max));
  5368.         $this->prect->SetPos(new Rectangle($x,$y,$width,$height));
  5369.     }
  5370.     $this->prect->Stroke($aImg);
  5371.     }
  5372. }
  5373.  
  5374. //===================================================
  5375. // CLASS PlotLine
  5376. // Description:
  5377. // Data container class to hold properties for a static
  5378. // line that is drawn directly in the plot area.
  5379. // Usefull to add static borders inside a plot to show
  5380. // for example set-values
  5381. //===================================================
  5382. class PlotLine {
  5383.     var $weight=1;
  5384.     var $color="black";
  5385.     var $direction=-1;
  5386.     var $scaleposition;
  5387.  
  5388. //---------------
  5389. // CONSTRUCTOR
  5390.     function PlotLine($aDir=HORIZONTAL,$aPos=0,$aColor="black",$aWeight=1) {
  5391.     $this->direction = $aDir;
  5392.     $this->color=$aColor;
  5393.     $this->weight=$aWeight;
  5394.     $this->scaleposition=$aPos;
  5395.     }
  5396.    
  5397. //---------------
  5398. // PUBLIC METHODS  
  5399.     function SetPosition($aScalePosition) {
  5400.     $this->scaleposition=$aScalePosition;
  5401.     }
  5402.    
  5403.     function SetDirection($aDir) {
  5404.     $this->direction = $aDir;
  5405.     }
  5406.    
  5407.     function SetColor($aColor) {
  5408.     $this->color=$aColor;
  5409.     }
  5410.    
  5411.     function SetWeight($aWeight) {
  5412.     $this->weight=$aWeight;
  5413.     }
  5414.    
  5415.     function Stroke(&$aImg,&$aXScale,&$aYScale) {
  5416.     $aImg->SetColor($this->color);
  5417.     $aImg->SetLineWeight($this->weight);       
  5418.     if( $this->direction == VERTICAL ) {
  5419.         $ymin_abs=$aYScale->Translate($aYScale->GetMinVal());
  5420.         $ymax_abs=$aYScale->Translate($aYScale->GetMaxVal());
  5421.         $xpos_abs=$aXScale->Translate($this->scaleposition);
  5422.         $aImg->Line($xpos_abs, $ymin_abs, $xpos_abs, $ymax_abs);
  5423.     }
  5424.     elseif( $this->direction == HORIZONTAL ) {
  5425.         $xmin_abs=$aXScale->Translate($aXScale->GetMinVal());
  5426.         $xmax_abs=$aXScale->Translate($aXScale->GetMaxVal());
  5427.         $ypos_abs=$aYScale->Translate($this->scaleposition);
  5428.         $aImg->Line($xmin_abs, $ypos_abs, $xmax_abs, $ypos_abs);
  5429.     }
  5430.     else
  5431.         JpGraphError::Raise(" Illegal direction for static line");
  5432.     }
  5433. }
  5434.  
  5435. // <EOF>
  5436. ?>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement