PageRenderTime 123ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 1ms

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

#
PHP | 1996 lines | 1357 code | 253 blank | 386 comment | 253 complexity | 6847c08d3f69661c7ac484a53243f33a 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 coordin