PageRenderTime 86ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 1ms

/bin/jpgraph.php

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