PageRenderTime 778ms CodeModel.GetById 50ms RepoModel.GetById 2ms app.codeStats 2ms

/tags/rel-1_4_5rc1-20050616/locales/support/smstats/includes/jpgraph.php

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