PageRenderTime 81ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/protected/Common/3rdParty/jpgraph/src/jpgraph.php

http://pradoportal.googlecode.com/
PHP | 5401 lines | 4060 code | 647 blank | 694 comment | 1008 complexity | e11c1a5aa6a07af326f5de49cd802d21 MD5 | raw file
  1. <?php
  2. //=======================================================================
  3. // File: JPGRAPH.PHP
  4. // Description: PHP Graph Plotting library. Base module.
  5. // Created: 2001-01-08
  6. // Ver: $Id: jpgraph.php 1924 2010-01-11 14:03:26Z ljp $
  7. //
  8. // Copyright (c) Aditus Consulting. All rights reserved.
  9. //========================================================================
  10. require_once('jpg-config.inc.php');
  11. require_once('jpgraph_gradient.php');
  12. require_once('jpgraph_errhandler.inc.php');
  13. require_once('jpgraph_ttf.inc.php');
  14. require_once('jpgraph_rgb.inc.php');
  15. require_once('jpgraph_text.inc.php');
  16. require_once('jpgraph_legend.inc.php');
  17. require_once('gd_image.inc.php');
  18. // Version info
  19. define('JPG_VERSION','3.0.7');
  20. // Minimum required PHP version
  21. define('MIN_PHPVERSION','5.1.0');
  22. // Special file name to indicate that we only want to calc
  23. // the image map in the call to Graph::Stroke() used
  24. // internally from the GetHTMLCSIM() method.
  25. define('_CSIM_SPECIALFILE','_csim_special_');
  26. // HTTP GET argument that is used with image map
  27. // to indicate to the script to just generate the image
  28. // and not the full CSIM HTML page.
  29. define('_CSIM_DISPLAY','_jpg_csimd');
  30. // Special filename for Graph::Stroke(). If this filename is given
  31. // then the image will NOT be streamed to browser of file. Instead the
  32. // Stroke call will return the handler for the created GD image.
  33. define('_IMG_HANDLER','__handle');
  34. // Special filename for Graph::Stroke(). If this filename is given
  35. // the image will be stroked to a file with a name based on the script name.
  36. define('_IMG_AUTO','auto');
  37. // Tick density
  38. define("TICKD_DENSE",1);
  39. define("TICKD_NORMAL",2);
  40. define("TICKD_SPARSE",3);
  41. define("TICKD_VERYSPARSE",4);
  42. // Side for ticks and labels.
  43. define("SIDE_LEFT",-1);
  44. define("SIDE_RIGHT",1);
  45. define("SIDE_DOWN",-1);
  46. define("SIDE_BOTTOM",-1);
  47. define("SIDE_UP",1);
  48. define("SIDE_TOP",1);
  49. // Legend type stacked vertical or horizontal
  50. define("LEGEND_VERT",0);
  51. define("LEGEND_HOR",1);
  52. // Mark types for plot marks
  53. define("MARK_SQUARE",1);
  54. define("MARK_UTRIANGLE",2);
  55. define("MARK_DTRIANGLE",3);
  56. define("MARK_DIAMOND",4);
  57. define("MARK_CIRCLE",5);
  58. define("MARK_FILLEDCIRCLE",6);
  59. define("MARK_CROSS",7);
  60. define("MARK_STAR",8);
  61. define("MARK_X",9);
  62. define("MARK_LEFTTRIANGLE",10);
  63. define("MARK_RIGHTTRIANGLE",11);
  64. define("MARK_FLASH",12);
  65. define("MARK_IMG",13);
  66. define("MARK_FLAG1",14);
  67. define("MARK_FLAG2",15);
  68. define("MARK_FLAG3",16);
  69. define("MARK_FLAG4",17);
  70. // Builtin images
  71. define("MARK_IMG_PUSHPIN",50);
  72. define("MARK_IMG_SPUSHPIN",50);
  73. define("MARK_IMG_LPUSHPIN",51);
  74. define("MARK_IMG_DIAMOND",52);
  75. define("MARK_IMG_SQUARE",53);
  76. define("MARK_IMG_STAR",54);
  77. define("MARK_IMG_BALL",55);
  78. define("MARK_IMG_SBALL",55);
  79. define("MARK_IMG_MBALL",56);
  80. define("MARK_IMG_LBALL",57);
  81. define("MARK_IMG_BEVEL",58);
  82. // Inline defines
  83. define("INLINE_YES",1);
  84. define("INLINE_NO",0);
  85. // Format for background images
  86. define("BGIMG_FILLPLOT",1);
  87. define("BGIMG_FILLFRAME",2);
  88. define("BGIMG_COPY",3);
  89. define("BGIMG_CENTER",4);
  90. define("BGIMG_FREE",5);
  91. // Depth of objects
  92. define("DEPTH_BACK",0);
  93. define("DEPTH_FRONT",1);
  94. // Direction
  95. define("VERTICAL",1);
  96. define("HORIZONTAL",0);
  97. // Axis styles for scientific style axis
  98. define('AXSTYLE_SIMPLE',1);
  99. define('AXSTYLE_BOXIN',2);
  100. define('AXSTYLE_BOXOUT',3);
  101. define('AXSTYLE_YBOXIN',4);
  102. define('AXSTYLE_YBOXOUT',5);
  103. // Style for title backgrounds
  104. define('TITLEBKG_STYLE1',1);
  105. define('TITLEBKG_STYLE2',2);
  106. define('TITLEBKG_STYLE3',3);
  107. define('TITLEBKG_FRAME_NONE',0);
  108. define('TITLEBKG_FRAME_FULL',1);
  109. define('TITLEBKG_FRAME_BOTTOM',2);
  110. define('TITLEBKG_FRAME_BEVEL',3);
  111. define('TITLEBKG_FILLSTYLE_HSTRIPED',1);
  112. define('TITLEBKG_FILLSTYLE_VSTRIPED',2);
  113. define('TITLEBKG_FILLSTYLE_SOLID',3);
  114. // Styles for axis labels background
  115. define('LABELBKG_NONE',0);
  116. define('LABELBKG_XAXIS',1);
  117. define('LABELBKG_YAXIS',2);
  118. define('LABELBKG_XAXISFULL',3);
  119. define('LABELBKG_YAXISFULL',4);
  120. define('LABELBKG_XYFULL',5);
  121. define('LABELBKG_XY',6);
  122. // Style for background gradient fills
  123. define('BGRAD_FRAME',1);
  124. define('BGRAD_MARGIN',2);
  125. define('BGRAD_PLOT',3);
  126. // Width of tab titles
  127. define('TABTITLE_WIDTHFIT',0);
  128. define('TABTITLE_WIDTHFULL',-1);
  129. // Defines for 3D skew directions
  130. define('SKEW3D_UP',0);
  131. define('SKEW3D_DOWN',1);
  132. define('SKEW3D_LEFT',2);
  133. define('SKEW3D_RIGHT',3);
  134. // For internal use only
  135. define("_JPG_DEBUG",false);
  136. define("_FORCE_IMGTOFILE",false);
  137. define("_FORCE_IMGDIR",'/tmp/jpgimg/');
  138. //
  139. // Automatic settings of path for cache and font directory
  140. // if they have not been previously specified
  141. //
  142. if(USE_CACHE) {
  143. if (!defined('CACHE_DIR')) {
  144. if ( strstr( PHP_OS, 'WIN') ) {
  145. if( empty($_SERVER['TEMP']) ) {
  146. $t = new ErrMsgText();
  147. $msg = $t->Get(11,$file,$lineno);
  148. die($msg);
  149. }
  150. else {
  151. define('CACHE_DIR', $_SERVER['TEMP'] . '/');
  152. }
  153. } else {
  154. define('CACHE_DIR','/tmp/jpgraph_cache/');
  155. }
  156. }
  157. }
  158. elseif( !defined('CACHE_DIR') ) {
  159. define('CACHE_DIR', '');
  160. }
  161. //
  162. // Setup path for western/latin TTF fonts
  163. //
  164. if (!defined('TTF_DIR')) {
  165. if (strstr( PHP_OS, 'WIN') ) {
  166. $sroot = getenv('SystemRoot');
  167. if( empty($sroot) ) {
  168. $t = new ErrMsgText();
  169. $msg = $t->Get(12,$file,$lineno);
  170. die($msg);
  171. }
  172. else {
  173. define('TTF_DIR', $sroot.'/fonts/');
  174. }
  175. } else {
  176. define('TTF_DIR','/usr/share/fonts/truetype/');
  177. }
  178. }
  179. //
  180. // Setup path for MultiByte TTF fonts (japanese, chinese etc.)
  181. //
  182. if (!defined('MBTTF_DIR')) {
  183. if (strstr( PHP_OS, 'WIN') ) {
  184. $sroot = getenv('SystemRoot');
  185. if( empty($sroot) ) {
  186. $t = new ErrMsgText();
  187. $msg = $t->Get(12,$file,$lineno);
  188. die($msg);
  189. }
  190. else {
  191. define('MBTTF_DIR', $sroot.'/fonts/');
  192. }
  193. } else {
  194. define('MBTTF_DIR','/usr/share/fonts/truetype/');
  195. }
  196. }
  197. //
  198. // Check minimum PHP version
  199. //
  200. function CheckPHPVersion($aMinVersion) {
  201. list($majorC, $minorC, $editC) = preg_split('/[\/.-]/', PHP_VERSION);
  202. list($majorR, $minorR, $editR) = preg_split('/[\/.-]/', $aMinVersion);
  203. if ($majorC != $majorR) return false;
  204. if ($majorC < $majorR) return false;
  205. // same major - check minor
  206. if ($minorC > $minorR) return true;
  207. if ($minorC < $minorR) return false;
  208. // and same minor
  209. if ($editC >= $editR) return true;
  210. return true;
  211. }
  212. //
  213. // Make sure PHP version is high enough
  214. //
  215. if( !CheckPHPVersion(MIN_PHPVERSION) ) {
  216. JpGraphError::RaiseL(13,PHP_VERSION,MIN_PHPVERSION);
  217. die();
  218. }
  219. //
  220. // Make GD sanity check
  221. //
  222. if( !function_exists("imagetypes") || !function_exists('imagecreatefromstring') ) {
  223. JpGraphError::RaiseL(25001);
  224. //("This PHP installation is not configured with the GD library. Please recompile PHP with GD support to run JpGraph. (Neither function imagetypes() nor imagecreatefromstring() does exist)");
  225. }
  226. //
  227. // Setup PHP error handler
  228. //
  229. function _phpErrorHandler($errno,$errmsg,$filename, $linenum, $vars) {
  230. // Respect current error level
  231. if( $errno & error_reporting() ) {
  232. JpGraphError::RaiseL(25003,basename($filename),$linenum,$errmsg);
  233. }
  234. }
  235. if( INSTALL_PHP_ERR_HANDLER ) {
  236. set_error_handler("_phpErrorHandler");
  237. }
  238. //
  239. // Check if there were any warnings, perhaps some wrong includes by the user. In this
  240. // case we raise it immediately since otherwise the image will not show and makes
  241. // debugging difficult. This is controlled by the user setting CATCH_PHPERRMSG
  242. //
  243. if( isset($GLOBALS['php_errormsg']) && CATCH_PHPERRMSG && !preg_match('/|Deprecated|/i', $GLOBALS['php_errormsg']) ) {
  244. JpGraphError::RaiseL(25004,$GLOBALS['php_errormsg']);
  245. }
  246. // Useful mathematical function
  247. function sign($a) {return $a >= 0 ? 1 : -1;}
  248. //
  249. // Utility function to generate an image name based on the filename we
  250. // are running from and assuming we use auto detection of graphic format
  251. // (top level), i.e it is safe to call this function
  252. // from a script that uses JpGraph
  253. //
  254. function GenImgName() {
  255. // Determine what format we should use when we save the images
  256. $supported = imagetypes();
  257. if( $supported & IMG_PNG ) $img_format="png";
  258. elseif( $supported & IMG_GIF ) $img_format="gif";
  259. elseif( $supported & IMG_JPG ) $img_format="jpeg";
  260. elseif( $supported & IMG_WBMP ) $img_format="wbmp";
  261. elseif( $supported & IMG_XPM ) $img_format="xpm";
  262. if( !isset($_SERVER['PHP_SELF']) ) {
  263. JpGraphError::RaiseL(25005);
  264. //(" Can't access PHP_SELF, PHP global variable. You can't run PHP from command line if you want to use the 'auto' naming of cache or image files.");
  265. }
  266. $fname = basename($_SERVER['PHP_SELF']);
  267. if( !empty($_SERVER['QUERY_STRING']) ) {
  268. $q = @$_SERVER['QUERY_STRING'];
  269. $fname .= '_'.preg_replace("/\W/", "_", $q).'.'.$img_format;
  270. }
  271. else {
  272. $fname = substr($fname,0,strlen($fname)-4).'.'.$img_format;
  273. }
  274. return $fname;
  275. }
  276. //===================================================
  277. // CLASS JpgTimer
  278. // Description: General timing utility class to handle
  279. // time measurement of generating graphs. Multiple
  280. // timers can be started.
  281. //===================================================
  282. class JpgTimer {
  283. private $start, $idx;
  284. function __construct() {
  285. $this->idx=0;
  286. }
  287. // Push a new timer start on stack
  288. function Push() {
  289. list($ms,$s)=explode(" ",microtime());
  290. $this->start[$this->idx++]=floor($ms*1000) + 1000*$s;
  291. }
  292. // Pop the latest timer start and return the diff with the
  293. // current time
  294. function Pop() {
  295. assert($this->idx>0);
  296. list($ms,$s)=explode(" ",microtime());
  297. $etime=floor($ms*1000) + (1000*$s);
  298. $this->idx--;
  299. return $etime-$this->start[$this->idx];
  300. }
  301. } // Class
  302. //===================================================
  303. // CLASS DateLocale
  304. // Description: Hold localized text used in dates
  305. //===================================================
  306. class DateLocale {
  307. public $iLocale = 'C'; // environmental locale be used by default
  308. private $iDayAbb = null, $iShortDay = null, $iShortMonth = null, $iMonthName = null;
  309. function __construct() {
  310. settype($this->iDayAbb, 'array');
  311. settype($this->iShortDay, 'array');
  312. settype($this->iShortMonth, 'array');
  313. settype($this->iMonthName, 'array');
  314. $this->Set('C');
  315. }
  316. function Set($aLocale) {
  317. if ( in_array($aLocale, array_keys($this->iDayAbb)) ){
  318. $this->iLocale = $aLocale;
  319. return TRUE; // already cached nothing else to do!
  320. }
  321. $pLocale = setlocale(LC_TIME, 0); // get current locale for LC_TIME
  322. if (is_array($aLocale)) {
  323. foreach ($aLocale as $loc) {
  324. $res = @setlocale(LC_TIME, $loc);
  325. if ( $res ) {
  326. $aLocale = $loc;
  327. break;
  328. }
  329. }
  330. }
  331. else {
  332. $res = @setlocale(LC_TIME, $aLocale);
  333. }
  334. if ( ! $res ) {
  335. JpGraphError::RaiseL(25007,$aLocale);
  336. //("You are trying to use the locale ($aLocale) which your PHP installation does not support. Hint: Use '' to indicate the default locale for this geographic region.");
  337. return FALSE;
  338. }
  339. $this->iLocale = $aLocale;
  340. for( $i = 0, $ofs = 0 - strftime('%w'); $i < 7; $i++, $ofs++ ) {
  341. $day = strftime('%a', strtotime("$ofs day"));
  342. $day[0] = strtoupper($day[0]);
  343. $this->iDayAbb[$aLocale][]= $day[0];
  344. $this->iShortDay[$aLocale][]= $day;
  345. }
  346. for($i=1; $i<=12; ++$i) {
  347. list($short ,$full) = explode('|', strftime("%b|%B",strtotime("2001-$i-01")));
  348. $this->iShortMonth[$aLocale][] = ucfirst($short);
  349. $this->iMonthName [$aLocale][] = ucfirst($full);
  350. }
  351. setlocale(LC_TIME, $pLocale);
  352. return TRUE;
  353. }
  354. function GetDayAbb() {
  355. return $this->iDayAbb[$this->iLocale];
  356. }
  357. function GetShortDay() {
  358. return $this->iShortDay[$this->iLocale];
  359. }
  360. function GetShortMonth() {
  361. return $this->iShortMonth[$this->iLocale];
  362. }
  363. function GetShortMonthName($aNbr) {
  364. return $this->iShortMonth[$this->iLocale][$aNbr];
  365. }
  366. function GetLongMonthName($aNbr) {
  367. return $this->iMonthName[$this->iLocale][$aNbr];
  368. }
  369. function GetMonth() {
  370. return $this->iMonthName[$this->iLocale];
  371. }
  372. }
  373. // Global object handlers
  374. $gDateLocale = new DateLocale();
  375. $gJpgDateLocale = new DateLocale();
  376. //=======================================================
  377. // CLASS Footer
  378. // Description: Encapsulates the footer line in the Graph
  379. //=======================================================
  380. class Footer {
  381. public $iLeftMargin = 3, $iRightMargin = 3, $iBottomMargin = 3 ;
  382. public $left,$center,$right;
  383. private $iTimer=null, $itimerpoststring='';
  384. function __construct() {
  385. $this->left = new Text();
  386. $this->left->ParagraphAlign('left');
  387. $this->center = new Text();
  388. $this->center->ParagraphAlign('center');
  389. $this->right = new Text();
  390. $this->right->ParagraphAlign('right');
  391. }
  392. function SetTimer($aTimer,$aTimerPostString='') {
  393. $this->iTimer = $aTimer;
  394. $this->itimerpoststring = $aTimerPostString;
  395. }
  396. function SetMargin($aLeft=3,$aRight=3,$aBottom=3) {
  397. $this->iLeftMargin = $aLeft;
  398. $this->iRightMargin = $aRight;
  399. $this->iBottomMargin = $aBottom;
  400. }
  401. function Stroke($aImg) {
  402. $y = $aImg->height - $this->iBottomMargin;
  403. $x = $this->iLeftMargin;
  404. $this->left->Align('left','bottom');
  405. $this->left->Stroke($aImg,$x,$y);
  406. $x = ($aImg->width - $this->iLeftMargin - $this->iRightMargin)/2;
  407. $this->center->Align('center','bottom');
  408. $this->center->Stroke($aImg,$x,$y);
  409. $x = $aImg->width - $this->iRightMargin;
  410. $this->right->Align('right','bottom');
  411. if( $this->iTimer != null ) {
  412. $this->right->Set( $this->right->t . sprintf('%.3f',$this->iTimer->Pop()/1000.0) . $this->itimerpoststring );
  413. }
  414. $this->right->Stroke($aImg,$x,$y);
  415. }
  416. }
  417. //===================================================
  418. // CLASS Graph
  419. // Description: Main class to handle graphs
  420. //===================================================
  421. class Graph {
  422. public $cache=null; // Cache object (singleton)
  423. public $img=null; // Img object (singleton)
  424. public $plots=array(); // Array of all plot object in the graph (for Y 1 axis)
  425. public $y2plots=array(); // Array of all plot object in the graph (for Y 2 axis)
  426. public $ynplots=array();
  427. public $xscale=null; // X Scale object (could be instance of LinearScale or LogScale
  428. public $yscale=null,$y2scale=null, $ynscale=array();
  429. public $iIcons = array(); // Array of Icons to add to
  430. public $cache_name; // File name to be used for the current graph in the cache directory
  431. public $xgrid=null; // X Grid object (linear or logarithmic)
  432. public $ygrid=null,$y2grid=null; //dito for Y
  433. public $doframe=true,$frame_color='black', $frame_weight=1; // Frame around graph
  434. public $boxed=false, $box_color='black', $box_weight=1; // Box around plot area
  435. public $doshadow=false,$shadow_width=4,$shadow_color='gray@0.5'; // Shadow for graph
  436. public $xaxis=null; // X-axis (instane of Axis class)
  437. public $yaxis=null, $y2axis=null, $ynaxis=array(); // Y axis (instance of Axis class)
  438. public $margin_color=array(230,230,230); // Margin color of graph
  439. public $plotarea_color=array(255,255,255); // Plot area color
  440. public $title,$subtitle,$subsubtitle; // Title and subtitle(s) text object
  441. public $axtype="linlin"; // Type of axis
  442. public $xtick_factor,$ytick_factor; // Factor to determine the maximum number of ticks depending on the plot width
  443. public $texts=null, $y2texts=null; // Text object to ge shown in the graph
  444. public $lines=null, $y2lines=null;
  445. public $bands=null, $y2bands=null;
  446. public $text_scale_off=0, $text_scale_abscenteroff=-1; // Text scale in fractions and for centering bars
  447. public $background_image='',$background_image_type=-1,$background_image_format="png";
  448. public $background_image_bright=0,$background_image_contr=0,$background_image_sat=0;
  449. public $background_image_xpos=0,$background_image_ypos=0;
  450. public $image_bright=0, $image_contr=0, $image_sat=0;
  451. public $inline;
  452. public $showcsim=0,$csimcolor="red";//debug stuff, draw the csim boundaris on the image if <>0
  453. public $grid_depth=DEPTH_BACK; // Draw grid under all plots as default
  454. public $iAxisStyle = AXSTYLE_SIMPLE;
  455. public $iCSIMdisplay=false,$iHasStroked = false;
  456. public $footer;
  457. public $csimcachename = '', $csimcachetimeout = 0, $iCSIMImgAlt='';
  458. public $iDoClipping = false;
  459. public $y2orderback=true;
  460. public $tabtitle;
  461. public $bkg_gradtype=-1,$bkg_gradstyle=BGRAD_MARGIN;
  462. public $bkg_gradfrom='navy', $bkg_gradto='silver';
  463. public $plot_gradtype=-1,$plot_gradstyle=BGRAD_MARGIN;
  464. public $plot_gradfrom='silver', $plot_gradto='navy';
  465. public $titlebackground = false;
  466. public $titlebackground_color = 'lightblue',
  467. $titlebackground_style = 1,
  468. $titlebackground_framecolor = 'blue',
  469. $titlebackground_framestyle = 2,
  470. $titlebackground_frameweight = 1,
  471. $titlebackground_bevelheight = 3 ;
  472. public $titlebkg_fillstyle=TITLEBKG_FILLSTYLE_SOLID;
  473. public $titlebkg_scolor1='black',$titlebkg_scolor2='white';
  474. public $framebevel = false, $framebeveldepth = 2 ;
  475. public $framebevelborder = false, $framebevelbordercolor='black';
  476. public $framebevelcolor1='white@0.4', $framebevelcolor2='black@0.4';
  477. public $background_image_mix=100;
  478. public $background_cflag = '';
  479. public $background_cflag_type = BGIMG_FILLPLOT;
  480. public $background_cflag_mix = 100;
  481. public $iImgTrans=false,
  482. $iImgTransHorizon = 100,$iImgTransSkewDist=150,
  483. $iImgTransDirection = 1, $iImgTransMinSize = true,
  484. $iImgTransFillColor='white',$iImgTransHighQ=false,
  485. $iImgTransBorder=false,$iImgTransHorizonPos=0.5;
  486. public $legend;
  487. protected $iYAxisDeltaPos=50;
  488. protected $iIconDepth=DEPTH_BACK;
  489. protected $iAxisLblBgType = 0,
  490. $iXAxisLblBgFillColor = 'lightgray', $iXAxisLblBgColor = 'black',
  491. $iYAxisLblBgFillColor = 'lightgray', $iYAxisLblBgColor = 'black';
  492. protected $iTables=NULL;
  493. // aWIdth Width in pixels of image
  494. // aHeight Height in pixels of image
  495. // aCachedName Name for image file in cache directory
  496. // aTimeOut Timeout in minutes for image in cache
  497. // aInline If true the image is streamed back in the call to Stroke()
  498. // If false the image is just created in the cache
  499. function __construct($aWidth=300,$aHeight=200,$aCachedName='',$aTimeout=0,$aInline=true) {
  500. if( !is_numeric($aWidth) || !is_numeric($aHeight) ) {
  501. JpGraphError::RaiseL(25008);//('Image width/height argument in Graph::Graph() must be numeric');
  502. }
  503. // Automatically generate the image file name based on the name of the script that
  504. // generates the graph
  505. if( $aCachedName == 'auto' ) {
  506. $aCachedName=GenImgName();
  507. }
  508. // Should the image be streamed back to the browser or only to the cache?
  509. $this->inline=$aInline;
  510. $this->img = new RotImage($aWidth,$aHeight);
  511. $this->cache = new ImgStreamCache();
  512. // Window doesn't like '?' in the file name so replace it with an '_'
  513. $aCachedName = str_replace("?","_",$aCachedName);
  514. $this->SetupCache($aCachedName, $aTimeout);
  515. $this->title = new Text();
  516. $this->title->ParagraphAlign('center');
  517. $this->title->SetFont(FF_FONT2,FS_BOLD);
  518. $this->title->SetMargin(5);
  519. $this->title->SetAlign('center');
  520. $this->subtitle = new Text();
  521. $this->subtitle->ParagraphAlign('center');
  522. $this->subtitle->SetMargin(3);
  523. $this->subtitle->SetAlign('center');
  524. $this->subsubtitle = new Text();
  525. $this->subsubtitle->ParagraphAlign('center');
  526. $this->subsubtitle->SetMargin(3);
  527. $this->subsubtitle->SetAlign('center');
  528. $this->legend = new Legend();
  529. $this->footer = new Footer();
  530. // If the cached version exist just read it directly from the
  531. // cache, stream it back to browser and exit
  532. if( $aCachedName!='' && READ_CACHE && $aInline ) {
  533. if( $this->cache->GetAndStream($this->img,$aCachedName) ) {
  534. exit();
  535. }
  536. }
  537. $this->SetTickDensity(); // Normal density
  538. $this->tabtitle = new GraphTabTitle();
  539. }
  540. function SetupCache($aFilename,$aTimeout=60) {
  541. $this->cache_name = $aFilename;
  542. $this->cache->SetTimeOut($aTimeout);
  543. }
  544. // Enable final image perspective transformation
  545. function Set3DPerspective($aDir=1,$aHorizon=100,$aSkewDist=120,$aQuality=false,$aFillColor='#FFFFFF',$aBorder=false,$aMinSize=true,$aHorizonPos=0.5) {
  546. $this->iImgTrans = true;
  547. $this->iImgTransHorizon = $aHorizon;
  548. $this->iImgTransSkewDist= $aSkewDist;
  549. $this->iImgTransDirection = $aDir;
  550. $this->iImgTransMinSize = $aMinSize;
  551. $this->iImgTransFillColor=$aFillColor;
  552. $this->iImgTransHighQ=$aQuality;
  553. $this->iImgTransBorder=$aBorder;
  554. $this->iImgTransHorizonPos=$aHorizonPos;
  555. }
  556. function SetUserFont($aNormal,$aBold='',$aItalic='',$aBoldIt='') {
  557. $this->img->ttf->SetUserFont($aNormal,$aBold,$aItalic,$aBoldIt);
  558. }
  559. function SetUserFont1($aNormal,$aBold='',$aItalic='',$aBoldIt='') {
  560. $this->img->ttf->SetUserFont1($aNormal,$aBold,$aItalic,$aBoldIt);
  561. }
  562. function SetUserFont2($aNormal,$aBold='',$aItalic='',$aBoldIt='') {
  563. $this->img->ttf->SetUserFont2($aNormal,$aBold,$aItalic,$aBoldIt);
  564. }
  565. function SetUserFont3($aNormal,$aBold='',$aItalic='',$aBoldIt='') {
  566. $this->img->ttf->SetUserFont3($aNormal,$aBold,$aItalic,$aBoldIt);
  567. }
  568. // Set Image format and optional quality
  569. function SetImgFormat($aFormat,$aQuality=75) {
  570. $this->img->SetImgFormat($aFormat,$aQuality);
  571. }
  572. // Should the grid be in front or back of the plot?
  573. function SetGridDepth($aDepth) {
  574. $this->grid_depth=$aDepth;
  575. }
  576. function SetIconDepth($aDepth) {
  577. $this->iIconDepth=$aDepth;
  578. }
  579. // Specify graph angle 0-360 degrees.
  580. function SetAngle($aAngle) {
  581. $this->img->SetAngle($aAngle);
  582. }
  583. function SetAlphaBlending($aFlg=true) {
  584. $this->img->SetAlphaBlending($aFlg);
  585. }
  586. // Shortcut to image margin
  587. function SetMargin($lm,$rm,$tm,$bm) {
  588. $this->img->SetMargin($lm,$rm,$tm,$bm);
  589. }
  590. function SetY2OrderBack($aBack=true) {
  591. $this->y2orderback = $aBack;
  592. }
  593. // Rotate the graph 90 degrees and set the margin
  594. // when we have done a 90 degree rotation
  595. function Set90AndMargin($lm=0,$rm=0,$tm=0,$bm=0) {
  596. $lm = $lm ==0 ? floor(0.2 * $this->img->width) : $lm ;
  597. $rm = $rm ==0 ? floor(0.1 * $this->img->width) : $rm ;
  598. $tm = $tm ==0 ? floor(0.2 * $this->img->height) : $tm ;
  599. $bm = $bm ==0 ? floor(0.1 * $this->img->height) : $bm ;
  600. $adj = ($this->img->height - $this->img->width)/2;
  601. $this->img->SetMargin($tm-$adj,$bm-$adj,$rm+$adj,$lm+$adj);
  602. $this->img->SetCenter(floor($this->img->width/2),floor($this->img->height/2));
  603. $this->SetAngle(90);
  604. if( empty($this->yaxis) || empty($this->xaxis) ) {
  605. JpgraphError::RaiseL(25009);//('You must specify what scale to use with a call to Graph::SetScale()');
  606. }
  607. $this->xaxis->SetLabelAlign('right','center');
  608. $this->yaxis->SetLabelAlign('center','bottom');
  609. }
  610. function SetClipping($aFlg=true) {
  611. $this->iDoClipping = $aFlg ;
  612. }
  613. // Add a plot object to the graph
  614. function Add($aPlot) {
  615. if( $aPlot == null ) {
  616. JpGraphError::RaiseL(25010);//("Graph::Add() You tried to add a null plot to the graph.");
  617. }
  618. if( is_array($aPlot) && count($aPlot) > 0 ) {
  619. $cl = $aPlot[0];
  620. }
  621. else {
  622. $cl = $aPlot;
  623. }
  624. if( $cl instanceof Text ) $this->AddText($aPlot);
  625. elseif( class_exists('PlotLine',false) && ($cl instanceof PlotLine) ) $this->AddLine($aPlot);
  626. elseif( class_exists('PlotBand',false) && ($cl instanceof PlotBand) ) $this->AddBand($aPlot);
  627. elseif( class_exists('IconPlot',false) && ($cl instanceof IconPlot) ) $this->AddIcon($aPlot);
  628. elseif( class_exists('GTextTable',false) && ($cl instanceof GTextTable) ) $this->AddTable($aPlot);
  629. else {
  630. if( is_array($aPlot) ) {
  631. $this->plots = array_merge($this->plots,$aPlot);
  632. }
  633. else {
  634. $this->plots[] = $aPlot;
  635. }
  636. }
  637. }
  638. function AddTable($aTable) {
  639. if( is_array($aTable) ) {
  640. for($i=0; $i < count($aTable); ++$i ) {
  641. $this->iTables[]=$aTable[$i];
  642. }
  643. }
  644. else {
  645. $this->iTables[] = $aTable ;
  646. }
  647. }
  648. function AddIcon($aIcon) {
  649. if( is_array($aIcon) ) {
  650. for($i=0; $i < count($aIcon); ++$i ) {
  651. $this->iIcons[]=$aIcon[$i];
  652. }
  653. }
  654. else {
  655. $this->iIcons[] = $aIcon ;
  656. }
  657. }
  658. // Add plot to second Y-scale
  659. function AddY2($aPlot) {
  660. if( $aPlot == null ) {
  661. JpGraphError::RaiseL(25011);//("Graph::AddY2() You tried to add a null plot to the graph.");
  662. }
  663. if( is_array($aPlot) && count($aPlot) > 0 ) {
  664. $cl = $aPlot[0];
  665. }
  666. else {
  667. $cl = $aPlot;
  668. }
  669. if( $cl instanceof Text ) {
  670. $this->AddText($aPlot,true);
  671. }
  672. elseif( class_exists('PlotLine',false) && ($cl instanceof PlotLine) ) {
  673. $this->AddLine($aPlot,true);
  674. }
  675. elseif( class_exists('PlotBand',false) && ($cl instanceof PlotBand) ) {
  676. $this->AddBand($aPlot,true);
  677. }
  678. else {
  679. $this->y2plots[] = $aPlot;
  680. }
  681. }
  682. // Add plot to the extra Y-axises
  683. function AddY($aN,$aPlot) {
  684. if( $aPlot == null ) {
  685. JpGraphError::RaiseL(25012);//("Graph::AddYN() You tried to add a null plot to the graph.");
  686. }
  687. if( is_array($aPlot) && count($aPlot) > 0 ) {
  688. $cl = $aPlot[0];
  689. }
  690. else {
  691. $cl = $aPlot;
  692. }
  693. if( ($cl instanceof Text) ||
  694. (class_exists('PlotLine',false) && ($cl instanceof PlotLine)) ||
  695. (class_exists('PlotBand',false) && ($cl instanceof PlotBand)) ) {
  696. JpGraph::RaiseL(25013);//('You can only add standard plots to multiple Y-axis');
  697. }
  698. else {
  699. $this->ynplots[$aN][] = $aPlot;
  700. }
  701. }
  702. // Add text object to the graph
  703. function AddText($aTxt,$aToY2=false) {
  704. if( $aTxt == null ) {
  705. JpGraphError::RaiseL(25014);//("Graph::AddText() You tried to add a null text to the graph.");
  706. }
  707. if( $aToY2 ) {
  708. if( is_array($aTxt) ) {
  709. for($i=0; $i < count($aTxt); ++$i ) {
  710. $this->y2texts[]=$aTxt[$i];
  711. }
  712. }
  713. else {
  714. $this->y2texts[] = $aTxt;
  715. }
  716. }
  717. else {
  718. if( is_array($aTxt) ) {
  719. for($i=0; $i < count($aTxt); ++$i ) {
  720. $this->texts[]=$aTxt[$i];
  721. }
  722. }
  723. else {
  724. $this->texts[] = $aTxt;
  725. }
  726. }
  727. }
  728. // Add a line object (class PlotLine) to the graph
  729. function AddLine($aLine,$aToY2=false) {
  730. if( $aLine == null ) {
  731. JpGraphError::RaiseL(25015);//("Graph::AddLine() You tried to add a null line to the graph.");
  732. }
  733. if( $aToY2 ) {
  734. if( is_array($aLine) ) {
  735. for($i=0; $i < count($aLine); ++$i ) {
  736. //$this->y2lines[]=$aLine[$i];
  737. $this->y2plots[]=$aLine[$i];
  738. }
  739. }
  740. else {
  741. //$this->y2lines[] = $aLine;
  742. $this->y2plots[]=$aLine;
  743. }
  744. }
  745. else {
  746. if( is_array($aLine) ) {
  747. for($i=0; $i<count($aLine); ++$i ) {
  748. //$this->lines[]=$aLine[$i];
  749. $this->plots[]=$aLine[$i];
  750. }
  751. }
  752. else {
  753. //$this->lines[] = $aLine;
  754. $this->plots[] = $aLine;
  755. }
  756. }
  757. }
  758. // Add vertical or horizontal band
  759. function AddBand($aBand,$aToY2=false) {
  760. if( $aBand == null ) {
  761. JpGraphError::RaiseL(25016);//(" Graph::AddBand() You tried to add a null band to the graph.");
  762. }
  763. if( $aToY2 ) {
  764. if( is_array($aBand) ) {
  765. for($i=0; $i < count($aBand); ++$i ) {
  766. $this->y2bands[] = $aBand[$i];
  767. }
  768. }
  769. else {
  770. $this->y2bands[] = $aBand;
  771. }
  772. }
  773. else {
  774. if( is_array($aBand) ) {
  775. for($i=0; $i < count($aBand); ++$i ) {
  776. $this->bands[] = $aBand[$i];
  777. }
  778. }
  779. else {
  780. $this->bands[] = $aBand;
  781. }
  782. }
  783. }
  784. function SetPlotGradient($aFrom='navy',$aTo='silver',$aGradType=2) {
  785. $this->plot_gradtype=$aGradType;
  786. $this->plot_gradfrom = $aFrom;
  787. $this->plot_gradto = $aTo;
  788. }
  789. function SetBackgroundGradient($aFrom='navy',$aTo='silver',$aGradType=2,$aStyle=BGRAD_FRAME) {
  790. $this->bkg_gradtype=$aGradType;
  791. $this->bkg_gradstyle=$aStyle;
  792. $this->bkg_gradfrom = $aFrom;
  793. $this->bkg_gradto = $aTo;
  794. }
  795. // Set a country flag in the background
  796. function SetBackgroundCFlag($aName,$aBgType=BGIMG_FILLPLOT,$aMix=100) {
  797. $this->background_cflag = $aName;
  798. $this->background_cflag_type = $aBgType;
  799. $this->background_cflag_mix = $aMix;
  800. }
  801. // Alias for the above method
  802. function SetBackgroundCountryFlag($aName,$aBgType=BGIMG_FILLPLOT,$aMix=100) {
  803. $this->background_cflag = $aName;
  804. $this->background_cflag_type = $aBgType;
  805. $this->background_cflag_mix = $aMix;
  806. }
  807. // Specify a background image
  808. function SetBackgroundImage($aFileName,$aBgType=BGIMG_FILLPLOT,$aImgFormat='auto') {
  809. // Get extension to determine image type
  810. if( $aImgFormat == 'auto' ) {
  811. $e = explode('.',$aFileName);
  812. if( !$e ) {
  813. JpGraphError::RaiseL(25018,$aFileName);//('Incorrect file name for Graph::SetBackgroundImage() : '.$aFileName.' Must have a valid image extension (jpg,gif,png) when using autodetection of image type');
  814. }
  815. $valid_formats = array('png', 'jpg', 'gif');
  816. $aImgFormat = strtolower($e[count($e)-1]);
  817. if ($aImgFormat == 'jpeg') {
  818. $aImgFormat = 'jpg';
  819. }
  820. elseif (!in_array($aImgFormat, $valid_formats) ) {
  821. JpGraphError::RaiseL(25019,$aImgFormat);//('Unknown file extension ($aImgFormat) in Graph::SetBackgroundImage() for filename: '.$aFileName);
  822. }
  823. }
  824. $this->background_image = $aFileName;
  825. $this->background_image_type=$aBgType;
  826. $this->background_image_format=$aImgFormat;
  827. }
  828. function SetBackgroundImageMix($aMix) {
  829. $this->background_image_mix = $aMix ;
  830. }
  831. // Adjust background image position
  832. function SetBackgroundImagePos($aXpos,$aYpos) {
  833. $this->background_image_xpos = $aXpos ;
  834. $this->background_image_ypos = $aYpos ;
  835. }
  836. // Specify axis style (boxed or single)
  837. function SetAxisStyle($aStyle) {
  838. $this->iAxisStyle = $aStyle ;
  839. }
  840. // Set a frame around the plot area
  841. function SetBox($aDrawPlotFrame=true,$aPlotFrameColor=array(0,0,0),$aPlotFrameWeight=1) {
  842. $this->boxed = $aDrawPlotFrame;
  843. $this->box_weight = $aPlotFrameWeight;
  844. $this->box_color = $aPlotFrameColor;
  845. }
  846. // Specify color for the plotarea (not the margins)
  847. function SetColor($aColor) {
  848. $this->plotarea_color=$aColor;
  849. }
  850. // Specify color for the margins (all areas outside the plotarea)
  851. function SetMarginColor($aColor) {
  852. $this->margin_color=$aColor;
  853. }
  854. // Set a frame around the entire image
  855. function SetFrame($aDrawImgFrame=true,$aImgFrameColor=array(0,0,0),$aImgFrameWeight=1) {
  856. $this->doframe = $aDrawImgFrame;
  857. $this->frame_color = $aImgFrameColor;
  858. $this->frame_weight = $aImgFrameWeight;
  859. }
  860. function SetFrameBevel($aDepth=3,$aBorder=false,$aBorderColor='black',$aColor1='white@0.4',$aColor2='darkgray@0.4',$aFlg=true) {
  861. $this->framebevel = $aFlg ;
  862. $this->framebeveldepth = $aDepth ;
  863. $this->framebevelborder = $aBorder ;
  864. $this->framebevelbordercolor = $aBorderColor ;
  865. $this->framebevelcolor1 = $aColor1 ;
  866. $this->framebevelcolor2 = $aColor2 ;
  867. $this->doshadow = false ;
  868. }
  869. // Set the shadow around the whole image
  870. function SetShadow($aShowShadow=true,$aShadowWidth=5,$aShadowColor='darkgray') {
  871. $this->doshadow = $aShowShadow;
  872. $this->shadow_color = $aShadowColor;
  873. $this->shadow_width = $aShadowWidth;
  874. $this->footer->iBottomMargin += $aShadowWidth;
  875. $this->footer->iRightMargin += $aShadowWidth;
  876. }
  877. // Specify x,y scale. Note that if you manually specify the scale
  878. // you must also specify the tick distance with a call to Ticks::Set()
  879. function SetScale($aAxisType,$aYMin=1,$aYMax=1,$aXMin=1,$aXMax=1) {
  880. $this->axtype = $aAxisType;
  881. if( $aYMax < $aYMin || $aXMax < $aXMin ) {
  882. JpGraphError::RaiseL(25020);//('Graph::SetScale(): Specified Max value must be larger than the specified Min value.');
  883. }
  884. $yt=substr($aAxisType,-3,3);
  885. if( $yt == 'lin' ) {
  886. $this->yscale = new LinearScale($aYMin,$aYMax);
  887. }
  888. elseif( $yt == 'int' ) {
  889. $this->yscale = new LinearScale($aYMin,$aYMax);
  890. $this->yscale->SetIntScale();
  891. }
  892. elseif( $yt == 'log' ) {
  893. $this->yscale = new LogScale($aYMin,$aYMax);
  894. }
  895. else {
  896. JpGraphError::RaiseL(25021,$aAxisType);//("Unknown scale specification for Y-scale. ($aAxisType)");
  897. }
  898. $xt=substr($aAxisType,0,3);
  899. if( $xt == 'lin' || $xt == 'tex' ) {
  900. $this->xscale = new LinearScale($aXMin,$aXMax,'x');
  901. $this->xscale->textscale = ($xt == 'tex');
  902. }
  903. elseif( $xt == 'int' ) {
  904. $this->xscale = new LinearScale($aXMin,$aXMax,'x');
  905. $this->xscale->SetIntScale();
  906. }
  907. elseif( $xt == 'dat' ) {
  908. $this->xscale = new DateScale($aXMin,$aXMax,'x');
  909. }
  910. elseif( $xt == 'log' ) {
  911. $this->xscale = new LogScale($aXMin,$aXMax,'x');
  912. }
  913. else {
  914. JpGraphError::RaiseL(25022,$aAxisType);//(" Unknown scale specification for X-scale. ($aAxisType)");
  915. }
  916. $this->xaxis = new Axis($this->img,$this->xscale);
  917. $this->yaxis = new Axis($this->img,$this->yscale);
  918. $this->xgrid = new Grid($this->xaxis);
  919. $this->ygrid = new Grid($this->yaxis);
  920. $this->ygrid->Show();
  921. }
  922. // Specify secondary Y scale
  923. function SetY2Scale($aAxisType='lin',$aY2Min=1,$aY2Max=1) {
  924. if( $aAxisType == 'lin' ) {
  925. $this->y2scale = new LinearScale($aY2Min,$aY2Max);
  926. }
  927. elseif( $aAxisType == 'int' ) {
  928. $this->y2scale = new LinearScale($aY2Min,$aY2Max);
  929. $this->y2scale->SetIntScale();
  930. }
  931. elseif( $aAxisType == 'log' ) {
  932. $this->y2scale = new LogScale($aY2Min,$aY2Max);
  933. }
  934. else {
  935. JpGraphError::RaiseL(25023,$aAxisType);//("JpGraph: Unsupported Y2 axis type: $aAxisType\nMust be one of (lin,log,int)");
  936. }
  937. $this->y2axis = new Axis($this->img,$this->y2scale);
  938. $this->y2axis->scale->ticks->SetDirection(SIDE_LEFT);
  939. $this->y2axis->SetLabelSide(SIDE_RIGHT);
  940. $this->y2axis->SetPos('max');
  941. $this->y2axis->SetTitleSide(SIDE_RIGHT);
  942. // Deafult position is the max x-value
  943. $this->y2grid = new Grid($this->y2axis);
  944. }
  945. // Set the delta position (in pixels) between the multiple Y-axis
  946. function SetYDeltaDist($aDist) {
  947. $this->iYAxisDeltaPos = $aDist;
  948. }
  949. // Specify secondary Y scale
  950. function SetYScale($aN,$aAxisType="lin",$aYMin=1,$aYMax=1) {
  951. if( $aAxisType == 'lin' ) {
  952. $this->ynscale[$aN] = new LinearScale($aYMin,$aYMax);
  953. }
  954. elseif( $aAxisType == 'int' ) {
  955. $this->ynscale[$aN] = new LinearScale($aYMin,$aYMax);
  956. $this->ynscale[$aN]->SetIntScale();
  957. }
  958. elseif( $aAxisType == 'log' ) {
  959. $this->ynscale[$aN] = new LogScale($aYMin,$aYMax);
  960. }
  961. else {
  962. JpGraphError::RaiseL(25024,$aAxisType);//("JpGraph: Unsupported Y axis type: $aAxisType\nMust be one of (lin,log,int)");
  963. }
  964. $this->ynaxis[$aN] = new Axis($this->img,$this->ynscale[$aN]);
  965. $this->ynaxis[$aN]->scale->ticks->SetDirection(SIDE_LEFT);
  966. $this->ynaxis[$aN]->SetLabelSide(SIDE_RIGHT);
  967. }
  968. // Specify density of ticks when autoscaling 'normal', 'dense', 'sparse', 'verysparse'
  969. // The dividing factor have been determined heuristically according to my aesthetic
  970. // sense (or lack off) y.m.m.v !
  971. function SetTickDensity($aYDensity=TICKD_NORMAL,$aXDensity=TICKD_NORMAL) {
  972. $this->xtick_factor=30;
  973. $this->ytick_factor=25;
  974. switch( $aYDensity ) {
  975. case TICKD_DENSE:
  976. $this->ytick_factor=12;
  977. break;
  978. case TICKD_NORMAL:
  979. $this->ytick_factor=25;
  980. break;
  981. case TICKD_SPARSE:
  982. $this->ytick_factor=40;
  983. break;
  984. case TICKD_VERYSPARSE:
  985. $this->ytick_factor=100;
  986. break;
  987. default:
  988. JpGraphError::RaiseL(25025,$densy);//("JpGraph: Unsupported Tick density: $densy");
  989. }
  990. switch( $aXDensity ) {
  991. case TICKD_DENSE:
  992. $this->xtick_factor=15;
  993. break;
  994. case TICKD_NORMAL:
  995. $this->xtick_factor=30;
  996. break;
  997. case TICKD_SPARSE:
  998. $this->xtick_factor=45;
  999. break;
  1000. case TICKD_VERYSPARSE:
  1001. $this->xtick_factor=60;
  1002. break;
  1003. default:
  1004. JpGraphError::RaiseL(25025,$densx);//("JpGraph: Unsupported Tick density: $densx");
  1005. }
  1006. }
  1007. // Get a string of all image map areas
  1008. function GetCSIMareas() {
  1009. if( !$this->iHasStroked ) {
  1010. $this->Stroke(_CSIM_SPECIALFILE);
  1011. }
  1012. $csim = $this->title->GetCSIMAreas();
  1013. $csim .= $this->subtitle->GetCSIMAreas();
  1014. $csim .= $this->subsubtitle->GetCSIMAreas();
  1015. $csim .= $this->legend->GetCSIMAreas();
  1016. if( $this->y2axis != NULL ) {
  1017. $csim .= $this->y2axis->title->GetCSIMAreas();
  1018. }
  1019. if( $this->texts != null ) {
  1020. $n = count($this->texts);
  1021. for($i=0; $i < $n; ++$i ) {
  1022. $csim .= $this->texts[$i]->GetCSIMAreas();
  1023. }
  1024. }
  1025. if( $this->y2texts != null && $this->y2scale != null ) {
  1026. $n = count($this->y2texts);
  1027. for($i=0; $i < $n; ++$i ) {
  1028. $csim .= $this->y2texts[$i]->GetCSIMAreas();
  1029. }
  1030. }
  1031. if( $this->yaxis != null && $this->xaxis != null ) {
  1032. $csim .= $this->yaxis->title->GetCSIMAreas();
  1033. $csim .= $this->xaxis->title->GetCSIMAreas();
  1034. }
  1035. $n = count($this->plots);
  1036. for( $i=0; $i < $n; ++$i ) {
  1037. $csim .= $this->plots[$i]->GetCSIMareas();
  1038. }
  1039. $n = count($this->y2plots);
  1040. for( $i=0; $i < $n; ++$i ) {
  1041. $csim .= $this->y2plots[$i]->GetCSIMareas();
  1042. }
  1043. $n = count($this->ynaxis);
  1044. for( $i=0; $i < $n; ++$i ) {
  1045. $m = count($this->ynplots[$i]);
  1046. for($j=0; $j < $m; ++$j ) {
  1047. $csim .= $this->ynplots[$i][$j]->GetCSIMareas();
  1048. }
  1049. }
  1050. $n = count($this->iTables);
  1051. for( $i=0; $i < $n; ++$i ) {
  1052. $csim .= $this->iTables[$i]->GetCSIMareas();
  1053. }
  1054. return $csim;
  1055. }
  1056. // Get a complete <MAP>..</MAP> tag for the final image map
  1057. function GetHTMLImageMap($aMapName) {
  1058. $im = "<map name=\"$aMapName\" id=\"$aMapName\" >\n";
  1059. $im .= $this->GetCSIMareas();
  1060. $im .= "</map>";
  1061. return $im;
  1062. }
  1063. function CheckCSIMCache($aCacheName,$aTimeOut=60) {
  1064. global $_SERVER;
  1065. if( $aCacheName=='auto' ) {
  1066. $aCacheName=basename($_SERVER['PHP_SELF']);
  1067. }
  1068. $urlarg = $this->GetURLArguments();
  1069. $this->csimcachename = CSIMCACHE_DIR.$aCacheName.$urlarg;
  1070. $this->csimcachetimeout = $aTimeOut;
  1071. // First determine if we need to check for a cached version
  1072. // This differs from the standard cache in the sense that the
  1073. // image and CSIM map HTML file is written relative to the directory
  1074. // the script executes in and not the specified cache directory.
  1075. // The reason for this is that the cache directory is not necessarily
  1076. // accessible from the HTTP server.
  1077. if( $this->csimcachename != '' ) {
  1078. $dir = dirname($this->csimcachename);
  1079. $base = basename($this->csimcachename);
  1080. $base = strtok($base,'.');
  1081. $suffix = strtok('.');
  1082. $basecsim = $dir.'/'.$base.'?'.$urlarg.'_csim_.html';
  1083. $baseimg = $dir.'/'.$base.'?'.$urlarg.'.'.$this->img->img_format;
  1084. $timedout=false;
  1085. // Does it exist at all ?
  1086. if( file_exists($basecsim) && file_exists($baseimg) ) {
  1087. // Check that it hasn't timed out
  1088. $diff=time()-filemtime($basecsim);
  1089. if( $this->csimcachetimeout>0 && ($diff > $this->csimcachetimeout*60) ) {
  1090. $timedout=true;
  1091. @unlink($basecsim);
  1092. @unlink($baseimg);
  1093. }
  1094. else {
  1095. if ($fh = @fopen($basecsim, "r")) {
  1096. fpassthru($fh);
  1097. return true;
  1098. }
  1099. else {
  1100. JpGraphError::RaiseL(25027,$basecsim);//(" Can't open cached CSIM \"$basecsim\" for reading.");
  1101. }
  1102. }
  1103. }
  1104. }
  1105. return false;
  1106. }
  1107. // Build the argument string to be used with the csim images
  1108. static function GetURLArguments($aAddRecursiveBlocker=false) {
  1109. if( $aAddRecursiveBlocker ) {
  1110. // This is a JPGRAPH internal defined that prevents
  1111. // us from recursively coming here again
  1112. $urlarg = _CSIM_DISPLAY.'=1';
  1113. }
  1114. // Now reconstruct any user URL argument
  1115. reset($_GET);
  1116. while( list($key,$value) = each($_GET) ) {
  1117. if( is_array($value) ) {
  1118. foreach ( $value as $k => $v ) {
  1119. $urlarg .= '&amp;'.$key.'%5B'.$k.'%5D='.urlencode($v);
  1120. }
  1121. }
  1122. else {
  1123. $urlarg .= '&amp;'.$key.'='.urlencode($value);
  1124. }
  1125. }
  1126. // It's not ideal to convert POST argument to GET arguments
  1127. // but there is little else we can do. One idea for the
  1128. // future might be recreate the POST header in case.
  1129. reset($_POST);
  1130. while( list($key,$value) = each($_POST) ) {
  1131. if( is_array($value) ) {
  1132. foreach ( $value as $k => $v ) {
  1133. $urlarg .= '&amp;'.$key.'%5B'.$k.'%5D='.urlencode($v);
  1134. }
  1135. }
  1136. else {
  1137. $urlarg .= '&amp;'.$key.'='.urlencode($value);
  1138. }
  1139. }
  1140. return $urlarg;
  1141. }
  1142. function SetCSIMImgAlt($aAlt) {
  1143. $this->iCSIMImgAlt = $aAlt;
  1144. }
  1145. function StrokeCSIM($aScriptName='auto',$aCSIMName='',$aBorder=0) {
  1146. if( $aCSIMName=='' ) {
  1147. // create a random map name
  1148. srand ((double) microtime() * 1000000);
  1149. $r = rand(0,100000);
  1150. $aCSIMName='__mapname'.$r.'__';
  1151. }
  1152. if( $aScriptName=='auto' ) {
  1153. $aScriptName=basename($_SERVER['PHP_SELF']);
  1154. }
  1155. $urlarg = $this->GetURLArguments(true);
  1156. if( empty($_GET[_CSIM_DISPLAY]) ) {
  1157. // First determine if we need to check for a cached version
  1158. // This differs from the standard cache in the sense that the
  1159. // image and CSIM map HTML file is written relative to the directory
  1160. // the script executes in and not the specified cache directory.
  1161. // The reason for this is that the cache directory is not necessarily
  1162. // accessible from the HTTP server.
  1163. if( $this->csimcachename != '' ) {
  1164. $dir = dirname($this->csimcachename);
  1165. $base = basename($this->csimcachename);
  1166. $base = strtok($base,'.');
  1167. $suffix = strtok('.');
  1168. $basecsim = $dir.'/'.$base.'?'.$urlarg.'_csim_.html';
  1169. $baseimg = $base.'?'.$urlarg.'.'.$this->img->img_format;
  1170. // Check that apache can write to directory specified
  1171. if( file_exists($dir) && !is_writeable($dir) ) {
  1172. JpgraphError::RaiseL(25028,$dir);//('Apache/PHP does not have permission to write to the CSIM cache directory ('.$dir.'). Check permissions.');
  1173. }
  1174. // Make sure directory exists
  1175. $this->cache->MakeDirs($dir);
  1176. // Write the image file
  1177. $this->Stroke(CSIMCACHE_DIR.$baseimg);
  1178. // Construct wrapper HTML and write to file and send it back to browser
  1179. // In the src URL we must replace the '?' with its encoding to prevent the arguments
  1180. // to be converted to real arguments.
  1181. $tmp = str_replace('?','%3f',$baseimg);
  1182. $htmlwrap = $this->GetHTMLImageMap($aCSIMName)."\n".
  1183. '<img src="'.CSIMCACHE_HTTP_DIR.$tmp.'" ismap="ismap" usemap="#'.$aCSIMName.' width="'.$this->img->width.'" height="'.$this->img->height."\" alt=\"".$this->iCSIMImgAlt."\" />\n";
  1184. if($fh = @fopen($basecsim,'w') ) {
  1185. fwrite($fh,$htmlwrap);
  1186. fclose($fh);
  1187. echo $htmlwrap;
  1188. }
  1189. else {
  1190. JpGraphError::RaiseL(25029,$basecsim);//(" Can't write CSIM \"$basecsim\" for writing. Check free space and permissions.");
  1191. }
  1192. }
  1193. else {
  1194. if( $aScriptName=='' ) {
  1195. JpGraphError::RaiseL(25030);//('Missing script name in call to StrokeCSIM(). You must specify the name of the actual image script as the first parameter to StrokeCSIM().');
  1196. }
  1197. echo $this->GetHTMLImageMap($aCSIMName) . $this->GetCSIMImgHTML($aCSIMName, $aScriptName, $aBorder);
  1198. }
  1199. }
  1200. else {
  1201. $this->Stroke();
  1202. }
  1203. }
  1204. function StrokeCSIMImage() {
  1205. if( @$_GET[_CSIM_DISPLAY] == 1 ) {
  1206. $this->Stroke();
  1207. }
  1208. }
  1209. function GetCSIMImgHTML($aCSIMName, $aScriptName='auto', $aBorder=0 ) {
  1210. if( $aScriptName=='auto' ) {
  1211. $aScriptName=basename($_SERVER['PHP_SELF']);
  1212. }
  1213. $urlarg = $this->GetURLArguments(true);
  1214. return "<img src=\"".$aScriptName.'?'.$urlarg."\" ismap=\"ismap\" usemap=\"#".$aCSIMName.'" height="'.$this->img->height."\" alt=\"".$this->iCSIMImgAlt."\" />\n";
  1215. }
  1216. function GetTextsYMinMax($aY2=false) {
  1217. if( $aY2 ) {
  1218. $txts = $this->y2texts;
  1219. }
  1220. else {
  1221. $txts = $this->texts;
  1222. }
  1223. $n = count($txts);
  1224. $min=null;
  1225. $max=null;
  1226. for( $i=0; $i < $n; ++$i ) {
  1227. if( $txts[$i]->iScalePosY !== null && $txts[$i]->iScalePosX !== null ) {
  1228. if( $min === null ) {
  1229. $min = $max = $txts[$i]->iScalePosY ;
  1230. }
  1231. else {
  1232. $min = min($min,$txts[$i]->iScalePosY);
  1233. $max = max($max,$txts[$i]->iScalePosY);
  1234. }
  1235. }
  1236. }
  1237. if( $min !== null ) {
  1238. return array($min,$max);
  1239. }
  1240. else {
  1241. return null;
  1242. }
  1243. }
  1244. function GetTextsXMinMax($aY2=false) {
  1245. if( $aY2 ) {
  1246. $txts = $this->y2texts;
  1247. }
  1248. else {
  1249. $txts = $this->texts;
  1250. }
  1251. $n = count($txts);
  1252. $min=null;
  1253. $max=null;
  1254. for( $i=0; $i < $n; ++$i ) {
  1255. if( $txts[$i]->iScalePosY !== null && $txts[$i]->iScalePosX !== null ) {
  1256. if( $min === null ) {
  1257. $min = $max = $txts[$i]->iScalePosX ;
  1258. }
  1259. else {
  1260. $min = min($min,$txts[$i]->iScalePosX);
  1261. $max = max($max,$txts[$i]->iScalePosX);
  1262. }
  1263. }
  1264. }
  1265. if( $min !== null ) {
  1266. return array($min,$max);
  1267. }
  1268. else {
  1269. return null;
  1270. }
  1271. }
  1272. function GetXMinMax() {
  1273. list($min,$ymin) = $this->plots[0]->Min();
  1274. list($max,$ymax) = $this->plots[0]->Max();
  1275. $i=0;
  1276. // Some plots, e.g. PlotLine should not affect the scale
  1277. // and will return (null,null). We should ignore those
  1278. // values.
  1279. while( ($min===null || $max === null) && ($i < count($this->plots)-1) ) {
  1280. ++$i;
  1281. list($min,$ymin) = $this->plots[$i]->Min();
  1282. list($max,$ymax) = $this->plots[$i]->Max();
  1283. }
  1284. foreach( $this->plots as $p ) {
  1285. list($xmin,$ymin) = $p->Min();
  1286. list($xmax,$ymax) = $p->Max();
  1287. if( $xmin !== null && $xmax !== null ) {
  1288. $min = Min($xmin,$min);
  1289. $max = Max($xmax,$max);
  1290. }
  1291. }
  1292. if( $this->y2axis != null ) {
  1293. foreach( $this->y2plots as $p ) {
  1294. list($xmin,$ymin) = $p->Min();
  1295. list($xmax,$ymax) = $p->Max();
  1296. $min = Min($xmin,$min);
  1297. $max = Max($xmax,$max);
  1298. }
  1299. }
  1300. $n = count($this->ynaxis);
  1301. for( $i=0; $i < $n; ++$i ) {
  1302. if( $this->ynaxis[$i] != null) {
  1303. foreach( $this->ynplots[$i] as $p ) {
  1304. list($xmin,$ymin) = $p->Min();
  1305. list($xmax,$ymax) = $p->Max();
  1306. $min = Min($xmin,$min);
  1307. $max = Max($xmax,$max);
  1308. }
  1309. }
  1310. }
  1311. return array($min,$max);
  1312. }
  1313. function AdjustMarginsForTitles() {
  1314. $totrequired = ($this->title->t != '' ? $this->title->GetTextHeight($this->img) + $this->title->margin + 5 : 0 ) +
  1315. ($this->subtitle->t != '' ? $this->subtitle->GetTextHeight($this->img) + $this->subtitle->margin + 5 : 0 ) +
  1316. ($this->subsubtitle->t != '' ? $this->subsubtitle->GetTextHeight($this->img) + $this->subsubtitle->margin + 5 : 0 ) ;
  1317. $btotrequired = 0;
  1318. if($this->xaxis != null && !$this->xaxis->hide && !$this->xaxis->hide_labels ) {
  1319. // Minimum bottom margin
  1320. if( $this->xaxis->title->t != '' ) {
  1321. if( $this->img->a == 90 ) {
  1322. $btotrequired = $this->yaxis->title->GetTextHeight($this->img) + 7 ;
  1323. }
  1324. else {
  1325. $btotrequired = $this->xaxis->title->GetTextHeight($this->img) + 7 ;
  1326. }
  1327. }
  1328. else {
  1329. $btotrequired = 0;
  1330. }
  1331. if( $this->img->a == 90 ) {
  1332. $this->img->SetFont($this->yaxis->font_family,$this->yaxis->font_style,
  1333. $this->yaxis->font_size);
  1334. $lh = $this->img->GetTextHeight('Mg',$this->yaxis->label_angle);
  1335. }
  1336. else {
  1337. $this->img->SetFont($this->xaxis->font_family,$this->xaxis->font_style,
  1338. $this->xaxis->font_size);
  1339. $lh = $this->img->GetTextHeight('Mg',$this->xaxis->label_angle);
  1340. }
  1341. $btotrequired += $lh + 6;
  1342. }
  1343. if( $this->img->a == 90 ) {
  1344. // DO Nothing. It gets too messy to do this properly for 90 deg...
  1345. }
  1346. else{
  1347. if( $this->img->top_margin < $totrequired ) {
  1348. $this->SetMargin($this->img->left_margin,$this->img->right_margin,
  1349. $totrequired,$this->img->bottom_margin);
  1350. }
  1351. if( $this->img->bottom_margin < $btotrequired ) {
  1352. $this->SetMargin($this->img->left_margin,$this->img->right_margin,
  1353. $this->img->top_margin,$btotrequired);
  1354. }
  1355. }
  1356. }
  1357. function StrokeStore($aStrokeFileName) {
  1358. // Get the handler to prevent the library from sending the
  1359. // image to the browser
  1360. $ih = $this->Stroke(_IMG_HANDLER);
  1361. // Stroke it to a file
  1362. $this->img->Stream($aStrokeFileName);
  1363. // Send it back to browser
  1364. $this->img->Headers();
  1365. $this->img->Stream();
  1366. }
  1367. function doAutoscaleXAxis() {
  1368. //Check if we should autoscale x-axis
  1369. if( !$this->xscale->IsSpecified() ) {
  1370. if( substr($this->axtype,0,4) == "text" ) {
  1371. $max=0;
  1372. $n = count($this->plots);
  1373. for($i=0; $i < $n; ++$i ) {
  1374. $p = $this->plots[$i];
  1375. // We need some unfortunate sub class knowledge here in order
  1376. // to increase number of data points in case it is a line plot
  1377. // which has the barcenter set. If not it could mean that the
  1378. // last point of the data is outside the scale since the barcenter
  1379. // settings means that we will shift the entire plot half a tick step
  1380. // to the right in oder to align with the center of the bars.
  1381. if( class_exists('BarPlot',false) ) {
  1382. $cl = strtolower(get_class($p));
  1383. if( (class_exists('BarPlot',false) && ($p instanceof BarPlot)) || empty($p->barcenter) ) {
  1384. $max=max($max,$p->numpoints-1);
  1385. }
  1386. else {
  1387. $max=max($max,$p->numpoints);
  1388. }
  1389. }
  1390. else {
  1391. if( empty($p->barcenter) ) {
  1392. $max=max($max,$p->numpoints-1);
  1393. }
  1394. else {
  1395. $max=max($max,$p->numpoints);
  1396. }
  1397. }
  1398. }
  1399. $min=0;
  1400. if( $this->y2axis != null ) {
  1401. foreach( $this->y2plots as $p ) {
  1402. $max=max($max,$p->numpoints-1);
  1403. }
  1404. }
  1405. $n = count($this->ynaxis);
  1406. for( $i=0; $i < $n; ++$i ) {
  1407. if( $this->ynaxis[$i] != null) {
  1408. foreach( $this->ynplots[$i] as $p ) {
  1409. $max=max($max,$p->numpoints-1);
  1410. }
  1411. }
  1412. }
  1413. $this->xscale->Update($this->img,$min,$max);
  1414. $this->xscale->ticks->Set($this->xaxis->tick_step,1);
  1415. $this->xscale->ticks->SupressMinorTickMarks();
  1416. }
  1417. else {
  1418. list($min,$max) = $this->GetXMinMax();
  1419. $lres = $this->GetLinesXMinMax($this->lines);
  1420. if( $lres ) {
  1421. list($linmin,$linmax) = $lres ;
  1422. $min = min($min,$linmin);
  1423. $max = max($max,$linmax);
  1424. }
  1425. $lres = $this->GetLinesXMinMax($this->y2lines);
  1426. if( $lres ) {
  1427. list($linmin,$linmax) = $lres ;
  1428. $min = min($min,$linmin);
  1429. $max = max($max,$linmax);
  1430. }
  1431. $tres = $this->GetTextsXMinMax();
  1432. if( $tres ) {
  1433. list($tmin,$tmax) = $tres ;
  1434. $min = min($min,$tmin);
  1435. $max = max($max,$tmax);
  1436. }
  1437. $tres = $this->GetTextsXMinMax(true);
  1438. if( $tres ) {
  1439. list($tmin,$tmax) = $tres ;
  1440. $min = min($min,$tmin);
  1441. $max = max($max,$tmax);
  1442. }
  1443. $this->xscale->AutoScale($this->img,$min,$max,round($this->img->plotwidth/$this->xtick_factor));
  1444. }
  1445. //Adjust position of y-axis and y2-axis to minimum/maximum of x-scale
  1446. if( !is_numeric($this->yaxis->pos) && !is_string($this->yaxis->pos) ) {
  1447. $this->yaxis->SetPos($this->xscale->GetMinVal());
  1448. }
  1449. }
  1450. elseif( $this->xscale->IsSpecified() &&
  1451. ( $this->xscale->auto_ticks || !$this->xscale->ticks->IsSpecified()) ) {
  1452. // The tick calculation will use the user suplied min/max values to determine
  1453. // the ticks. If auto_ticks is false the exact user specifed min and max
  1454. // values will be used for the scale.
  1455. // If auto_ticks is true then the scale might be slightly adjusted
  1456. // so that the min and max values falls on an even major step.
  1457. $min = $this->xscale->scale[0];
  1458. $max = $this->xscale->scale[1];
  1459. $this->xscale->AutoScale($this->img,$min,$max,round($this->img->plotwidth/$this->xtick_factor),false);
  1460. // Now make sure we show enough precision to accurate display the
  1461. // labels. If this is not done then the user might end up with
  1462. // a scale that might actually start with, say 13.5, butdue to rounding
  1463. // the scale label will ony show 14.
  1464. if( abs(floor($min)-$min) > 0 ) {
  1465. // If the user has set a format then we bail out
  1466. if( $this->xscale->ticks->label_formatstr == '' && $this->xscale->ticks->label_dateformatstr == '' ) {
  1467. $this->xscale->ticks->precision = abs( floor(log10( abs(floor($min)-$min))) )+1;
  1468. }
  1469. }
  1470. }
  1471. // Position the optional Y2 and Yn axis to the rightmost position of the x-axis
  1472. if( $this->y2axis != null ) {
  1473. if( !is_numeric($this->y2axis->pos) && !is_string($this->y2axis->pos) ) {
  1474. $this->y2axis->SetPos($this->xscale->GetMaxVal());
  1475. }
  1476. $this->y2axis->SetTitleSide(SIDE_RIGHT);
  1477. }
  1478. $n = count($this->ynaxis);
  1479. $nY2adj = $this->y2axis != null ? $this->iYAxisDeltaPos : 0;
  1480. for( $i=0; $i < $n; ++$i ) {
  1481. if( $this->ynaxis[$i] != null ) {
  1482. if( !is_numeric($this->ynaxis[$i]->pos) && !is_string($this->ynaxis[$i]->pos) ) {
  1483. $this->ynaxis[$i]->SetPos($this->xscale->GetMaxVal());
  1484. $this->ynaxis[$i]->SetPosAbsDelta($i*$this->iYAxisDeltaPos + $nY2adj);
  1485. }
  1486. $this->ynaxis[$i]->SetTitleSide(SIDE_RIGHT);
  1487. }
  1488. }
  1489. }
  1490. function doAutoScaleYnAxis() {
  1491. if( $this->y2scale != null) {
  1492. if( !$this->y2scale->IsSpecified() && count($this->y2plots)>0 ) {
  1493. list($min,$max) = $this->GetPlotsYMinMax($this->y2plots);
  1494. $lres = $this->GetLinesYMinMax($this->y2lines);
  1495. if( is_array($lres) ) {
  1496. list($linmin,$linmax) = $lres ;
  1497. $min = min($min,$linmin);
  1498. $max = max($max,$linmax);
  1499. }
  1500. $tres = $this->GetTextsYMinMax(true);
  1501. if( is_array($tres) ) {
  1502. list($tmin,$tmax) = $tres ;
  1503. $min = min($min,$tmin);
  1504. $max = max($max,$tmax);
  1505. }
  1506. $this->y2scale->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor);
  1507. }
  1508. elseif( $this->y2scale->IsSpecified() && ( $this->y2scale->auto_ticks || !$this->y2scale->ticks->IsSpecified()) ) {
  1509. // The tick calculation will use the user suplied min/max values to determine
  1510. // the ticks. If auto_ticks is false the exact user specifed min and max
  1511. // values will be used for the scale.
  1512. // If auto_ticks is true then the scale might be slightly adjusted
  1513. // so that the min and max values falls on an even major step.
  1514. $min = $this->y2scale->scale[0];
  1515. $max = $this->y2scale->scale[1];
  1516. $this->y2scale->AutoScale($this->img,$min,$max,
  1517. $this->img->plotheight/$this->ytick_factor,
  1518. $this->y2scale->auto_ticks);
  1519. // Now make sure we show enough precision to accurate display the
  1520. // labels. If this is not done then the user might end up with
  1521. // a scale that might actually start with, say 13.5, butdue to rounding
  1522. // the scale label will ony show 14.
  1523. if( abs(floor($min)-$min) > 0 ) {
  1524. // If the user has set a format then we bail out
  1525. if( $this->y2scale->ticks->label_formatstr == '' && $this->y2scale->ticks->label_dateformatstr == '' ) {
  1526. $this->y2scale->ticks->precision = abs( floor(log10( abs(floor($min)-$min))) )+1;
  1527. }
  1528. }
  1529. }
  1530. }
  1531. //
  1532. // Autoscale the extra Y-axises
  1533. //
  1534. $n = count($this->ynaxis);
  1535. for( $i=0; $i < $n; ++$i ) {
  1536. if( $this->ynscale[$i] != null) {
  1537. if( !$this->ynscale[$i]->IsSpecified() && count($this->ynplots[$i])>0 ) {
  1538. list($min,$max) = $this->GetPlotsYMinMax($this->ynplots[$i]);
  1539. $this->ynscale[$i]->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor);
  1540. }
  1541. elseif( $this->ynscale[$i]->IsSpecified() && ( $this->ynscale[$i]->auto_ticks || !$this->ynscale[$i]->ticks->IsSpecified()) ) {
  1542. // The tick calculation will use the user suplied min/max values to determine
  1543. // the ticks. If auto_ticks is false the exact user specifed min and max
  1544. // values will be used for the scale.
  1545. // If auto_ticks is true then the scale might be slightly adjusted
  1546. // so that the min and max values falls on an even major step.
  1547. $min = $this->ynscale[$i]->scale[0];
  1548. $max = $this->ynscale[$i]->scale[1];
  1549. $this->ynscale[$i]->AutoScale($this->img,$min,$max,
  1550. $this->img->plotheight/$this->ytick_factor,
  1551. $this->ynscale[$i]->auto_ticks);
  1552. // Now make sure we show enough precision to accurate display the
  1553. // labels. If this is not done then the user might end up with
  1554. // a scale that might actually start with, say 13.5, butdue to rounding
  1555. // the scale label will ony show 14.
  1556. if( abs(floor($min)-$min) > 0 ) {
  1557. // If the user has set a format then we bail out
  1558. if( $this->ynscale[$i]->ticks->label_formatstr == '' && $this->ynscale[$i]->ticks->label_dateformatstr == '' ) {
  1559. $this->ynscale[$i]->ticks->precision = abs( floor(log10( abs(floor($min)-$min))) )+1;
  1560. }
  1561. }
  1562. }
  1563. }
  1564. }
  1565. }
  1566. function doAutoScaleYAxis() {
  1567. //Check if we should autoscale y-axis
  1568. if( !$this->yscale->IsSpecified() && count($this->plots)>0 ) {
  1569. list($min,$max) = $this->GetPlotsYMinMax($this->plots);
  1570. $lres = $this->GetLinesYMinMax($this->lines);
  1571. if( is_array($lres) ) {
  1572. list($linmin,$linmax) = $lres ;
  1573. $min = min($min,$linmin);
  1574. $max = max($max,$linmax);
  1575. }
  1576. $tres = $this->GetTextsYMinMax();
  1577. if( is_array($tres) ) {
  1578. list($tmin,$tmax) = $tres ;
  1579. $min = min($min,$tmin);
  1580. $max = max($max,$tmax);
  1581. }
  1582. $this->yscale->AutoScale($this->img,$min,$max,
  1583. $this->img->plotheight/$this->ytick_factor);
  1584. }
  1585. elseif( $this->yscale->IsSpecified() && ( $this->yscale->auto_ticks || !$this->yscale->ticks->IsSpecified()) ) {
  1586. // The tick calculation will use the user suplied min/max values to determine
  1587. // the ticks. If auto_ticks is false the exact user specifed min and max
  1588. // values will be used for the scale.
  1589. // If auto_ticks is true then the scale might be slightly adjusted
  1590. // so that the min and max values falls on an even major step.
  1591. $min = $this->yscale->scale[0];
  1592. $max = $this->yscale->scale[1];
  1593. $this->yscale->AutoScale($this->img,$min,$max,
  1594. $this->img->plotheight/$this->ytick_factor,
  1595. $this->yscale->auto_ticks);
  1596. // Now make sure we show enough precision to accurate display the
  1597. // labels. If this is not done then the user might end up with
  1598. // a scale that might actually start with, say 13.5, butdue to rounding
  1599. // the scale label will ony show 14.
  1600. if( abs(floor($min)-$min) > 0 ) {
  1601. // If the user has set a format then we bail out
  1602. if( $this->yscale->ticks->label_formatstr == '' && $this->yscale->ticks->label_dateformatstr == '' ) {
  1603. $this->yscale->ticks->precision = abs( floor(log10( abs(floor($min)-$min))) )+1;
  1604. }
  1605. }
  1606. }
  1607. }
  1608. function InitScaleConstants() {
  1609. // Setup scale constants
  1610. if( $this->yscale ) $this->yscale->InitConstants($this->img);
  1611. if( $this->xscale ) $this->xscale->InitConstants($this->img);
  1612. if( $this->y2scale ) $this->y2scale->InitConstants($this->img);
  1613. $n=count($this->ynscale);
  1614. for($i=0; $i < $n; ++$i) {
  1615. if( $this->ynscale[$i] ) {
  1616. $this->ynscale[$i]->InitConstants($this->img);
  1617. }
  1618. }
  1619. }
  1620. function doPrestrokeAdjustments() {
  1621. // Do any pre-stroke adjustment that is needed by the different plot types
  1622. // (i.e bar plots want's to add an offset to the x-labels etc)
  1623. for($i=0; $i < count($this->plots) ; ++$i ) {
  1624. $this->plots[$i]->PreStrokeAdjust($this);
  1625. $this->plots[$i]->DoLegend($this);
  1626. }
  1627. // Any plots on the second Y scale?
  1628. if( $this->y2scale != null ) {
  1629. for($i=0; $i<count($this->y2plots) ; ++$i ) {
  1630. $this->y2plots[$i]->PreStrokeAdjust($this);
  1631. $this->y2plots[$i]->DoLegend($this);
  1632. }
  1633. }
  1634. // Any plots on the extra Y axises?
  1635. $n = count($this->ynaxis);
  1636. for($i=0; $i<$n ; ++$i ) {
  1637. if( $this->ynplots == null || $this->ynplots[$i] == null) {
  1638. JpGraphError::RaiseL(25032,$i);//("No plots for Y-axis nbr:$i");
  1639. }
  1640. $m = count($this->ynplots[$i]);
  1641. for($j=0; $j < $m; ++$j ) {
  1642. $this->ynplots[$i][$j]->PreStrokeAdjust($this);
  1643. $this->ynplots[$i][$j]->DoLegend($this);
  1644. }
  1645. }
  1646. }
  1647. function StrokeBands($aDepth,$aCSIM) {
  1648. // Stroke bands
  1649. if( $this->bands != null && !$aCSIM) {
  1650. for($i=0; $i < count($this->bands); ++$i) {
  1651. // Stroke all bands that asks to be in the background
  1652. if( $this->bands[$i]->depth == $aDepth ) {
  1653. $this->bands[$i]->Stroke($this->img,$this->xscale,$this->yscale);
  1654. }
  1655. }
  1656. }
  1657. if( $this->y2bands != null && $this->y2scale != null && !$aCSIM ) {
  1658. for($i=0; $i < count($this->y2bands); ++$i) {
  1659. // Stroke all bands that asks to be in the foreground
  1660. if( $this->y2bands[$i]->depth == $aDepth ) {
  1661. $this->y2bands[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
  1662. }
  1663. }
  1664. }
  1665. }
  1666. // Stroke the graph
  1667. // $aStrokeFileName If != "" the image will be written to this file and NOT
  1668. // streamed back to the browser
  1669. function Stroke($aStrokeFileName='') {
  1670. // Fist make a sanity check that user has specified a scale
  1671. if( empty($this->yscale) ) {
  1672. JpGraphError::RaiseL(25031);//('You must specify what scale to use with a call to Graph::SetScale().');
  1673. }
  1674. // Start by adjusting the margin so that potential titles will fit.
  1675. $this->AdjustMarginsForTitles();
  1676. // Give the plot a chance to do any scale adjuments the individual plots
  1677. // wants to do. Right now this is only used by the contour plot to set scale
  1678. // limits
  1679. for($i=0; $i < count($this->plots) ; ++$i ) {
  1680. $this->plots[$i]->PreScaleSetup($this);
  1681. }
  1682. // Init scale constants that are used to calculate the transformation from
  1683. // world to pixel coordinates
  1684. $this->InitScaleConstants();
  1685. // If the filename is the predefined value = '_csim_special_'
  1686. // we assume that the call to stroke only needs to do enough
  1687. // to correctly generate the CSIM maps.
  1688. // We use this variable to skip things we don't strictly need
  1689. // to do to generate the image map to improve performance
  1690. // a best we can. Therefor you will see a lot of tests !$_csim in the
  1691. // code below.
  1692. $_csim = ($aStrokeFileName===_CSIM_SPECIALFILE);
  1693. // If we are called the second time (perhaps the user has called GetHTMLImageMap()
  1694. // himself then the legends have alsready been populated once in order to get the
  1695. // CSIM coordinats. Since we do not want the legends to be populated a second time
  1696. // we clear the legends
  1697. $this->legend->Clear();
  1698. // We need to know if we have stroked the plot in the
  1699. // GetCSIMareas. Otherwise the CSIM hasn't been generated
  1700. // and in the case of GetCSIM called before stroke to generate
  1701. // CSIM without storing an image to disk GetCSIM must call Stroke.
  1702. $this->iHasStroked = true;
  1703. // Setup pre-stroked adjustments and Legends
  1704. $this->doPrestrokeAdjustments();
  1705. // Bail out if any of the Y-axis not been specified and
  1706. // has no plots. (This means it is impossible to do autoscaling and
  1707. // no other scale was given so we can't possible draw anything). If you use manual
  1708. // scaling you also have to supply the tick steps as well.
  1709. if( (!$this->yscale->IsSpecified() && count($this->plots)==0) ||
  1710. ($this->y2scale!=null && !$this->y2scale->IsSpecified() && count($this->y2plots)==0) ) {
  1711. //$e = "n=".count($this->y2plots)."\n";
  1712. // $e = "Can't draw unspecified Y-scale.<br>\nYou have either:<br>\n";
  1713. // $e .= "1. Specified an Y axis for autoscaling but have not supplied any plots<br>\n";
  1714. // $e .= "2. Specified a scale manually but have forgot to specify the tick steps";
  1715. JpGraphError::RaiseL(25026);
  1716. }
  1717. // Bail out if no plots and no specified X-scale
  1718. if( (!$this->xscale->IsSpecified() && count($this->plots)==0 && count($this->y2plots)==0) ) {
  1719. JpGraphError::RaiseL(25034);//("<strong>JpGraph: Can't draw unspecified X-scale.</strong><br>No plots.<br>");
  1720. }
  1721. // Autoscale the normal Y-axis
  1722. $this->doAutoScaleYAxis();
  1723. // Autoscale all additiopnal y-axis
  1724. $this->doAutoScaleYnAxis();
  1725. // Autoscale the regular x-axis and position the y-axis properly
  1726. $this->doAutoScaleXAxis();
  1727. // If we have a negative values and x-axis position is at 0
  1728. // we need to supress the first and possible the last tick since
  1729. // they will be drawn on top of the y-axis (and possible y2 axis)
  1730. // The test below might seem strange the reasone being that if
  1731. // the user hasn't specified a value for position this will not
  1732. // be set until we do the stroke for the axis so as of now it
  1733. // is undefined.
  1734. // For X-text scale we ignore all this since the tick are usually
  1735. // much further in and not close to the Y-axis. Hence the test
  1736. // for 'text'
  1737. if( ($this->yaxis->pos==$this->xscale->GetMinVal() || (is_string($this->yaxis->pos) && $this->yaxis->pos=='min')) &&
  1738. !is_numeric($this->xaxis->pos) && $this->yscale->GetMinVal() < 0 &&
  1739. substr($this->axtype,0,4) != 'text' && $this->xaxis->pos != 'min' ) {
  1740. //$this->yscale->ticks->SupressZeroLabel(false);
  1741. $this->xscale->ticks->SupressFirst();
  1742. if( $this->y2axis != null ) {
  1743. $this->xscale->ticks->SupressLast();
  1744. }
  1745. }
  1746. elseif( !is_numeric($this->yaxis->pos) && $this->yaxis->pos=='max' ) {
  1747. $this->xscale->ticks->SupressLast();
  1748. }
  1749. if( !$_csim ) {
  1750. $this->StrokePlotArea();
  1751. if( $this->iIconDepth == DEPTH_BACK ) {
  1752. $this->StrokeIcons();
  1753. }
  1754. }
  1755. $this->StrokeAxis(false);
  1756. // Stroke colored bands
  1757. $this->StrokeBands(DEPTH_BACK,$_csim);
  1758. if( $this->grid_depth == DEPTH_BACK && !$_csim) {
  1759. $this->ygrid->Stroke();
  1760. $this->xgrid->Stroke();
  1761. }
  1762. // Stroke Y2-axis
  1763. if( $this->y2axis != null && !$_csim) {
  1764. $this->y2axis->Stroke($this->xscale);
  1765. $this->y2grid->Stroke();
  1766. }
  1767. // Stroke yn-axis
  1768. $n = count($this->ynaxis);
  1769. for( $i=0; $i < $n; ++$i ) {
  1770. $this->ynaxis[$i]->Stroke($this->xscale);
  1771. }
  1772. $oldoff=$this->xscale->off;
  1773. if( substr($this->axtype,0,4) == 'text' ) {
  1774. if( $this->text_scale_abscenteroff > -1 ) {
  1775. // For a text scale the scale factor is the number of pixel per step.
  1776. // Hence we can use the scale factor as a substitute for number of pixels
  1777. // per major scale step and use that in order to adjust the offset so that
  1778. // an object of width "abscenteroff" becomes centered.
  1779. $this->xscale->off += round($this->xscale->scale_factor/2)-round($this->text_scale_abscenteroff/2);
  1780. }
  1781. else {
  1782. $this->xscale->off += ceil($this->xscale->scale_factor*$this->text_scale_off*$this->xscale->ticks->minor_step);
  1783. }
  1784. }
  1785. if( $this->iDoClipping ) {
  1786. $oldimage = $this->img->CloneCanvasH();
  1787. }
  1788. if( ! $this->y2orderback ) {
  1789. // Stroke all plots for Y1 axis
  1790. for($i=0; $i < count($this->plots); ++$i) {
  1791. $this->plots[$i]->Stroke($this->img,$this->xscale,$this->yscale);
  1792. $this->plots[$i]->StrokeMargin($this->img);
  1793. }
  1794. }
  1795. // Stroke all plots for Y2 axis
  1796. if( $this->y2scale != null ) {
  1797. for($i=0; $i< count($this->y2plots); ++$i ) {
  1798. $this->y2plots[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
  1799. }
  1800. }
  1801. if( $this->y2orderback ) {
  1802. // Stroke all plots for Y1 axis
  1803. for($i=0; $i < count($this->plots); ++$i) {
  1804. $this->plots[$i]->Stroke($this->img,$this->xscale,$this->yscale);
  1805. $this->plots[$i]->StrokeMargin($this->img);
  1806. }
  1807. }
  1808. $n = count($this->ynaxis);
  1809. for( $i=0; $i < $n; ++$i ) {
  1810. $m = count($this->ynplots[$i]);
  1811. for( $j=0; $j < $m; ++$j ) {
  1812. $this->ynplots[$i][$j]->Stroke($this->img,$this->xscale,$this->ynscale[$i]);
  1813. $this->ynplots[$i][$j]->StrokeMargin($this->img);
  1814. }
  1815. }
  1816. if( $this->iIconDepth == DEPTH_FRONT) {
  1817. $this->StrokeIcons();
  1818. }
  1819. if( $this->iDoClipping ) {
  1820. // Clipping only supports graphs at 0 and 90 degrees
  1821. if( $this->img->a == 0 ) {
  1822. $this->img->CopyCanvasH($oldimage,$this->img->img,
  1823. $this->img->left_margin,$this->img->top_margin,
  1824. $this->img->left_margin,$this->img->top_margin,
  1825. $this->img->plotwidth+1,$this->img->plotheight);
  1826. }
  1827. elseif( $this->img->a == 90 ) {
  1828. $adj = ($this->img->height - $this->img->width)/2;
  1829. $this->img->CopyCanvasH($oldimage,$this->img->img,
  1830. $this->img->bottom_margin-$adj,$this->img->left_margin+$adj,
  1831. $this->img->bottom_margin-$adj,$this->img->left_margin+$adj,
  1832. $this->img->plotheight+1,$this->img->plotwidth);
  1833. }
  1834. else {
  1835. JpGraphError::RaiseL(25035,$this->img->a);//('You have enabled clipping. Cliping is only supported for graphs at 0 or 90 degrees rotation. Please adjust you current angle (='.$this->img->a.' degrees) or disable clipping.');
  1836. }
  1837. $this->img->Destroy();
  1838. $this->img->SetCanvasH($oldimage);
  1839. }
  1840. $this->xscale->off=$oldoff;
  1841. if( $this->grid_depth == DEPTH_FRONT && !$_csim ) {
  1842. $this->ygrid->Stroke();
  1843. $this->xgrid->Stroke();
  1844. }
  1845. // Stroke colored bands
  1846. $this->StrokeBands(DEPTH_FRONT,$_csim);
  1847. // Finally draw the axis again since some plots may have nagged
  1848. // the axis in the edges.
  1849. if( !$_csim ) {
  1850. $this->StrokeAxis();
  1851. }
  1852. if( $this->y2scale != null && !$_csim ) {
  1853. $this->y2axis->Stroke($this->xscale,false);
  1854. }
  1855. if( !$_csim ) {
  1856. $this->StrokePlotBox();
  1857. }
  1858. // The titles and legends never gets rotated so make sure
  1859. // that the angle is 0 before stroking them
  1860. $aa = $this->img->SetAngle(0);
  1861. $this->StrokeTitles();
  1862. $this->footer->Stroke($this->img);
  1863. $this->legend->Stroke($this->img);
  1864. $this->img->SetAngle($aa);
  1865. $this->StrokeTexts();
  1866. $this->StrokeTables();
  1867. if( !$_csim ) {
  1868. $this->img->SetAngle($aa);
  1869. // Draw an outline around the image map
  1870. if(_JPG_DEBUG) {
  1871. $this->DisplayClientSideaImageMapAreas();
  1872. }
  1873. // Should we do any final image transformation
  1874. if( $this->iImgTrans ) {
  1875. if( !class_exists('ImgTrans',false) ) {
  1876. require_once('jpgraph_imgtrans.php');
  1877. //JpGraphError::Raise('In order to use image transformation you must include the file jpgraph_imgtrans.php in your script.');
  1878. }
  1879. $tform = new ImgTrans($this->img->img);
  1880. $this->img->img = $tform->Skew3D($this->iImgTransHorizon,$this->iImgTransSkewDist,
  1881. $this->iImgTransDirection,$this->iImgTransHighQ,
  1882. $this->iImgTransMinSize,$this->iImgTransFillColor,
  1883. $this->iImgTransBorder);
  1884. }
  1885. // If the filename is given as the special "__handle"
  1886. // then the image handler is returned and the image is NOT
  1887. // streamed back
  1888. if( $aStrokeFileName == _IMG_HANDLER ) {
  1889. return $this->img->img;
  1890. }
  1891. else {
  1892. // Finally stream the generated picture
  1893. $this->cache->PutAndStream($this->img,$this->cache_name,$this->inline,$aStrokeFileName);
  1894. }
  1895. }
  1896. }
  1897. function SetAxisLabelBackground($aType,$aXFColor='lightgray',$aXColor='black',$aYFColor='lightgray',$aYColor='black') {
  1898. $this->iAxisLblBgType = $aType;
  1899. $this->iXAxisLblBgFillColor = $aXFColor;
  1900. $this->iXAxisLblBgColor = $aXColor;
  1901. $this->iYAxisLblBgFillColor = $aYFColor;
  1902. $this->iYAxisLblBgColor = $aYColor;
  1903. }
  1904. function StrokeAxisLabelBackground() {
  1905. // Types
  1906. // 0 = No background
  1907. // 1 = Only X-labels, length of axis
  1908. // 2 = Only Y-labels, length of axis
  1909. // 3 = As 1 but extends to width of graph
  1910. // 4 = As 2 but extends to height of graph
  1911. // 5 = Combination of 3 & 4
  1912. // 6 = Combination of 1 & 2
  1913. $t = $this->iAxisLblBgType ;
  1914. if( $t < 1 ) return;
  1915. // Stroke optional X-axis label background color
  1916. if( $t == 1 || $t == 3 || $t == 5 || $t == 6 ) {
  1917. $this->img->PushColor($this->iXAxisLblBgFillColor);
  1918. if( $t == 1 || $t == 6 ) {
  1919. $xl = $this->img->left_margin;
  1920. $yu = $this->img->height - $this->img->bottom_margin + 1;
  1921. $xr = $this->img->width - $this->img->right_margin ;
  1922. $yl = $this->img->height-1-$this->frame_weight;
  1923. }
  1924. else { // t==3 || t==5
  1925. $xl = $this->frame_weight;
  1926. $yu = $this->img->height - $this->img->bottom_margin + 1;
  1927. $xr = $this->img->width - 1 - $this->frame_weight;
  1928. $yl = $this->img->height-1-$this->frame_weight;
  1929. }
  1930. $this->img->FilledRectangle($xl,$yu,$xr,$yl);
  1931. $this->img->PopColor();
  1932. // Check if we should add the vertical lines at left and right edge
  1933. if( $this->iXAxisLblBgColor !== '' ) {
  1934. // Hardcode to one pixel wide
  1935. $this->img->SetLineWeight(1);
  1936. $this->img->PushColor($this->iXAxisLblBgColor);
  1937. if( $t == 1 || $t == 6 ) {
  1938. $this->img->Line($xl,$yu,$xl,$yl);
  1939. $this->img->Line($xr,$yu,$xr,$yl);
  1940. }
  1941. else {
  1942. $xl = $this->img->width - $this->img->right_margin ;
  1943. $this->img->Line($xl,$yu-1,$xr,$yu-1);
  1944. }
  1945. $this->img->PopColor();
  1946. }
  1947. }
  1948. if( $t == 2 || $t == 4 || $t == 5 || $t == 6 ) {
  1949. $this->img->PushColor($this->iYAxisLblBgFillColor);
  1950. if( $t == 2 || $t == 6 ) {
  1951. $xl = $this->frame_weight;
  1952. $yu = $this->frame_weight+$this->img->top_margin;
  1953. $xr = $this->img->left_margin - 1;
  1954. $yl = $this->img->height - $this->img->bottom_margin + 1;
  1955. }
  1956. else {
  1957. $xl = $this->frame_weight;
  1958. $yu = $this->frame_weight;
  1959. $xr = $this->img->left_margin - 1;
  1960. $yl = $this->img->height-1-$this->frame_weight;
  1961. }
  1962. $this->img->FilledRectangle($xl,$yu,$xr,$yl);
  1963. $this->img->PopColor();
  1964. // Check if we should add the vertical lines at left and right edge
  1965. if( $this->iXAxisLblBgColor !== '' ) {
  1966. $this->img->PushColor($this->iXAxisLblBgColor);
  1967. if( $t == 2 || $t == 6 ) {
  1968. $this->img->Line($xl,$yu-1,$xr,$yu-1);
  1969. $this->img->Line($xl,$yl-1,$xr,$yl-1);
  1970. }
  1971. else {
  1972. $this->img->Line($xr+1,$yu,$xr+1,$this->img->top_margin);
  1973. }
  1974. $this->img->PopColor();
  1975. }
  1976. }
  1977. }
  1978. function StrokeAxis($aStrokeLabels=true) {
  1979. if( $aStrokeLabels ) {
  1980. $this->StrokeAxisLabelBackground();
  1981. }
  1982. // Stroke axis
  1983. if( $this->iAxisStyle != AXSTYLE_SIMPLE ) {
  1984. switch( $this->iAxisStyle ) {
  1985. case AXSTYLE_BOXIN :
  1986. $toppos = SIDE_DOWN;
  1987. $bottompos = SIDE_UP;
  1988. $leftpos = SIDE_RIGHT;
  1989. $rightpos = SIDE_LEFT;
  1990. break;
  1991. case AXSTYLE_BOXOUT :
  1992. $toppos = SIDE_UP;
  1993. $bottompos = SIDE_DOWN;
  1994. $leftpos = SIDE_LEFT;
  1995. $rightpos = SIDE_RIGHT;
  1996. break;
  1997. case AXSTYLE_YBOXIN:
  1998. $toppos = FALSE;
  1999. $bottompos = SIDE_UP;
  2000. $leftpos = SIDE_RIGHT;
  2001. $rightpos = SIDE_LEFT;
  2002. break;
  2003. case AXSTYLE_YBOXOUT:
  2004. $toppos = FALSE;
  2005. $bottompos = SIDE_DOWN;
  2006. $leftpos = SIDE_LEFT;
  2007. $rightpos = SIDE_RIGHT;
  2008. break;
  2009. default:
  2010. JpGRaphError::RaiseL(25036,$this->iAxisStyle); //('Unknown AxisStyle() : '.$this->iAxisStyle);
  2011. break;
  2012. }
  2013. // By default we hide the first label so it doesn't cross the
  2014. // Y-axis in case the positon hasn't been set by the user.
  2015. // However, if we use a box we always want the first value
  2016. // displayed so we make sure it will be displayed.
  2017. $this->xscale->ticks->SupressFirst(false);
  2018. // Now draw the bottom X-axis
  2019. $this->xaxis->SetPos('min');
  2020. $this->xaxis->SetLabelSide(SIDE_DOWN);
  2021. $this->xaxis->scale->ticks->SetSide($bottompos);
  2022. $this->xaxis->Stroke($this->yscale,$aStrokeLabels);
  2023. if( $toppos !== FALSE ) {
  2024. // We also want a top X-axis
  2025. $this->xaxis = $this->xaxis;
  2026. $this->xaxis->SetPos('max');
  2027. $this->xaxis->SetLabelSide(SIDE_UP);
  2028. // No title for the top X-axis
  2029. if( $aStrokeLabels ) {
  2030. $this->xaxis->title->Set('');
  2031. }
  2032. $this->xaxis->scale->ticks->SetSide($toppos);
  2033. $this->xaxis->Stroke($this->yscale,$aStrokeLabels);
  2034. }
  2035. // Stroke the left Y-axis
  2036. $this->yaxis->SetPos('min');
  2037. $this->yaxis->SetLabelSide(SIDE_LEFT);
  2038. $this->yaxis->scale->ticks->SetSide($leftpos);
  2039. $this->yaxis->Stroke($this->xscale,$aStrokeLabels);
  2040. // Stroke the right Y-axis
  2041. $this->yaxis->SetPos('max');
  2042. // No title for the right side
  2043. if( $aStrokeLabels ) {
  2044. $this->yaxis->title->Set('');
  2045. }
  2046. $this->yaxis->SetLabelSide(SIDE_RIGHT);
  2047. $this->yaxis->scale->ticks->SetSide($rightpos);
  2048. $this->yaxis->Stroke($this->xscale,$aStrokeLabels);
  2049. }
  2050. else {
  2051. $this->xaxis->Stroke($this->yscale,$aStrokeLabels);
  2052. $this->yaxis->Stroke($this->xscale,$aStrokeLabels);
  2053. }
  2054. }
  2055. // Private helper function for backgound image
  2056. static function LoadBkgImage($aImgFormat='',$aFile='',$aImgStr='') {
  2057. if( $aImgStr != '' ) {
  2058. return Image::CreateFromString($aImgStr);
  2059. }
  2060. // Remove case sensitivity and setup appropriate function to create image
  2061. // Get file extension. This should be the LAST '.' separated part of the filename
  2062. $e = explode('.',$aFile);
  2063. $ext = strtolower($e[count($e)-1]);
  2064. if ($ext == "jpeg") {
  2065. $ext = "jpg";
  2066. }
  2067. if( trim($ext) == '' ) {
  2068. $ext = 'png'; // Assume PNG if no extension specified
  2069. }
  2070. if( $aImgFormat == '' ) {
  2071. $imgtag = $ext;
  2072. }
  2073. else {
  2074. $imgtag = $aImgFormat;
  2075. }
  2076. $supported = imagetypes();
  2077. if( ( $ext == 'jpg' && !($supported & IMG_JPG) ) ||
  2078. ( $ext == 'gif' && !($supported & IMG_GIF) ) ||
  2079. ( $ext == 'png' && !($supported & IMG_PNG) ) ||
  2080. ( $ext == 'bmp' && !($supported & IMG_WBMP) ) ||
  2081. ( $ext == 'xpm' && !($supported & IMG_XPM) ) ) {
  2082. JpGraphError::RaiseL(25037,$aFile);//('The image format of your background image ('.$aFile.') is not supported in your system configuration. ');
  2083. }
  2084. if( $imgtag == "jpg" || $imgtag == "jpeg") {
  2085. $f = "imagecreatefromjpeg";
  2086. $imgtag = "jpg";
  2087. }
  2088. else {
  2089. $f = "imagecreatefrom".$imgtag;
  2090. }
  2091. // Compare specified image type and file extension
  2092. if( $imgtag != $ext ) {
  2093. //$t = "Background image seems to be of different type (has different file extension) than specified imagetype. Specified: '".$aImgFormat."'File: '".$aFile."'";
  2094. JpGraphError::RaiseL(25038, $aImgFormat, $aFile);
  2095. }
  2096. $img = @$f($aFile);
  2097. if( !$img ) {
  2098. JpGraphError::RaiseL(25039,$aFile);//(" Can't read background image: '".$aFile."'");
  2099. }
  2100. return $img;
  2101. }
  2102. function StrokePlotGrad() {
  2103. if( $this->plot_gradtype < 0 )
  2104. return;
  2105. $grad = new Gradient($this->img);
  2106. $xl = $this->img->left_margin;
  2107. $yt = $this->img->top_margin;
  2108. $xr = $xl + $this->img->plotwidth+1 ;
  2109. $yb = $yt + $this->img->plotheight ;
  2110. $grad->FilledRectangle($xl,$yt,$xr,$yb,$this->plot_gradfrom,$this->plot_gradto,$this->plot_gradtype);
  2111. }
  2112. function StrokeBackgroundGrad() {
  2113. if( $this->bkg_gradtype < 0 )
  2114. return;
  2115. $grad = new Gradient($this->img);
  2116. if( $this->bkg_gradstyle == BGRAD_PLOT ) {
  2117. $xl = $this->img->left_margin;
  2118. $yt = $this->img->top_margin;
  2119. $xr = $xl + $this->img->plotwidth+1 ;
  2120. $yb = $yt + $this->img->plotheight ;
  2121. $grad->FilledRectangle($xl,$yt,$xr,$yb,$this->bkg_gradfrom,$this->bkg_gradto,$this->bkg_gradtype);
  2122. }
  2123. else {
  2124. $xl = 0;
  2125. $yt = 0;
  2126. $xr = $xl + $this->img->width - 1;
  2127. $yb = $yt + $this->img->height - 1 ;
  2128. if( $this->doshadow ) {
  2129. $xr -= $this->shadow_width;
  2130. $yb -= $this->shadow_width;
  2131. }
  2132. if( $this->doframe ) {
  2133. $yt += $this->frame_weight;
  2134. $yb -= $this->frame_weight;
  2135. $xl += $this->frame_weight;
  2136. $xr -= $this->frame_weight;
  2137. }
  2138. $aa = $this->img->SetAngle(0);
  2139. $grad->FilledRectangle($xl,$yt,$xr,$yb,$this->bkg_gradfrom,$this->bkg_gradto,$this->bkg_gradtype);
  2140. $aa = $this->img->SetAngle($aa);
  2141. }
  2142. }
  2143. function StrokeFrameBackground() {
  2144. if( $this->background_image != '' && $this->background_cflag != '' ) {
  2145. JpGraphError::RaiseL(25040);//('It is not possible to specify both a background image and a background country flag.');
  2146. }
  2147. if( $this->background_image != '' ) {
  2148. $bkgimg = $this->LoadBkgImage($this->background_image_format,$this->background_image);
  2149. }
  2150. elseif( $this->background_cflag != '' ) {
  2151. if( ! class_exists('FlagImages',false) ) {
  2152. JpGraphError::RaiseL(25041);//('In order to use Country flags as backgrounds you must include the "jpgraph_flags.php" file.');
  2153. }
  2154. $fobj = new FlagImages(FLAGSIZE4);
  2155. $dummy='';
  2156. $bkgimg = $fobj->GetImgByName($this->background_cflag,$dummy);
  2157. $this->background_image_mix = $this->background_cflag_mix;
  2158. $this->background_image_type = $this->background_cflag_type;
  2159. }
  2160. else {
  2161. return ;
  2162. }
  2163. $bw = ImageSX($bkgimg);
  2164. $bh = ImageSY($bkgimg);
  2165. // No matter what the angle is we always stroke the image and frame
  2166. // assuming it is 0 degree
  2167. $aa = $this->img->SetAngle(0);
  2168. switch( $this->background_image_type ) {
  2169. case BGIMG_FILLPLOT: // Resize to just fill the plotarea
  2170. $this->FillMarginArea();
  2171. $this->StrokeFrame();
  2172. // Special case to hande 90 degree rotated graph corectly
  2173. if( $aa == 90 ) {
  2174. $this->img->SetAngle(90);
  2175. $this->FillPlotArea();
  2176. $aa = $this->img->SetAngle(0);
  2177. $adj = ($this->img->height - $this->img->width)/2;
  2178. $this->img->CopyMerge($bkgimg,
  2179. $this->img->bottom_margin-$adj,$this->img->left_margin+$adj,
  2180. 0,0,
  2181. $this->img->plotheight+1,$this->img->plotwidth,
  2182. $bw,$bh,$this->background_image_mix);
  2183. }
  2184. else {
  2185. $this->FillPlotArea();
  2186. $this->img->CopyMerge($bkgimg,
  2187. $this->img->left_margin,$this->img->top_margin+1,
  2188. 0,0,$this->img->plotwidth+1,$this->img->plotheight,
  2189. $bw,$bh,$this->background_image_mix);
  2190. }
  2191. break;
  2192. case BGIMG_FILLFRAME: // Fill the whole area from upper left corner, resize to just fit
  2193. $hadj=0; $vadj=0;
  2194. if( $this->doshadow ) {
  2195. $hadj = $this->shadow_width;
  2196. $vadj = $this->shadow_width;
  2197. }
  2198. $this->FillMarginArea();
  2199. $this->FillPlotArea();
  2200. $this->img->CopyMerge($bkgimg,0,0,0,0,$this->img->width-$hadj,$this->img->height-$vadj,
  2201. $bw,$bh,$this->background_image_mix);
  2202. $this->StrokeFrame();
  2203. break;
  2204. case BGIMG_COPY: // Just copy the image from left corner, no resizing
  2205. $this->FillMarginArea();
  2206. $this->FillPlotArea();
  2207. $this->img->CopyMerge($bkgimg,0,0,0,0,$bw,$bh,
  2208. $bw,$bh,$this->background_image_mix);
  2209. $this->StrokeFrame();
  2210. break;
  2211. case BGIMG_CENTER: // Center original image in the plot area
  2212. $this->FillMarginArea();
  2213. $this->FillPlotArea();
  2214. $centerx = round($this->img->plotwidth/2+$this->img->left_margin-$bw/2);
  2215. $centery = round($this->img->plotheight/2+$this->img->top_margin-$bh/2);
  2216. $this->img->CopyMerge($bkgimg,$centerx,$centery,0,0,$bw,$bh,
  2217. $bw,$bh,$this->background_image_mix);
  2218. $this->StrokeFrame();
  2219. break;
  2220. case BGIMG_FREE: // Just copy the image to the specified location
  2221. $this->img->CopyMerge($bkgimg,
  2222. $this->background_image_xpos,$this->background_image_ypos,
  2223. 0,0,$bw,$bh,$bw,$bh,$this->background_image_mix);
  2224. $this->StrokeFrame(); // New
  2225. break;
  2226. default:
  2227. JpGraphError::RaiseL(25042);//(" Unknown background image layout");
  2228. }
  2229. $this->img->SetAngle($aa);
  2230. }
  2231. // Private
  2232. // Draw a frame around the image
  2233. function StrokeFrame() {
  2234. if( !$this->doframe ) return;
  2235. if( $this->background_image_type <= 1 && ($this->bkg_gradtype < 0 || ($this->bkg_gradtype > 0 && $this->bkg_gradstyle==BGRAD_PLOT)) ) {
  2236. $c = $this->margin_color;
  2237. }
  2238. else {
  2239. $c = false;
  2240. }
  2241. if( $this->doshadow ) {
  2242. $this->img->SetColor($this->frame_color);
  2243. $this->img->ShadowRectangle(0,0,$this->img->width,$this->img->height,
  2244. $c,$this->shadow_width,$this->shadow_color);
  2245. }
  2246. elseif( $this->framebevel ) {
  2247. if( $c ) {
  2248. $this->img->SetColor($this->margin_color);
  2249. $this->img->FilledRectangle(0,0,$this->img->width-1,$this->img->height-1);
  2250. }
  2251. $this->img->Bevel(1,1,$this->img->width-2,$this->img->height-2,
  2252. $this->framebeveldepth,
  2253. $this->framebevelcolor1,$this->framebevelcolor2);
  2254. if( $this->framebevelborder ) {
  2255. $this->img->SetColor($this->framebevelbordercolor);
  2256. $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);
  2257. }
  2258. }
  2259. else {
  2260. $this->img->SetLineWeight($this->frame_weight);
  2261. if( $c ) {
  2262. $this->img->SetColor($this->margin_color);
  2263. $this->img->FilledRectangle(0,0,$this->img->width-1,$this->img->height-1);
  2264. }
  2265. $this->img->SetColor($this->frame_color);
  2266. $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);
  2267. }
  2268. }
  2269. function FillMarginArea() {
  2270. $hadj=0; $vadj=0;
  2271. if( $this->doshadow ) {
  2272. $hadj = $this->shadow_width;
  2273. $vadj = $this->shadow_width;
  2274. }
  2275. $this->img->SetColor($this->margin_color);
  2276. // $this->img->FilledRectangle(0,0,$this->img->width-1-$hadj,$this->img->height-1-$vadj);
  2277. $this->img->FilledRectangle(0,0,$this->img->width-1-$hadj,$this->img->top_margin);
  2278. $this->img->FilledRectangle(0,$this->img->top_margin,$this->img->left_margin,$this->img->height-1-$hadj);
  2279. $this->img->FilledRectangle($this->img->left_margin+1,
  2280. $this->img->height-$this->img->bottom_margin,
  2281. $this->img->width-1-$hadj,
  2282. $this->img->height-1-$hadj);
  2283. $this->img->FilledRectangle($this->img->width-$this->img->right_margin,
  2284. $this->img->top_margin+1,
  2285. $this->img->width-1-$hadj,
  2286. $this->img->height-$this->img->bottom_margin-1);
  2287. }
  2288. function FillPlotArea() {
  2289. $this->img->PushColor($this->plotarea_color);
  2290. $this->img->FilledRectangle($this->img->left_margin,
  2291. $this->img->top_margin,
  2292. $this->img->width-$this->img->right_margin,
  2293. $this->img->height-$this->img->bottom_margin);
  2294. $this->img->PopColor();
  2295. }
  2296. // Stroke the plot area with either a solid color or a background image
  2297. function StrokePlotArea() {
  2298. // Note: To be consistent we really should take a possible shadow
  2299. // into account. However, that causes some problem for the LinearScale class
  2300. // since in the current design it does not have any links to class Graph which
  2301. // means it has no way of compensating for the adjusted plotarea in case of a
  2302. // shadow. So, until I redesign LinearScale we can't compensate for this.
  2303. // So just set the two adjustment parameters to zero for now.
  2304. $boxadj = 0; //$this->doframe ? $this->frame_weight : 0 ;
  2305. $adj = 0; //$this->doshadow ? $this->shadow_width : 0 ;
  2306. if( $this->background_image != '' || $this->background_cflag != '' ) {
  2307. $this->StrokeFrameBackground();
  2308. }
  2309. else {
  2310. $aa = $this->img->SetAngle(0);
  2311. $this->StrokeFrame();
  2312. $aa = $this->img->SetAngle($aa);
  2313. $this->StrokeBackgroundGrad();
  2314. if( $this->bkg_gradtype < 0 || ($this->bkg_gradtype > 0 && $this->bkg_gradstyle==BGRAD_MARGIN) ) {
  2315. $this->FillPlotArea();
  2316. }
  2317. $this->StrokePlotGrad();
  2318. }
  2319. }
  2320. function StrokeIcons() {
  2321. $n = count($this->iIcons);
  2322. for( $i=0; $i < $n; ++$i ) {
  2323. $this->iIcons[$i]->StrokeWithScale($this->img,$this->xscale,$this->yscale);
  2324. }
  2325. }
  2326. function StrokePlotBox() {
  2327. // Should we draw a box around the plot area?
  2328. if( $this->boxed ) {
  2329. $this->img->SetLineWeight(1);
  2330. $this->img->SetLineStyle('solid');
  2331. $this->img->SetColor($this->box_color);
  2332. for($i=0; $i < $this->box_weight; ++$i ) {
  2333. $this->img->Rectangle(
  2334. $this->img->left_margin-$i,$this->img->top_margin-$i,
  2335. $this->img->width-$this->img->right_margin+$i,
  2336. $this->img->height-$this->img->bottom_margin+$i);
  2337. }
  2338. }
  2339. }
  2340. function SetTitleBackgroundFillStyle($aStyle,$aColor1='black',$aColor2='white') {
  2341. $this->titlebkg_fillstyle = $aStyle;
  2342. $this->titlebkg_scolor1 = $aColor1;
  2343. $this->titlebkg_scolor2 = $aColor2;
  2344. }
  2345. function SetTitleBackground($aBackColor='gray', $aStyle=TITLEBKG_STYLE1, $aFrameStyle=TITLEBKG_FRAME_NONE, $aFrameColor='black', $aFrameWeight=1, $aBevelHeight=3, $aEnable=true) {
  2346. $this->titlebackground = $aEnable;
  2347. $this->titlebackground_color = $aBackColor;
  2348. $this->titlebackground_style = $aStyle;
  2349. $this->titlebackground_framecolor = $aFrameColor;
  2350. $this->titlebackground_framestyle = $aFrameStyle;
  2351. $this->titlebackground_frameweight = $aFrameWeight;
  2352. $this->titlebackground_bevelheight = $aBevelHeight ;
  2353. }
  2354. function StrokeTitles() {
  2355. $margin=3;
  2356. if( $this->titlebackground ) {
  2357. // Find out height
  2358. $this->title->margin += 2 ;
  2359. $h = $this->title->GetTextHeight($this->img)+$this->title->margin+$margin;
  2360. if( $this->subtitle->t != '' && !$this->subtitle->hide ) {
  2361. $h += $this->subtitle->GetTextHeight($this->img)+$margin+
  2362. $this->subtitle->margin;
  2363. $h += 2;
  2364. }
  2365. if( $this->subsubtitle->t != '' && !$this->subsubtitle->hide ) {
  2366. $h += $this->subsubtitle->GetTextHeight($this->img)+$margin+
  2367. $this->subsubtitle->margin;
  2368. $h += 2;
  2369. }
  2370. $this->img->PushColor($this->titlebackground_color);
  2371. if( $this->titlebackground_style === TITLEBKG_STYLE1 ) {
  2372. // Inside the frame
  2373. if( $this->framebevel ) {
  2374. $x1 = $y1 = $this->framebeveldepth + 1 ;
  2375. $x2 = $this->img->width - $this->framebeveldepth - 2 ;
  2376. $this->title->margin += $this->framebeveldepth + 1 ;
  2377. $h += $y1 ;
  2378. $h += 2;
  2379. }
  2380. else {
  2381. $x1 = $y1 = $this->frame_weight;
  2382. $x2 = $this->img->width - $this->frame_weight-1;
  2383. }
  2384. }
  2385. elseif( $this->titlebackground_style === TITLEBKG_STYLE2 ) {
  2386. // Cover the frame as well
  2387. $x1 = $y1 = 0;
  2388. $x2 = $this->img->width - 1 ;
  2389. }
  2390. elseif( $this->titlebackground_style === TITLEBKG_STYLE3 ) {
  2391. // Cover the frame as well (the difference is that
  2392. // for style==3 a bevel frame border is on top
  2393. // of the title background)
  2394. $x1 = $y1 = 0;
  2395. $x2 = $this->img->width - 1 ;
  2396. $h += $this->framebeveldepth ;
  2397. $this->title->margin += $this->framebeveldepth ;
  2398. }
  2399. else {
  2400. JpGraphError::RaiseL(25043);//('Unknown title background style.');
  2401. }
  2402. if( $this->titlebackground_framestyle === 3 ) {
  2403. $h += $this->titlebackground_bevelheight*2 + 1 ;
  2404. $this->title->margin += $this->titlebackground_bevelheight ;
  2405. }
  2406. if( $this->doshadow ) {
  2407. $x2 -= $this->shadow_width ;
  2408. }
  2409. $indent=0;
  2410. if( $this->titlebackground_framestyle == TITLEBKG_FRAME_BEVEL ) {
  2411. $indent = $this->titlebackground_bevelheight;
  2412. }
  2413. if( $this->titlebkg_fillstyle==TITLEBKG_FILLSTYLE_HSTRIPED ) {
  2414. $this->img->FilledRectangle2($x1+$indent,$y1+$indent,$x2-$indent,$h-$indent,
  2415. $this->titlebkg_scolor1,
  2416. $this->titlebkg_scolor2);
  2417. }
  2418. elseif( $this->titlebkg_fillstyle==TITLEBKG_FILLSTYLE_VSTRIPED ) {
  2419. $this->img->FilledRectangle2($x1+$indent,$y1+$indent,$x2-$indent,$h-$indent,
  2420. $this->titlebkg_scolor1,
  2421. $this->titlebkg_scolor2,2);
  2422. }
  2423. else {
  2424. // Solid fill
  2425. $this->img->FilledRectangle($x1,$y1,$x2,$h);
  2426. }
  2427. $this->img->PopColor();
  2428. $this->img->PushColor($this->titlebackground_framecolor);
  2429. $this->img->SetLineWeight($this->titlebackground_frameweight);
  2430. if( $this->titlebackground_framestyle == TITLEBKG_FRAME_FULL ) {
  2431. // Frame background
  2432. $this->img->Rectangle($x1,$y1,$x2,$h);
  2433. }
  2434. elseif( $this->titlebackground_framestyle == TITLEBKG_FRAME_BOTTOM ) {
  2435. // Bottom line only
  2436. $this->img->Line($x1,$h,$x2,$h);
  2437. }
  2438. elseif( $this->titlebackground_framestyle == TITLEBKG_FRAME_BEVEL ) {
  2439. $this->img->Bevel($x1,$y1,$x2,$h,$this->titlebackground_bevelheight);
  2440. }
  2441. $this->img->PopColor();
  2442. // This is clumsy. But we neeed to stroke the whole graph frame if it is
  2443. // set to bevel to get the bevel shading on top of the text background
  2444. if( $this->framebevel && $this->doframe && $this->titlebackground_style === 3 ) {
  2445. $this->img->Bevel(1,1,$this->img->width-2,$this->img->height-2,
  2446. $this->framebeveldepth,
  2447. $this->framebevelcolor1,$this->framebevelcolor2);
  2448. if( $this->framebevelborder ) {
  2449. $this->img->SetColor($this->framebevelbordercolor);
  2450. $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);
  2451. }
  2452. }
  2453. }
  2454. // Stroke title
  2455. $y = $this->title->margin;
  2456. if( $this->title->halign == 'center' ) {
  2457. $this->title->Center(0,$this->img->width,$y);
  2458. }
  2459. elseif( $this->title->halign == 'left' ) {
  2460. $this->title->SetPos($this->title->margin+2,$y);
  2461. }
  2462. elseif( $this->title->halign == 'right' ) {
  2463. $indent = 0;
  2464. if( $this->doshadow ) {
  2465. $indent = $this->shadow_width+2;
  2466. }
  2467. $this->title->SetPos($this->img->width-$this->title->margin-$indent,$y,'right');
  2468. }
  2469. $this->title->Stroke($this->img);
  2470. // ... and subtitle
  2471. $y += $this->title->GetTextHeight($this->img) + $margin + $this->subtitle->margin;
  2472. if( $this->subtitle->halign == 'center' ) {
  2473. $this->subtitle->Center(0,$this->img->width,$y);
  2474. }
  2475. elseif( $this->subtitle->halign == 'left' ) {
  2476. $this->subtitle->SetPos($this->subtitle->margin+2,$y);
  2477. }
  2478. elseif( $this->subtitle->halign == 'right' ) {
  2479. $indent = 0;
  2480. if( $this->doshadow )
  2481. $indent = $this->shadow_width+2;
  2482. $this->subtitle->SetPos($this->img->width-$this->subtitle->margin-$indent,$y,'right');
  2483. }
  2484. $this->subtitle->Stroke($this->img);
  2485. // ... and subsubtitle
  2486. $y += $this->subtitle->GetTextHeight($this->img) + $margin + $this->subsubtitle->margin;
  2487. if( $this->subsubtitle->halign == 'center' ) {
  2488. $this->subsubtitle->Center(0,$this->img->width,$y);
  2489. }
  2490. elseif( $this->subsubtitle->halign == 'left' ) {
  2491. $this->subsubtitle->SetPos($this->subsubtitle->margin+2,$y);
  2492. }
  2493. elseif( $this->subsubtitle->halign == 'right' ) {
  2494. $indent = 0;
  2495. if( $this->doshadow )
  2496. $indent = $this->shadow_width+2;
  2497. $this->subsubtitle->SetPos($this->img->width-$this->subsubtitle->margin-$indent,$y,'right');
  2498. }
  2499. $this->subsubtitle->Stroke($this->img);
  2500. // ... and fancy title
  2501. $this->tabtitle->Stroke($this->img);
  2502. }
  2503. function StrokeTexts() {
  2504. // Stroke any user added text objects
  2505. if( $this->texts != null ) {
  2506. for($i=0; $i < count($this->texts); ++$i) {
  2507. $this->texts[$i]->StrokeWithScale($this->img,$this->xscale,$this->yscale);
  2508. }
  2509. }
  2510. if( $this->y2texts != null && $this->y2scale != null ) {
  2511. for($i=0; $i < count($this->y2texts); ++$i) {
  2512. $this->y2texts[$i]->StrokeWithScale($this->img,$this->xscale,$this->y2scale);
  2513. }
  2514. }
  2515. }
  2516. function StrokeTables() {
  2517. if( $this->iTables != null ) {
  2518. $n = count($this->iTables);
  2519. for( $i=0; $i < $n; ++$i ) {
  2520. $this->iTables[$i]->StrokeWithScale($this->img,$this->xscale,$this->yscale);
  2521. }
  2522. }
  2523. }
  2524. function DisplayClientSideaImageMapAreas() {
  2525. // Debug stuff - display the outline of the image map areas
  2526. $csim='';
  2527. foreach ($this->plots as $p) {
  2528. $csim.= $p->GetCSIMareas();
  2529. }
  2530. $csim .= $this->legend->GetCSIMareas();
  2531. if (preg_match_all("/area shape=\"(\w+)\" coords=\"([0-9\, ]+)\"/", $csim, $coords)) {
  2532. $this->img->SetColor($this->csimcolor);
  2533. $n = count($coords[0]);
  2534. for ($i=0; $i < $n; $i++) {
  2535. if ( $coords[1][$i] == 'poly' ) {
  2536. preg_match_all('/\s*([0-9]+)\s*,\s*([0-9]+)\s*,*/',$coords[2][$i],$pts);
  2537. $this->img->SetStartPoint($pts[1][count($pts[0])-1],$pts[2][count($pts[0])-1]);
  2538. $m = count($pts[0]);
  2539. for ($j=0; $j < $m; $j++) {
  2540. $this->img->LineTo($pts[1][$j],$pts[2][$j]);
  2541. }
  2542. } elseif ( $coords[1][$i] == 'rect' ) {
  2543. $pts = preg_split('/,/', $coords[2][$i]);
  2544. $this->img->SetStartPoint($pts[0],$pts[1]);
  2545. $this->img->LineTo($pts[2],$pts[1]);
  2546. $this->img->LineTo($pts[2],$pts[3]);
  2547. $this->img->LineTo($pts[0],$pts[3]);
  2548. $this->img->LineTo($pts[0],$pts[1]);
  2549. }
  2550. }
  2551. }
  2552. }
  2553. // Text scale offset in world coordinates
  2554. function SetTextScaleOff($aOff) {
  2555. $this->text_scale_off = $aOff;
  2556. $this->xscale->text_scale_off = $aOff;
  2557. }
  2558. // Text width of bar to be centered in absolute pixels
  2559. function SetTextScaleAbsCenterOff($aOff) {
  2560. $this->text_scale_abscenteroff = $aOff;
  2561. }
  2562. // Get Y min and max values for added lines
  2563. function GetLinesYMinMax( $aLines ) {
  2564. $n = count($aLines);
  2565. if( $n == 0 ) return false;
  2566. $min = $aLines[0]->scaleposition ;
  2567. $max = $min ;
  2568. $flg = false;
  2569. for( $i=0; $i < $n; ++$i ) {
  2570. if( $aLines[$i]->direction == HORIZONTAL ) {
  2571. $flg = true ;
  2572. $v = $aLines[$i]->scaleposition ;
  2573. if( $min > $v ) $min = $v ;
  2574. if( $max < $v ) $max = $v ;
  2575. }
  2576. }
  2577. return $flg ? array($min,$max) : false ;
  2578. }
  2579. // Get X min and max values for added lines
  2580. function GetLinesXMinMax( $aLines ) {
  2581. $n = count($aLines);
  2582. if( $n == 0 ) return false ;
  2583. $min = $aLines[0]->scaleposition ;
  2584. $max = $min ;
  2585. $flg = false;
  2586. for( $i=0; $i < $n; ++$i ) {
  2587. if( $aLines[$i]->direction == VERTICAL ) {
  2588. $flg = true ;
  2589. $v = $aLines[$i]->scaleposition ;
  2590. if( $min > $v ) $min = $v ;
  2591. if( $max < $v ) $max = $v ;
  2592. }
  2593. }
  2594. return $flg ? array($min,$max) : false ;
  2595. }
  2596. // Get min and max values for all included plots
  2597. function GetPlotsYMinMax($aPlots) {
  2598. $n = count($aPlots);
  2599. $i=0;
  2600. do {
  2601. list($xmax,$max) = $aPlots[$i]->Max();
  2602. } while( ++$i < $n && !is_numeric($max) );
  2603. $i=0;
  2604. do {
  2605. list($xmin,$min) = $aPlots[$i]->Min();
  2606. } while( ++$i < $n && !is_numeric($min) );
  2607. if( !is_numeric($min) || !is_numeric($max) ) {
  2608. JpGraphError::RaiseL(25044);//('Cannot use autoscaling since it is impossible to determine a valid min/max value of the Y-axis (only null values).');
  2609. }
  2610. for($i=0; $i < $n; ++$i ) {
  2611. list($xmax,$ymax)=$aPlots[$i]->Max();
  2612. list($xmin,$ymin)=$aPlots[$i]->Min();
  2613. if (is_numeric($ymax)) $max=max($max,$ymax);
  2614. if (is_numeric($ymin)) $min=min($min,$ymin);
  2615. }
  2616. if( $min == '' ) $min = 0;
  2617. if( $max == '' ) $max = 0;
  2618. if( $min == 0 && $max == 0 ) {
  2619. // Special case if all values are 0
  2620. $min=0;$max=1;
  2621. }
  2622. return array($min,$max);
  2623. }
  2624. } // Class
  2625. //===================================================
  2626. // CLASS LineProperty
  2627. // Description: Holds properties for a line
  2628. //===================================================
  2629. class LineProperty {
  2630. public $iWeight=1, $iColor='black', $iStyle='solid', $iShow=true;
  2631. function __construct($aWeight=1,$aColor='black',$aStyle='solid') {
  2632. $this->iWeight = $aWeight;
  2633. $this->iColor = $aColor;
  2634. $this->iStyle = $aStyle;
  2635. }
  2636. function SetColor($aColor) {
  2637. $this->iColor = $aColor;
  2638. }
  2639. function SetWeight($aWeight) {
  2640. $this->iWeight = $aWeight;
  2641. }
  2642. function SetStyle($aStyle) {
  2643. $this->iStyle = $aStyle;
  2644. }
  2645. function Show($aShow=true) {
  2646. $this->iShow=$aShow;
  2647. }
  2648. function Stroke($aImg,$aX1,$aY1,$aX2,$aY2) {
  2649. if( $this->iShow ) {
  2650. $aImg->PushColor($this->iColor);
  2651. $oldls = $aImg->line_style;
  2652. $oldlw = $aImg->line_weight;
  2653. $aImg->SetLineWeight($this->iWeight);
  2654. $aImg->SetLineStyle($this->iStyle);
  2655. $aImg->StyleLine($aX1,$aY1,$aX2,$aY2);
  2656. $aImg->PopColor($this->iColor);
  2657. $aImg->line_style = $oldls;
  2658. $aImg->line_weight = $oldlw;
  2659. }
  2660. }
  2661. }
  2662. //===================================================
  2663. // CLASS GraphTabTitle
  2664. // Description: Draw "tab" titles on top of graphs
  2665. //===================================================
  2666. class GraphTabTitle extends Text{
  2667. private $corner = 6 , $posx = 7, $posy = 4;
  2668. private $fillcolor='lightyellow',$bordercolor='black';
  2669. private $align = 'left', $width=TABTITLE_WIDTHFIT;
  2670. function __construct() {
  2671. $this->t = '';
  2672. $this->font_style = FS_BOLD;
  2673. $this->hide = true;
  2674. $this->color = 'darkred';
  2675. }
  2676. function SetColor($aTxtColor,$aFillColor='lightyellow',$aBorderColor='black') {
  2677. $this->color = $aTxtColor;
  2678. $this->fillcolor = $aFillColor;
  2679. $this->bordercolor = $aBorderColor;
  2680. }
  2681. function SetFillColor($aFillColor) {
  2682. $this->fillcolor = $aFillColor;
  2683. }
  2684. function SetTabAlign($aAlign) {
  2685. $this->align = $aAlign;
  2686. }
  2687. function SetWidth($aWidth) {
  2688. $this->width = $aWidth ;
  2689. }
  2690. function Set($t) {
  2691. $this->t = $t;
  2692. $this->hide = false;
  2693. }
  2694. function SetCorner($aD) {
  2695. $this->corner = $aD ;
  2696. }
  2697. function Stroke($aImg,$aDummy1=null,$aDummy2=null) {
  2698. if( $this->hide )
  2699. return;
  2700. $this->boxed = false;
  2701. $w = $this->GetWidth($aImg) + 2*$this->posx;
  2702. $h = $this->GetTextHeight($aImg) + 2*$this->posy;
  2703. $x = $aImg->left_margin;
  2704. $y = $aImg->top_margin;
  2705. if( $this->width === TABTITLE_WIDTHFIT ) {
  2706. if( $this->align == 'left' ) {
  2707. $p = array($x, $y,
  2708. $x, $y-$h+$this->corner,
  2709. $x + $this->corner,$y-$h,
  2710. $x + $w - $this->corner, $y-$h,
  2711. $x + $w, $y-$h+$this->corner,
  2712. $x + $w, $y);
  2713. }
  2714. elseif( $this->align == 'center' ) {
  2715. $x += round($aImg->plotwidth/2) - round($w/2);
  2716. $p = array($x, $y,
  2717. $x, $y-$h+$this->corner,
  2718. $x + $this->corner, $y-$h,
  2719. $x + $w - $this->corner, $y-$h,
  2720. $x + $w, $y-$h+$this->corner,
  2721. $x + $w, $y);
  2722. }
  2723. else {
  2724. $x += $aImg->plotwidth -$w;
  2725. $p = array($x, $y,
  2726. $x, $y-$h+$this->corner,
  2727. $x + $this->corner,$y-$h,
  2728. $x + $w - $this->corner, $y-$h,
  2729. $x + $w, $y-$h+$this->corner,
  2730. $x + $w, $y);
  2731. }
  2732. }
  2733. else {
  2734. if( $this->width === TABTITLE_WIDTHFULL ) {
  2735. $w = $aImg->plotwidth ;
  2736. }
  2737. else {
  2738. $w = $this->width ;
  2739. }
  2740. // Make the tab fit the width of the plot area
  2741. $p = array($x, $y,
  2742. $x, $y-$h+$this->corner,
  2743. $x + $this->corner,$y-$h,
  2744. $x + $w - $this->corner, $y-$h,
  2745. $x + $w, $y-$h+$this->corner,
  2746. $x + $w, $y);
  2747. }
  2748. if( $this->halign == 'left' ) {
  2749. $aImg->SetTextAlign('left','bottom');
  2750. $x += $this->posx;
  2751. $y -= $this->posy;
  2752. }
  2753. elseif( $this->halign == 'center' ) {
  2754. $aImg->SetTextAlign('center','bottom');
  2755. $x += $w/2;
  2756. $y -= $this->posy;
  2757. }
  2758. else {
  2759. $aImg->SetTextAlign('right','bottom');
  2760. $x += $w - $this->posx;
  2761. $y -= $this->posy;
  2762. }
  2763. $aImg->SetColor($this->fillcolor);
  2764. $aImg->FilledPolygon($p);
  2765. $aImg->SetColor($this->bordercolor);
  2766. $aImg->Polygon($p,true);
  2767. $aImg->SetColor($this->color);
  2768. $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  2769. $aImg->StrokeText($x,$y,$this->t,0,'center');
  2770. }
  2771. }
  2772. //===================================================
  2773. // CLASS SuperScriptText
  2774. // Description: Format a superscript text
  2775. //===================================================
  2776. class SuperScriptText extends Text {
  2777. private $iSuper='';
  2778. private $sfont_family='',$sfont_style='',$sfont_size=8;
  2779. private $iSuperMargin=2,$iVertOverlap=4,$iSuperScale=0.65;
  2780. private $iSDir=0;
  2781. private $iSimple=false;
  2782. function __construct($aTxt='',$aSuper='',$aXAbsPos=0,$aYAbsPos=0) {
  2783. parent::__construct($aTxt,$aXAbsPos,$aYAbsPos);
  2784. $this->iSuper = $aSuper;
  2785. }
  2786. function FromReal($aVal,$aPrecision=2) {
  2787. // Convert a floating point number to scientific notation
  2788. $neg=1.0;
  2789. if( $aVal < 0 ) {
  2790. $neg = -1.0;
  2791. $aVal = -$aVal;
  2792. }
  2793. $l = floor(log10($aVal));
  2794. $a = sprintf("%0.".$aPrecision."f",round($aVal / pow(10,$l),$aPrecision));
  2795. $a *= $neg;
  2796. if( $this->iSimple && ($a == 1 || $a==-1) ) $a = '';
  2797. if( $a != '' ) {
  2798. $this->t = $a.' * 10';
  2799. }
  2800. else {
  2801. if( $neg == 1 ) {
  2802. $this->t = '10';
  2803. }
  2804. else {
  2805. $this->t = '-10';
  2806. }
  2807. }
  2808. $this->iSuper = $l;
  2809. }
  2810. function Set($aTxt,$aSuper='') {
  2811. $this->t = $aTxt;
  2812. $this->iSuper = $aSuper;
  2813. }
  2814. function SetSuperFont($aFontFam,$aFontStyle=FS_NORMAL,$aFontSize=8) {
  2815. $this->sfont_family = $aFontFam;
  2816. $this->sfont_style = $aFontStyle;
  2817. $this->sfont_size = $aFontSize;
  2818. }
  2819. // Total width of text
  2820. function GetWidth($aImg) {
  2821. $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  2822. $w = $aImg->GetTextWidth($this->t);
  2823. $aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size);
  2824. $w += $aImg->GetTextWidth($this->iSuper);
  2825. $w += $this->iSuperMargin;
  2826. return $w;
  2827. }
  2828. // Hight of font (approximate the height of the text)
  2829. function GetFontHeight($aImg) {
  2830. $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  2831. $h = $aImg->GetFontHeight();
  2832. $aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size);
  2833. $h += $aImg->GetFontHeight();
  2834. return $h;
  2835. }
  2836. // Hight of text
  2837. function GetTextHeight($aImg) {
  2838. $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
  2839. $h = $aImg->GetTextHeight($this->t);
  2840. $aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size);
  2841. $h += $aImg->GetTextHeight($this->iSuper);
  2842. return $h;
  2843. }
  2844. function Stroke($aImg,$ax=-1,$ay=-1) {
  2845. // To position the super script correctly we need different
  2846. // cases to handle the alignmewnt specified since that will
  2847. // determine how we can interpret the x,y coordinates
  2848. $w = parent::GetWidth($aImg);
  2849. $h = parent::GetTextHeight($aImg);
  2850. switch( $this->valign ) {
  2851. case 'top':
  2852. $sy = $this->y;
  2853. break;
  2854. case 'center':
  2855. $sy = $this->y - $h/2;
  2856. break;
  2857. case 'bottom':
  2858. $sy = $this->y - $h;
  2859. break;
  2860. default:
  2861. JpGraphError::RaiseL(25052);//('PANIC: Internal error in SuperScript::Stroke(). Unknown vertical alignment for text');
  2862. break;
  2863. }
  2864. switch( $this->halign ) {
  2865. case 'left':
  2866. $sx = $this->x + $w;
  2867. break;
  2868. case 'center':
  2869. $sx = $this->x + $w/2;
  2870. break;
  2871. case 'right':
  2872. $sx = $this->x;
  2873. break;
  2874. default:
  2875. JpGraphError::RaiseL(25053);//('PANIC: Internal error in SuperScript::Stroke(). Unknown horizontal alignment for text');
  2876. break;
  2877. }
  2878. $sx += $this->iSuperMargin;
  2879. $sy += $this->iVertOverlap;
  2880. // Should we automatically determine the font or
  2881. // has the user specified it explicetly?
  2882. if( $this->sfont_family == '' ) {
  2883. if( $this->font_family <= FF_FONT2 ) {
  2884. if( $this->font_family == FF_FONT0 ) {
  2885. $sff = FF_FONT0;
  2886. }
  2887. elseif( $this->font_family == FF_FONT1 ) {
  2888. if( $this->font_style == FS_NORMAL ) {
  2889. $sff = FF_FONT0;
  2890. }
  2891. else {
  2892. $sff = FF_FONT1;
  2893. }
  2894. }
  2895. else {
  2896. $sff = FF_FONT1;
  2897. }
  2898. $sfs = $this->font_style;
  2899. $sfz = $this->font_size;
  2900. }
  2901. else {
  2902. // TTF fonts
  2903. $sff = $this->font_family;
  2904. $sfs = $this->font_style;
  2905. $sfz = floor($this->font_size*$this->iSuperScale);
  2906. if( $sfz < 8 ) $sfz = 8;
  2907. }
  2908. $this->sfont_family = $sff;
  2909. $this->sfont_style = $sfs;
  2910. $this->sfont_size = $sfz;
  2911. }
  2912. else {
  2913. $sff = $this->sfont_family;
  2914. $sfs = $this->sfont_style;
  2915. $sfz = $this->sfont_size;
  2916. }
  2917. parent::Stroke($aImg,$ax,$ay);
  2918. // For the builtin fonts we need to reduce the margins
  2919. // since the bounding bx reported for the builtin fonts
  2920. // are much larger than for the TTF fonts.
  2921. if( $sff <= FF_FONT2 ) {
  2922. $sx -= 2;
  2923. $sy += 3;
  2924. }
  2925. $aImg->SetTextAlign('left','bottom');
  2926. $aImg->SetFont($sff,$sfs,$sfz);
  2927. $aImg->PushColor($this->color);
  2928. $aImg->StrokeText($sx,$sy,$this->iSuper,$this->iSDir,'left');
  2929. $aImg->PopColor();
  2930. }
  2931. }
  2932. //===================================================
  2933. // CLASS Grid
  2934. // Description: responsible for drawing grid lines in graph
  2935. //===================================================
  2936. class Grid {
  2937. protected $img;
  2938. protected $scale;
  2939. protected $majorcolor='#DDDDDD',$minorcolor='#EEEEEE';
  2940. protected $majortype='solid',$minortype='solid';
  2941. protected $show=false, $showMinor=false,$majorweight=1,$minorweight=1;
  2942. protected $fill=false,$fillcolor=array('#EFEFEF','#BBCCFF');
  2943. function __construct($aAxis) {
  2944. $this->scale = $aAxis->scale;
  2945. $this->img = $aAxis->img;
  2946. }
  2947. function SetColor($aMajColor,$aMinColor=false) {
  2948. $this->majorcolor=$aMajColor;
  2949. if( $aMinColor === false ) {
  2950. $aMinColor = $aMajColor ;
  2951. }
  2952. $this->minorcolor = $aMinColor;
  2953. }
  2954. function SetWeight($aMajorWeight,$aMinorWeight=1) {
  2955. $this->majorweight=$aMajorWeight;
  2956. $this->minorweight=$aMinorWeight;
  2957. }
  2958. // Specify if grid should be dashed, dotted or solid
  2959. function SetLineStyle($aMajorType,$aMinorType='solid') {
  2960. $this->majortype = $aMajorType;
  2961. $this->minortype = $aMinorType;
  2962. }
  2963. function SetStyle($aMajorType,$aMinorType='solid') {
  2964. $this->SetLineStyle($aMajorType,$aMinorType);
  2965. }
  2966. // Decide if both major and minor grid should be displayed
  2967. function Show($aShowMajor=true,$aShowMinor=false) {
  2968. $this->show=$aShowMajor;
  2969. $this->showMinor=$aShowMinor;
  2970. }
  2971. function SetFill($aFlg=true,$aColor1='lightgray',$aColor2='lightblue') {
  2972. $this->fill = $aFlg;
  2973. $this->fillcolor = array( $aColor1, $aColor2 );
  2974. }
  2975. // Display the grid
  2976. function Stroke() {
  2977. if( $this->showMinor && !$this->scale->textscale ) {
  2978. $this->DoStroke($this->scale->ticks->ticks_pos,$this->minortype,$this->minorcolor,$this->minorweight);
  2979. $this->DoStroke($this->scale->ticks->maj_ticks_pos,$this->majortype,$this->majorcolor,$this->majorweight);
  2980. }
  2981. else {
  2982. $this->DoStroke($this->scale->ticks->maj_ticks_pos,$this->majortype,$this->majorcolor,$this->majorweight);
  2983. }
  2984. }
  2985. //--------------
  2986. // Private methods
  2987. // Draw the grid
  2988. function DoStroke($aTicksPos,$aType,$aColor,$aWeight) {
  2989. if( !$this->show ) return;
  2990. $nbrgrids = count($aTicksPos);
  2991. if( $this->scale->type == 'y' ) {
  2992. $xl=$this->img->left_margin;
  2993. $xr=$this->img->width-$this->img->right_margin;
  2994. if( $this->fill ) {
  2995. // Draw filled areas
  2996. $y2 = $aTicksPos[0];
  2997. $i=1;
  2998. while( $i < $nbrgrids ) {
  2999. $y1 = $y2;
  3000. $y2 = $aTicksPos[$i++];
  3001. $this->img->SetColor($this->fillcolor[$i & 1]);
  3002. $this->img->FilledRectangle($xl,$y1,$xr,$y2);
  3003. }
  3004. }
  3005. $this->img->SetColor($aColor);
  3006. $this->img->SetLineWeight($aWeight);
  3007. // Draw grid lines
  3008. switch( $aType ) {
  3009. case 'solid': $style = LINESTYLE_SOLID; break;
  3010. case 'dotted': $style = LINESTYLE_DOTTED; break;
  3011. case 'dashed': $style = LINESTYLE_DASHED; break;
  3012. case 'longdashed': $style = LINESTYLE_LONGDASH; break;
  3013. default:
  3014. $style = LINESTYLE_SOLID; break;
  3015. }
  3016. for($i=0; $i < $nbrgrids; ++$i) {
  3017. $y=$aTicksPos[$i];
  3018. $this->img->StyleLine($xl,$y,$xr,$y,$style);
  3019. }
  3020. }
  3021. elseif( $this->scale->type == 'x' ) {
  3022. $yu=$this->img->top_margin;
  3023. $yl=$this->img->height-$this->img->bottom_margin;
  3024. $limit=$this->img->width-$this->img->right_margin;
  3025. if( $this->fill ) {
  3026. // Draw filled areas
  3027. $x2 = $aTicksPos[0];
  3028. $i=1;
  3029. while( $i < $nbrgrids ) {
  3030. $x1 = $x2;
  3031. $x2 = min($aTicksPos[$i++],$limit) ;
  3032. $this->img->SetColor($this->fillcolor[$i & 1]);
  3033. $this->img->FilledRectangle($x1,$yu,$x2,$yl);
  3034. }
  3035. }
  3036. $this->img->SetColor($aColor);
  3037. $this->img->SetLineWeight($aWeight);
  3038. // We must also test for limit since we might have
  3039. // an offset and the number of ticks is calculated with
  3040. // assumption offset==0 so we might end up drawing one
  3041. // to many gridlines
  3042. $i=0;
  3043. $x=$aTicksPos[$i];
  3044. while( $i<count($aTicksPos) && ($x=$aTicksPos[$i]) <= $limit ) {
  3045. if ( $aType == 'solid' ) $this->img->Line($x,$yl,$x,$yu);
  3046. elseif( $aType == 'dotted' ) $this->img->DashedLine($x,$yl,$x,$yu,1,6);
  3047. elseif( $aType == 'dashed' ) $this->img->DashedLine($x,$yl,$x,$yu,2,4);
  3048. elseif( $aType == 'longdashed' ) $this->img->DashedLine($x,$yl,$x,$yu,8,6);
  3049. ++$i;
  3050. }
  3051. }
  3052. else {
  3053. JpGraphError::RaiseL(25054,$this->scale->type);//('Internal error: Unknown grid axis ['.$this->scale->type.']');
  3054. }
  3055. return true;
  3056. }
  3057. } // Class
  3058. //===================================================
  3059. // CLASS Axis
  3060. // Description: Defines X and Y axis. Notes that at the
  3061. // moment the code is not really good since the axis on
  3062. // several occasion must know wheter it's an X or Y axis.
  3063. // This was a design decision to make the code easier to
  3064. // follow.
  3065. //===================================================
  3066. class AxisPrototype {
  3067. public $scale=null;
  3068. public $img=null;
  3069. public $hide=false,$hide_labels=false;
  3070. public $title=null;
  3071. public $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12,$label_angle=0;
  3072. public $tick_step=1;
  3073. public $pos = false;
  3074. public $ticks_label = array();
  3075. protected $weight=1;
  3076. protected $color=array(0,0,0),$label_color=array(0,0,0);
  3077. protected $ticks_label_colors=null;
  3078. protected $show_first_label=true,$show_last_label=true;
  3079. protected $label_step=1; // Used by a text axis to specify what multiple of major steps
  3080. // should be labeled.
  3081. protected $labelPos=0; // Which side of the axis should the labels be?
  3082. protected $title_adjust,$title_margin,$title_side=SIDE_LEFT;
  3083. protected $tick_label_margin=5;
  3084. protected $label_halign = '',$label_valign = '', $label_para_align='left';
  3085. protected $hide_line=false;
  3086. protected $iDeltaAbsPos=0;
  3087. function __construct($img,$aScale,$color = array(0,0,0)) {
  3088. $this->img = $img;
  3089. $this->scale = $aScale;
  3090. $this->color = $color;
  3091. $this->title=new Text('');
  3092. if( $aScale->type == 'y' ) {
  3093. $this->title_margin = 25;
  3094. $this->title_adjust = 'middle';
  3095. $this->title->SetOrientation(90);
  3096. $this->tick_label_margin=7;
  3097. $this->labelPos=SIDE_LEFT;
  3098. }
  3099. else {
  3100. $this->title_margin = 5;
  3101. $this->title_adjust = 'high';
  3102. $this->title->SetOrientation(0);
  3103. $this->tick_label_margin=5;
  3104. $this->labelPos=SIDE_DOWN;
  3105. $this->title_side=SIDE_DOWN;
  3106. }
  3107. }
  3108. function SetLabelFormat($aFormStr) {
  3109. $this->scale->ticks->SetLabelFormat($aFormStr);
  3110. }
  3111. function SetLabelFormatString($aFormStr,$aDate=false) {
  3112. $this->scale->ticks->SetLabelFormat($aFormStr,$aDate);
  3113. }
  3114. function SetLabelFormatCallback($aFuncName) {
  3115. $this->scale->ticks->SetFormatCallback($aFuncName);
  3116. }
  3117. function SetLabelAlign($aHAlign,$aVAlign='top',$aParagraphAlign='left') {
  3118. $this->label_halign = $aHAlign;
  3119. $this->label_valign = $aVAlign;
  3120. $this->label_para_align = $aParagraphAlign;
  3121. }
  3122. // Don't display the first label
  3123. function HideFirstTickLabel($aShow=false) {
  3124. $this->show_first_label=$aShow;
  3125. }
  3126. function HideLastTickLabel($aShow=false) {
  3127. $this->show_last_label=$aShow;
  3128. }
  3129. // Manually specify the major and (optional) minor tick position and labels
  3130. function SetTickPositions($aMajPos,$aMinPos=NULL,$aLabels=NULL) {
  3131. $this->scale->ticks->SetTickPositions($aMajPos,$aMinPos,$aLabels);
  3132. }
  3133. // Manually specify major tick positions and optional labels
  3134. function SetMajTickPositions($aMajPos,$aLabels=NULL) {
  3135. $this->scale->ticks->SetTickPositions($aMajPos,NULL,$aLabels);
  3136. }
  3137. // Hide minor or major tick marks
  3138. function HideTicks($aHideMinor=true,$aHideMajor=true) {
  3139. $this->scale->ticks->SupressMinorTickMarks($aHideMinor);
  3140. $this->scale->ticks->SupressTickMarks($aHideMajor);
  3141. }
  3142. // Hide zero label
  3143. function HideZeroLabel($aFlag=true) {
  3144. $this->scale->ticks->SupressZeroLabel();
  3145. }
  3146. function HideFirstLastLabel() {
  3147. // The two first calls to ticks method will supress
  3148. // automatically generated scale values. However, that
  3149. // will not affect manually specified value, e.g text-scales.
  3150. // therefor we also make a kludge here to supress manually
  3151. // specified scale labels.
  3152. $this->scale->ticks->SupressLast();
  3153. $this->scale->ticks->SupressFirst();
  3154. $this->show_first_label = false;
  3155. $this->show_last_label = false;
  3156. }
  3157. // Hide the axis
  3158. function Hide($aHide=true) {
  3159. $this->hide=$aHide;
  3160. }
  3161. // Hide the actual axis-line, but still print the labels
  3162. function HideLine($aHide=true) {
  3163. $this->hide_line = $aHide;
  3164. }
  3165. function HideLabels($aHide=true) {
  3166. $this->hide_labels = $aHide;
  3167. }
  3168. // Weight of axis
  3169. function SetWeight($aWeight) {
  3170. $this->weight = $aWeight;
  3171. }
  3172. // Axis color
  3173. function SetColor($aColor,$aLabelColor=false) {
  3174. $this->color = $aColor;
  3175. if( !$aLabelColor ) $this->label_color = $aColor;
  3176. else $this->label_color = $aLabelColor;
  3177. }
  3178. // Title on axis
  3179. function SetTitle($aTitle,$aAdjustAlign='high') {
  3180. $this->title->Set($aTitle);
  3181. $this->title_adjust=$aAdjustAlign;
  3182. }
  3183. // Specify distance from the axis
  3184. function SetTitleMargin($aMargin) {
  3185. $this->title_margin=$aMargin;
  3186. }
  3187. // Which side of the axis should the axis title be?
  3188. function SetTitleSide($aSideOfAxis) {
  3189. $this->title_side = $aSideOfAxis;
  3190. }
  3191. function SetTickSide($aDir) {
  3192. $this->scale->ticks->SetSide($aDir);
  3193. }
  3194. function SetTickSize($aMajSize,$aMinSize=3) {
  3195. $this->scale->ticks->SetSize($aMajSize,$aMinSize=3);
  3196. }
  3197. // Specify text labels for the ticks. One label for each data point
  3198. function SetTickLabels($aLabelArray,$aLabelColorArray=null) {
  3199. $this->ticks_label = $aLabelArray;
  3200. $this->ticks_label_colors = $aLabelColorArray;
  3201. }
  3202. function SetLabelMargin($aMargin) {
  3203. $this->tick_label_margin=$aMargin;
  3204. }
  3205. // Specify that every $step of the ticks should be displayed starting
  3206. // at $start
  3207. function SetTextTickInterval($aStep,$aStart=0) {
  3208. $this->scale->ticks->SetTextLabelStart($aStart);
  3209. $this->tick_step=$aStep;
  3210. }
  3211. // Specify that every $step tick mark should have a label
  3212. // should be displayed starting
  3213. function SetTextLabelInterval($aStep) {
  3214. if( $aStep < 1 ) {
  3215. JpGraphError::RaiseL(25058);//(" Text label interval must be specified >= 1.");
  3216. }
  3217. $this->label_step=$aStep;
  3218. }
  3219. function SetLabelSide($aSidePos) {
  3220. $this->labelPos=$aSidePos;
  3221. }
  3222. // Set the font
  3223. function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
  3224. $this->font_family = $aFamily;
  3225. $this->font_style = $aStyle;
  3226. $this->font_size = $aSize;
  3227. }
  3228. // Position for axis line on the "other" scale
  3229. function SetPos($aPosOnOtherScale) {
  3230. $this->pos=$aPosOnOtherScale;
  3231. }
  3232. // Set the position of the axis to be X-pixels delta to the right
  3233. // of the max X-position (used to position the multiple Y-axis)
  3234. function SetPosAbsDelta($aDelta) {
  3235. $this->iDeltaAbsPos=$aDelta;
  3236. }
  3237. // Specify the angle for the tick labels
  3238. function SetLabelAngle($aAngle) {
  3239. $this->label_angle = $aAngle;
  3240. }
  3241. } // Class
  3242. //===================================================
  3243. // CLASS Axis
  3244. // Description: Defines X and Y axis. Notes that at the
  3245. // moment the code is not really good since the axis on
  3246. // several occasion must know wheter it's an X or Y axis.
  3247. // This was a design decision to make the code easier to
  3248. // follow.
  3249. //===================================================
  3250. class Axis extends AxisPrototype {
  3251. function __construct($img,$aScale,$color='black') {
  3252. parent::__construct($img,$aScale,$color);
  3253. }
  3254. // Stroke the axis.
  3255. function Stroke($aOtherAxisScale,$aStrokeLabels=true) {
  3256. if( $this->hide )
  3257. return;
  3258. if( is_numeric($this->pos) ) {
  3259. $pos=$aOtherAxisScale->Translate($this->pos);
  3260. }
  3261. else { // Default to minimum of other scale if pos not set
  3262. if( ($aOtherAxisScale->GetMinVal() >= 0 && $this->pos==false) || $this->pos == 'min' ) {
  3263. $pos = $aOtherAxisScale->scale_abs[0];
  3264. }
  3265. elseif($this->pos == "max") {
  3266. $pos = $aOtherAxisScale->scale_abs[1];
  3267. }
  3268. else { // If negative set x-axis at 0
  3269. $this->pos=0;
  3270. $pos=$aOtherAxisScale->Translate(0);
  3271. }
  3272. }
  3273. $pos += $this->iDeltaAbsPos;
  3274. $this->img->SetLineWeight($this->weight);
  3275. $this->img->SetColor($this->color);
  3276. $this->img->SetFont($this->font_family,$this->font_style,$this->font_size);
  3277. if( $this->scale->type == "x" ) {
  3278. if( !$this->hide_line ) {
  3279. $this->img->FilledRectangle($this->img->left_margin,$pos,$this->img->width-$this->img->right_margin,$pos+$this->weight-1);
  3280. }
  3281. if( $this->title_side == SIDE_DOWN ) {
  3282. $y = $pos + $this->img->GetFontHeight() + $this->title_margin + $this->title->margin;
  3283. $yalign = 'top';
  3284. }
  3285. else {
  3286. $y = $pos - $this->img->GetFontHeight() - $this->title_margin - $this->title->margin;
  3287. $yalign = 'bottom';
  3288. }
  3289. if( $this->title_adjust=='high' ) {
  3290. $this->title->SetPos($this->img->width-$this->img->right_margin,$y,'right',$yalign);
  3291. }
  3292. elseif( $this->title_adjust=='middle' || $this->title_adjust=='center' ) {
  3293. $this->title->SetPos(($this->img->width-$this->img->left_margin-$this->img->right_margin)/2+$this->img->left_margin,$y,'center',$yalign);
  3294. }
  3295. elseif($this->title_adjust=='low') {
  3296. $this->title->SetPos($this->img->left_margin,$y,'left',$yalign);
  3297. }
  3298. else {
  3299. JpGraphError::RaiseL(25060,$this->title_adjust);//('Unknown alignment specified for X-axis title. ('.$this->title_adjust.')');
  3300. }
  3301. }
  3302. elseif( $this->scale->type == "y" ) {
  3303. // Add line weight to the height of the axis since
  3304. // the x-axis could have a width>1 and we want the axis to fit nicely together.
  3305. if( !$this->hide_line ) {
  3306. $this->img->FilledRectangle($pos-$this->weight+1,$this->img->top_margin,$pos,$this->img->height-$this->img->bottom_margin+$this->weight-1);
  3307. }
  3308. $x=$pos ;
  3309. if( $this->title_side == SIDE_LEFT ) {
  3310. $x -= $this->title_margin;
  3311. $x -= $this->title->margin;
  3312. $halign = 'right';
  3313. }
  3314. else {
  3315. $x += $this->title_margin;
  3316. $x += $this->title->margin;
  3317. $halign = 'left';
  3318. }
  3319. // If the user has manually specified an hor. align
  3320. // then we override the automatic settings with this
  3321. // specifed setting. Since default is 'left' we compare
  3322. // with that. (This means a manually set 'left' align
  3323. // will have no effect.)
  3324. if( $this->title->halign != 'left' ) {
  3325. $halign = $this->title->halign;
  3326. }
  3327. if( $this->title_adjust == 'high' ) {
  3328. $this->title->SetPos($x,$this->img->top_margin,$halign,'top');
  3329. }
  3330. elseif($this->title_adjust=='middle' || $this->title_adjust=='center') {
  3331. $this->title->SetPos($x,($this->img->height-$this->img->top_margin-$this->img->bottom_margin)/2+$this->img->top_margin,$halign,"center");
  3332. }
  3333. elseif($this->title_adjust=='low') {
  3334. $this->title->SetPos($x,$this->img->height-$this->img->bottom_margin,$halign,'bottom');
  3335. }
  3336. else {
  3337. JpGraphError::RaiseL(25061,$this->title_adjust);//('Unknown alignment specified for Y-axis title. ('.$this->title_adjust.')');
  3338. }
  3339. }
  3340. $this->scale->ticks->Stroke($this->img,$this->scale,$pos);
  3341. if( $aStrokeLabels ) {
  3342. if( !$this->hide_labels ) {
  3343. $this->StrokeLabels($pos);
  3344. }
  3345. $this->title->Stroke($this->img);
  3346. }
  3347. }
  3348. //---------------
  3349. // PRIVATE METHODS
  3350. // Draw all the tick labels on major tick marks
  3351. function StrokeLabels($aPos,$aMinor=false,$aAbsLabel=false) {
  3352. if( is_array($this->label_color) && count($this->label_color) > 3 ) {
  3353. $this->ticks_label_colors = $this->label_color;
  3354. $this->img->SetColor($this->label_color[0]);
  3355. }
  3356. else {
  3357. $this->img->SetColor($this->label_color);
  3358. }
  3359. $this->img->SetFont($this->font_family,$this->font_style,$this->font_size);
  3360. $yoff=$this->img->GetFontHeight()/2;
  3361. // Only draw labels at major tick marks
  3362. $nbr = count($this->scale->ticks->maj_ticks_label);
  3363. // We have the option to not-display the very first mark
  3364. // (Usefull when the first label might interfere with another
  3365. // axis.)
  3366. $i = $this->show_first_label ? 0 : 1 ;
  3367. if( !$this->show_last_label ) {
  3368. --$nbr;
  3369. }
  3370. // Now run through all labels making sure we don't overshoot the end
  3371. // of the scale.
  3372. $ncolor=0;
  3373. if( isset($this->ticks_label_colors) ) {
  3374. $ncolor=count($this->ticks_label_colors);
  3375. }
  3376. while( $i < $nbr ) {
  3377. // $tpos holds the absolute text position for the label
  3378. $tpos=$this->scale->ticks->maj_ticklabels_pos[$i];
  3379. // Note. the $limit is only used for the x axis since we
  3380. // might otherwise overshoot if the scale has been centered
  3381. // This is due to us "loosing" the last tick mark if we center.
  3382. if( $this->scale->type == 'x' && $tpos > $this->img->width-$this->img->right_margin+1 ) {
  3383. return;
  3384. }
  3385. // we only draw every $label_step label
  3386. if( ($i % $this->label_step)==0 ) {
  3387. // Set specific label color if specified
  3388. if( $ncolor > 0 ) {
  3389. $this->img->SetColor($this->ticks_label_colors[$i % $ncolor]);
  3390. }
  3391. // If the label has been specified use that and in other case
  3392. // just label the mark with the actual scale value
  3393. $m=$this->scale->ticks->GetMajor();
  3394. // ticks_label has an entry for each data point and is the array
  3395. // that holds the labels set by the user. If the user hasn't
  3396. // specified any values we use whats in the automatically asigned
  3397. // labels in the maj_ticks_label
  3398. if( isset($this->ticks_label[$i*$m]) ) {
  3399. $label=$this->ticks_label[$i*$m];
  3400. }
  3401. else {
  3402. if( $aAbsLabel ) {
  3403. $label=abs($this->scale->ticks->maj_ticks_label[$i]);
  3404. }
  3405. else {
  3406. $label=$this->scale->ticks->maj_ticks_label[$i];
  3407. }
  3408. // We number the scale from 1 and not from 0 so increase by one
  3409. if( $this->scale->textscale &&
  3410. $this->scale->ticks->label_formfunc == '' &&
  3411. ! $this->scale->ticks->HaveManualLabels() ) {
  3412. ++$label;
  3413. }
  3414. }
  3415. if( $this->scale->type == "x" ) {
  3416. if( $this->labelPos == SIDE_DOWN ) {
  3417. if( $this->label_angle==0 || $this->label_angle==90 ) {
  3418. if( $this->label_halign=='' && $this->label_valign=='') {
  3419. $this->img->SetTextAlign('center','top');
  3420. }
  3421. else {
  3422. $this->img->SetTextAlign($this->label_halign,$this->label_valign);
  3423. }
  3424. }
  3425. else {
  3426. if( $this->label_halign=='' && $this->label_valign=='') {
  3427. $this->img->SetTextAlign("right","top");
  3428. }
  3429. else {
  3430. $this->img->SetTextAlign($this->label_halign,$this->label_valign);
  3431. }
  3432. }
  3433. $this->img->StrokeText($tpos,$aPos+$this->tick_label_margin,$label,
  3434. $this->label_angle,$this->label_para_align);
  3435. }
  3436. else {
  3437. if( $this->label_angle==0 || $this->label_angle==90 ) {
  3438. if( $this->label_halign=='' && $this->label_valign=='') {
  3439. $this->img->SetTextAlign("center","bottom");
  3440. }
  3441. else {
  3442. $this->img->SetTextAlign($this->label_halign,$this->label_valign);
  3443. }
  3444. }
  3445. else {
  3446. if( $this->label_halign=='' && $this->label_valign=='') {
  3447. $this->img->SetTextAlign("right","bottom");
  3448. }
  3449. else {
  3450. $this->img->SetTextAlign($this->label_halign,$this->label_valign);
  3451. }
  3452. }
  3453. $this->img->StrokeText($tpos,$aPos-$this->tick_label_margin-1,$label,
  3454. $this->label_angle,$this->label_para_align);
  3455. }
  3456. }
  3457. else {
  3458. // scale->type == "y"
  3459. //if( $this->label_angle!=0 )
  3460. //JpGraphError::Raise(" Labels at an angle are not supported on Y-axis");
  3461. if( $this->labelPos == SIDE_LEFT ) { // To the left of y-axis
  3462. if( $this->label_halign=='' && $this->label_valign=='') {
  3463. $this->img->SetTextAlign("right","center");
  3464. }
  3465. else {
  3466. $this->img->SetTextAlign($this->label_halign,$this->label_valign);
  3467. }
  3468. $this->img->StrokeText($aPos-$this->tick_label_margin,$tpos,$label,$this->label_angle,$this->label_para_align);
  3469. }
  3470. else { // To the right of the y-axis
  3471. if( $this->label_halign=='' && $this->label_valign=='') {
  3472. $this->img->SetTextAlign("left","center");
  3473. }
  3474. else {
  3475. $this->img->SetTextAlign($this->label_halign,$this->label_valign);
  3476. }
  3477. $this->img->StrokeText($aPos+$this->tick_label_margin,$tpos,$label,$this->label_angle,$this->label_para_align);
  3478. }
  3479. }
  3480. }
  3481. ++$i;
  3482. }
  3483. }
  3484. }
  3485. //===================================================
  3486. // CLASS Ticks
  3487. // Description: Abstract base class for drawing linear and logarithmic
  3488. // tick marks on axis
  3489. //===================================================
  3490. class Ticks {
  3491. public $label_formatstr=''; // C-style format string to use for labels
  3492. public $label_formfunc='';
  3493. public $label_dateformatstr='';
  3494. public $direction=1; // Should ticks be in(=1) the plot area or outside (=-1)
  3495. public $supress_last=false,$supress_tickmarks=false,$supress_minor_tickmarks=false;
  3496. public $maj_ticks_pos = array(), $maj_ticklabels_pos = array(),
  3497. $ticks_pos = array(), $maj_ticks_label = array();
  3498. public $precision;
  3499. protected $minor_abs_size=3, $major_abs_size=5;
  3500. protected $scale;
  3501. protected $is_set=false;
  3502. protected $supress_zerolabel=false,$supress_first=false;
  3503. protected $mincolor='',$majcolor='';
  3504. protected $weight=1;
  3505. protected $label_usedateformat=FALSE;
  3506. function __construct($aScale) {
  3507. $this->scale=$aScale;
  3508. $this->precision = -1;
  3509. }
  3510. // Set format string for automatic labels
  3511. function SetLabelFormat($aFormatString,$aDate=FALSE) {
  3512. $this->label_formatstr=$aFormatString;
  3513. $this->label_usedateformat=$aDate;
  3514. }
  3515. function SetLabelDateFormat($aFormatString) {
  3516. $this->label_dateformatstr=$aFormatString;
  3517. }
  3518. function SetFormatCallback($aCallbackFuncName) {
  3519. $this->label_formfunc = $aCallbackFuncName;
  3520. }
  3521. // Don't display the first zero label
  3522. function SupressZeroLabel($aFlag=true) {
  3523. $this->supress_zerolabel=$aFlag;
  3524. }
  3525. // Don't display minor tick marks
  3526. function SupressMinorTickMarks($aHide=true) {
  3527. $this->supress_minor_tickmarks=$aHide;
  3528. }
  3529. // Don't display major tick marks
  3530. function SupressTickMarks($aHide=true) {
  3531. $this->supress_tickmarks=$aHide;
  3532. }
  3533. // Hide the first tick mark
  3534. function SupressFirst($aHide=true) {
  3535. $this->supress_first=$aHide;
  3536. }
  3537. // Hide the last tick mark
  3538. function SupressLast($aHide=true) {
  3539. $this->supress_last=$aHide;
  3540. }
  3541. // Size (in pixels) of minor tick marks
  3542. function GetMinTickAbsSize() {
  3543. return $this->minor_abs_size;
  3544. }
  3545. // Size (in pixels) of major tick marks
  3546. function GetMajTickAbsSize() {
  3547. return $this->major_abs_size;
  3548. }
  3549. function SetSize($aMajSize,$aMinSize=3) {
  3550. $this->major_abs_size = $aMajSize;
  3551. $this->minor_abs_size = $aMinSize;
  3552. }
  3553. // Have the ticks been specified
  3554. function IsSpecified() {
  3555. return $this->is_set;
  3556. }
  3557. function SetSide($aSide) {
  3558. $this->direction=$aSide;
  3559. }
  3560. // Which side of the axis should the ticks be on
  3561. function SetDirection($aSide=SIDE_RIGHT) {
  3562. $this->direction=$aSide;
  3563. }
  3564. // Set colors for major and minor tick marks
  3565. function SetMarkColor($aMajorColor,$aMinorColor='') {
  3566. $this->SetColor($aMajorColor,$aMinorColor);
  3567. }
  3568. function SetColor($aMajorColor,$aMinorColor='') {
  3569. $this->majcolor=$aMajorColor;
  3570. // If not specified use same as major
  3571. if( $aMinorColor == '' ) {
  3572. $this->mincolor=$aMajorColor;
  3573. }
  3574. else {
  3575. $this->mincolor=$aMinorColor;
  3576. }
  3577. }
  3578. function SetWeight($aWeight) {
  3579. $this->weight=$aWeight;
  3580. }
  3581. } // Class
  3582. //===================================================
  3583. // CLASS LinearTicks
  3584. // Description: Draw linear ticks on axis
  3585. //===================================================
  3586. class LinearTicks extends Ticks {
  3587. public $minor_step=1, $major_step=2;
  3588. public $xlabel_offset=0,$xtick_offset=0;
  3589. private $label_offset=0; // What offset should the displayed label have
  3590. // i.e should we display 0,1,2 or 1,2,3,4 or 2,3,4 etc
  3591. private $text_label_start=0;
  3592. private $iManualTickPos = NULL, $iManualMinTickPos = NULL, $iManualTickLabels = NULL;
  3593. private $iAdjustForDST = false; // If a date falls within the DST period add one hour to the diaplyed time
  3594. function __construct() {
  3595. $this->precision = -1;
  3596. }
  3597. // Return major step size in world coordinates
  3598. function GetMajor() {
  3599. return $this->major_step;
  3600. }
  3601. // Return minor step size in world coordinates
  3602. function GetMinor() {
  3603. return $this->minor_step;
  3604. }
  3605. // Set Minor and Major ticks (in world coordinates)
  3606. function Set($aMajStep,$aMinStep=false) {
  3607. if( $aMinStep==false ) {
  3608. $aMinStep=$aMajStep;
  3609. }
  3610. if( $aMajStep <= 0 || $aMinStep <= 0 ) {
  3611. JpGraphError::RaiseL(25064);
  3612. //(" Minor or major step size is 0. Check that you haven't got an accidental SetTextTicks(0) in your code. If this is not the case you might have stumbled upon a bug in JpGraph. Please report this and if possible include the data that caused the problem.");
  3613. }
  3614. $this->major_step=$aMajStep;
  3615. $this->minor_step=$aMinStep;
  3616. $this->is_set = true;
  3617. }
  3618. function SetMajTickPositions($aMajPos,$aLabels=NULL) {
  3619. $this->SetTickPositions($aMajPos,NULL,$aLabels);
  3620. }
  3621. function SetTickPositions($aMajPos,$aMinPos=NULL,$aLabels=NULL) {
  3622. if( !is_array($aMajPos) || ($aMinPos!==NULL && !is_array($aMinPos)) ) {
  3623. JpGraphError::RaiseL(25065);//('Tick positions must be specifued as an array()');
  3624. return;
  3625. }
  3626. $n=count($aMajPos);
  3627. if( is_array($aLabels) && (count($aLabels) != $n) ) {
  3628. JpGraphError::RaiseL(25066);//('When manually specifying tick positions and labels the number of labels must be the same as the number of specified ticks.');
  3629. }
  3630. $this->iManualTickPos = $aMajPos;
  3631. $this->iManualMinTickPos = $aMinPos;
  3632. $this->iManualTickLabels = $aLabels;
  3633. }
  3634. function HaveManualLabels() {
  3635. return count($this->iManualTickLabels) > 0;
  3636. }
  3637. // Specify all the tick positions manually and possible also the exact labels
  3638. function _doManualTickPos($aScale) {
  3639. $n=count($this->iManualTickPos);
  3640. $m=count($this->iManualMinTickPos);
  3641. $doLbl=count($this->iManualTickLabels) > 0;
  3642. $this->maj_ticks_pos = array();
  3643. $this->maj_ticklabels_pos = array();
  3644. $this->ticks_pos = array();
  3645. // Now loop through the supplied positions and translate them to screen coordinates
  3646. // and store them in the maj_label_positions
  3647. $minScale = $aScale->scale[0];
  3648. $maxScale = $aScale->scale[1];
  3649. $j=0;
  3650. for($i=0; $i < $n ; ++$i ) {
  3651. // First make sure that the first tick is not lower than the lower scale value
  3652. if( !isset($this->iManualTickPos[$i]) || $this->iManualTickPos[$i] < $minScale || $this->iManualTickPos[$i] > $maxScale) {
  3653. continue;
  3654. }
  3655. $this->maj_ticks_pos[$j] = $aScale->Translate($this->iManualTickPos[$i]);
  3656. $this->maj_ticklabels_pos[$j] = $this->maj_ticks_pos[$j];
  3657. // Set the minor tick marks the same as major if not specified
  3658. if( $m <= 0 ) {
  3659. $this->ticks_pos[$j] = $this->maj_ticks_pos[$j];
  3660. }
  3661. if( $doLbl ) {
  3662. $this->maj_ticks_label[$j] = $this->iManualTickLabels[$i];
  3663. }
  3664. else {
  3665. $this->maj_ticks_label[$j]=$this->_doLabelFormat($this->iManualTickPos[$i],$i,$n);
  3666. }
  3667. ++$j;
  3668. }
  3669. // Some sanity check
  3670. if( count($this->maj_ticks_pos) < 2 ) {
  3671. JpGraphError::RaiseL(25067);//('Your manually specified scale and ticks is not correct. The scale seems to be too small to hold any of the specified tickl marks.');
  3672. }
  3673. // Setup the minor tick marks
  3674. $j=0;
  3675. for($i=0; $i < $m; ++$i ) {
  3676. if( empty($this->iManualMinTickPos[$i]) || $this->iManualMinTickPos[$i] < $minScale || $this->iManualMinTickPos[$i] > $maxScale) {
  3677. continue;
  3678. }
  3679. $this->ticks_pos[$j] = $aScale->Translate($this->iManualMinTickPos[$i]);
  3680. ++$j;
  3681. }
  3682. }
  3683. function _doAutoTickPos($aScale) {
  3684. $maj_step_abs = $aScale->scale_factor*$this->major_step;
  3685. $min_step_abs = $aScale->scale_factor*$this->minor_step;
  3686. if( $min_step_abs==0 || $maj_step_abs==0 ) {
  3687. JpGraphError::RaiseL(25068);//("A plot has an illegal scale. This could for example be that you are trying to use text autoscaling to draw a line plot with only one point or that the plot area is too small. It could also be that no input data value is numeric (perhaps only '-' or 'x')");
  3688. }
  3689. // We need to make this an int since comparing it below
  3690. // with the result from round() can give wrong result, such that
  3691. // (40 < 40) == TRUE !!!
  3692. $limit = (int)$aScale->scale_abs[1];
  3693. if( $aScale->textscale ) {
  3694. // This can only be true for a X-scale (horizontal)
  3695. // Define ticks for a text scale. This is slightly different from a
  3696. // normal linear type of scale since the position might be adjusted
  3697. // and the labels start at on
  3698. $label = (float)$aScale->GetMinVal()+$this->text_label_start+$this->label_offset;
  3699. $start_abs=$aScale->scale_factor*$this->text_label_start;
  3700. $nbrmajticks=round(($aScale->GetMaxVal()-$aScale->GetMinVal()-$this->text_label_start )/$this->major_step)+1;
  3701. $x = $aScale->scale_abs[0]+$start_abs+$this->xlabel_offset*$min_step_abs;
  3702. for( $i=0; $label <= $aScale->GetMaxVal()+$this->label_offset; ++$i ) {
  3703. // Apply format to label
  3704. $this->maj_ticks_label[$i]=$this->_doLabelFormat($label,$i,$nbrmajticks);
  3705. $label+=$this->major_step;
  3706. // The x-position of the tick marks can be different from the labels.
  3707. // Note that we record the tick position (not the label) so that the grid
  3708. // happen upon tick marks and not labels.
  3709. $xtick=$aScale->scale_abs[0]+$start_abs+$this->xtick_offset*$min_step_abs+$i*$maj_step_abs;
  3710. $this->maj_ticks_pos[$i]=$xtick;
  3711. $this->maj_ticklabels_pos[$i] = round($x);
  3712. $x += $maj_step_abs;
  3713. }
  3714. }
  3715. else {
  3716. $label = $aScale->GetMinVal();
  3717. $abs_pos = $aScale->scale_abs[0];
  3718. $j=0; $i=0;
  3719. $step = round($maj_step_abs/$min_step_abs);
  3720. if( $aScale->type == "x" ) {
  3721. // For a normal linear type of scale the major ticks will always be multiples
  3722. // of the minor ticks. In order to avoid any rounding issues the major ticks are
  3723. // defined as every "step" minor ticks and not calculated separately
  3724. $nbrmajticks=round(($aScale->GetMaxVal()-$aScale->GetMinVal()-$this->text_label_start )/$this->major_step)+1;
  3725. while( round($abs_pos) <= $limit ) {
  3726. $this->ticks_pos[] = round($abs_pos);
  3727. $this->ticks_label[] = $label;
  3728. if( $step== 0 || $i % $step == 0 && $j < $nbrmajticks ) {
  3729. $this->maj_ticks_pos[$j] = round($abs_pos);
  3730. $this->maj_ticklabels_pos[$j] = round($abs_pos);
  3731. $this->maj_ticks_label[$j]=$this->_doLabelFormat($label,$j,$nbrmajticks);
  3732. ++$j;
  3733. }
  3734. ++$i;
  3735. $abs_pos += $min_step_abs;
  3736. $label+=$this->minor_step;
  3737. }
  3738. }
  3739. elseif( $aScale->type == "y" ) {
  3740. $nbrmajticks=round(($aScale->GetMaxVal()-$aScale->GetMinVal())/$this->major_step)+1;
  3741. while( round($abs_pos) >= $limit ) {
  3742. $this->ticks_pos[$i] = round($abs_pos);
  3743. $this->ticks_label[$i]=$label;
  3744. if( $step== 0 || $i % $step == 0 && $j < $nbrmajticks) {
  3745. $this->maj_ticks_pos[$j] = round($abs_pos);
  3746. $this->maj_ticklabels_pos[$j] = round($abs_pos);
  3747. $this->maj_ticks_label[$j]=$this->_doLabelFormat($label,$j,$nbrmajticks);
  3748. ++$j;
  3749. }
  3750. ++$i;
  3751. $abs_pos += $min_step_abs;
  3752. $label += $this->minor_step;
  3753. }
  3754. }
  3755. }
  3756. }
  3757. function AdjustForDST($aFlg=true) {
  3758. $this->iAdjustForDST = $aFlg;
  3759. }
  3760. function _doLabelFormat($aVal,$aIdx,$aNbrTicks) {
  3761. // If precision hasn't been specified set it to a sensible value
  3762. if( $this->precision==-1 ) {
  3763. $t = log10($this->minor_step);
  3764. if( $t > 0 ) {
  3765. $precision = 0;
  3766. }
  3767. else {
  3768. $precision = -floor($t);
  3769. }
  3770. }
  3771. else {
  3772. $precision = $this->precision;
  3773. }
  3774. if( $this->label_formfunc != '' ) {
  3775. $f=$this->label_formfunc;
  3776. if( $this->label_formatstr == '' ) {
  3777. $l = call_user_func($f,$aVal);
  3778. }
  3779. else {
  3780. $l = sprintf($this->label_formatstr, call_user_func($f,$aVal));
  3781. }
  3782. }
  3783. elseif( $this->label_formatstr != '' || $this->label_dateformatstr != '' ) {
  3784. if( $this->label_usedateformat ) {
  3785. // Adjust the value to take daylight savings into account
  3786. if (date("I",$aVal)==1 && $this->iAdjustForDST ) {
  3787. // DST
  3788. $aVal+=3600;
  3789. }
  3790. $l = date($this->label_formatstr,$aVal);
  3791. if( $this->label_formatstr == 'W' ) {
  3792. // If we use week formatting then add a single 'w' in front of the
  3793. // week number to differentiate it from dates
  3794. $l = 'w'.$l;
  3795. }
  3796. }
  3797. else {
  3798. if( $this->label_dateformatstr !== '' ) {
  3799. // Adjust the value to take daylight savings into account
  3800. if (date("I",$aVal)==1 && $this->iAdjustForDST ) {
  3801. // DST
  3802. $aVal+=3600;
  3803. }
  3804. $l = date($this->label_dateformatstr,$aVal);
  3805. if( $this->label_formatstr == 'W' ) {
  3806. // If we use week formatting then add a single 'w' in front of the
  3807. // week number to differentiate it from dates
  3808. $l = 'w'.$l;
  3809. }
  3810. }
  3811. else {
  3812. $l = sprintf($this->label_formatstr,$aVal);
  3813. }
  3814. }
  3815. }
  3816. else {
  3817. $l = sprintf('%01.'.$precision.'f',round($aVal,$precision));
  3818. }
  3819. if( ($this->supress_zerolabel && $l==0) || ($this->supress_first && $aIdx==0) || ($this->supress_last && $aIdx==$aNbrTicks-1) ) {
  3820. $l='';
  3821. }
  3822. return $l;
  3823. }
  3824. // Stroke ticks on either X or Y axis
  3825. function _StrokeTicks($aImg,$aScale,$aPos) {
  3826. $hor = $aScale->type == 'x';
  3827. $aImg->SetLineWeight($this->weight);
  3828. // We need to make this an int since comparing it below
  3829. // with the result from round() can give wrong result, such that
  3830. // (40 < 40) == TRUE !!!
  3831. $limit = (int)$aScale->scale_abs[1];
  3832. // A text scale doesn't have any minor ticks
  3833. if( !$aScale->textscale ) {
  3834. // Stroke minor ticks
  3835. $yu = $aPos - $this->direction*$this->GetMinTickAbsSize();
  3836. $xr = $aPos + $this->direction*$this->GetMinTickAbsSize();
  3837. $n = count($this->ticks_pos);
  3838. for($i=0; $i < $n; ++$i ) {
  3839. if( !$this->supress_tickmarks && !$this->supress_minor_tickmarks) {
  3840. if( $this->mincolor != '') {
  3841. $aImg->PushColor($this->mincolor);
  3842. }
  3843. if( $hor ) {
  3844. //if( $this->ticks_pos[$i] <= $limit )
  3845. $aImg->Line($this->ticks_pos[$i],$aPos,$this->ticks_pos[$i],$yu);
  3846. }
  3847. else {
  3848. //if( $this->ticks_pos[$i] >= $limit )
  3849. $aImg->Line($aPos,$this->ticks_pos[$i],$xr,$this->ticks_pos[$i]);
  3850. }
  3851. if( $this->mincolor != '' ) {
  3852. $aImg->PopColor();
  3853. }
  3854. }
  3855. }
  3856. }
  3857. // Stroke major ticks
  3858. $yu = $aPos - $this->direction*$this->GetMajTickAbsSize();
  3859. $xr = $aPos + $this->direction*$this->GetMajTickAbsSize();
  3860. $nbrmajticks=round(($aScale->GetMaxVal()-$aScale->GetMinVal()-$this->text_label_start )/$this->major_step)+1;
  3861. $n = count($this->maj_ticks_pos);
  3862. for($i=0; $i < $n ; ++$i ) {
  3863. if(!($this->xtick_offset > 0 && $i==$nbrmajticks-1) && !$this->supress_tickmarks) {
  3864. if( $this->majcolor != '') {
  3865. $aImg->PushColor($this->majcolor);
  3866. }
  3867. if( $hor ) {
  3868. //if( $this->maj_ticks_pos[$i] <= $limit )
  3869. $aImg->Line($this->maj_ticks_pos[$i],$aPos,$this->maj_ticks_pos[$i],$yu);
  3870. }
  3871. else {
  3872. //if( $this->maj_ticks_pos[$i] >= $limit )
  3873. $aImg->Line($aPos,$this->maj_ticks_pos[$i],$xr,$this->maj_ticks_pos[$i]);
  3874. }
  3875. if( $this->majcolor != '') {
  3876. $aImg->PopColor();
  3877. }
  3878. }
  3879. }
  3880. }
  3881. // Draw linear ticks
  3882. function Stroke($aImg,$aScale,$aPos) {
  3883. if( $this->iManualTickPos != NULL ) {
  3884. $this->_doManualTickPos($aScale);
  3885. }
  3886. else {
  3887. $this->_doAutoTickPos($aScale);
  3888. }
  3889. $this->_StrokeTicks($aImg,$aScale,$aPos, $aScale->type == 'x' );
  3890. }
  3891. //---------------
  3892. // PRIVATE METHODS
  3893. // Spoecify the offset of the displayed tick mark with the tick "space"
  3894. // Legal values for $o is [0,1] used to adjust where the tick marks and label
  3895. // should be positioned within the major tick-size
  3896. // $lo specifies the label offset and $to specifies the tick offset
  3897. // this comes in handy for example in bar graphs where we wont no offset for the
  3898. // tick but have the labels displayed halfway under the bars.
  3899. function SetXLabelOffset($aLabelOff,$aTickOff=-1) {
  3900. $this->xlabel_offset=$aLabelOff;
  3901. if( $aTickOff==-1 ) {
  3902. // Same as label offset
  3903. $this->xtick_offset=$aLabelOff;
  3904. }
  3905. else {
  3906. $this->xtick_offset=$aTickOff;
  3907. }
  3908. if( $aLabelOff>0 ) {
  3909. $this->SupressLast(); // The last tick wont fit
  3910. }
  3911. }
  3912. // Which tick label should we start with?
  3913. function SetTextLabelStart($aTextLabelOff) {
  3914. $this->text_label_start=$aTextLabelOff;
  3915. }
  3916. } // Class
  3917. //===================================================
  3918. // CLASS LinearScale
  3919. // Description: Handle linear scaling between screen and world
  3920. //===================================================
  3921. class LinearScale {
  3922. public $textscale=false; // Just a flag to let the Plot class find out if
  3923. // we are a textscale or not. This is a cludge since
  3924. // this information is available in Graph::axtype but
  3925. // we don't have access to the graph object in the Plots
  3926. // stroke method. So we let graph store the status here
  3927. // when the linear scale is created. A real cludge...
  3928. public $type; // is this x or y scale ?
  3929. public $ticks=null; // Store ticks
  3930. public $text_scale_off = 0;
  3931. public $scale_abs=array(0,0);
  3932. public $scale_factor; // Scale factor between world and screen
  3933. public $off; // Offset between image edge and plot area
  3934. public $scale=array(0,0);
  3935. public $name = 'lin';
  3936. public $auto_ticks=false; // When using manual scale should the ticks be automatically set?
  3937. public $world_abs_size; // Plot area size in pixels (Needed public in jpgraph_radar.php)
  3938. public $world_size; // Plot area size in world coordinates
  3939. public $intscale=false; // Restrict autoscale to integers
  3940. protected $autoscale_min=false; // Forced minimum value, auto determine max
  3941. protected $autoscale_max=false; // Forced maximum value, auto determine min
  3942. private $gracetop=0,$gracebottom=0;
  3943. function __construct($aMin=0,$aMax=0,$aType='y') {
  3944. assert($aType=='x' || $aType=='y' );
  3945. assert($aMin<=$aMax);
  3946. $this->type=$aType;
  3947. $this->scale=array($aMin,$aMax);
  3948. $this->world_size=$aMax-$aMin;
  3949. $this->ticks = new LinearTicks();
  3950. }
  3951. // Check if scale is set or if we should autoscale
  3952. // We should do this is either scale or ticks has not been set
  3953. function IsSpecified() {
  3954. if( $this->GetMinVal()==$this->GetMaxVal() ) { // Scale not set
  3955. return false;
  3956. }
  3957. return true;
  3958. }
  3959. // Set the minimum data value when the autoscaling is used.
  3960. // Usefull if you want a fix minimum (like 0) but have an
  3961. // automatic maximum
  3962. function SetAutoMin($aMin) {
  3963. $this->autoscale_min=$aMin;
  3964. }
  3965. // Set the minimum data value when the autoscaling is used.
  3966. // Usefull if you want a fix minimum (like 0) but have an
  3967. // automatic maximum
  3968. function SetAutoMax($aMax) {
  3969. $this->autoscale_max=$aMax;
  3970. }
  3971. // If the user manually specifies a scale should the ticks
  3972. // still be set automatically?
  3973. function SetAutoTicks($aFlag=true) {
  3974. $this->auto_ticks = $aFlag;
  3975. }
  3976. // Specify scale "grace" value (top and bottom)
  3977. function SetGrace($aGraceTop,$aGraceBottom=0) {
  3978. if( $aGraceTop<0 || $aGraceBottom < 0 ) {
  3979. JpGraphError::RaiseL(25069);//(" Grace must be larger then 0");
  3980. }
  3981. $this->gracetop=$aGraceTop;
  3982. $this->gracebottom=$aGraceBottom;
  3983. }
  3984. // Get the minimum value in the scale
  3985. function GetMinVal() {
  3986. return $this->scale[0];
  3987. }
  3988. // get maximum value for scale
  3989. function GetMaxVal() {
  3990. return $this->scale[1];
  3991. }
  3992. // Specify a new min/max value for sclae
  3993. function Update($aImg,$aMin,$aMax) {
  3994. $this->scale=array($aMin,$aMax);
  3995. $this->world_size=$aMax-$aMin;
  3996. $this->InitConstants($aImg);
  3997. }
  3998. // Translate between world and screen
  3999. function Translate($aCoord) {
  4000. if( !is_numeric($aCoord) ) {
  4001. if( $aCoord != '' && $aCoord != '-' && $aCoord != 'x' ) {
  4002. JpGraphError::RaiseL(25070);//('Your data contains non-numeric values.');
  4003. }
  4004. return 0;
  4005. }
  4006. else {
  4007. return round($this->off+($aCoord - $this->scale[0]) * $this->scale_factor);
  4008. }
  4009. }
  4010. // Relative translate (don't include offset) usefull when we just want
  4011. // to know the relative position (in pixels) on the axis
  4012. function RelTranslate($aCoord) {
  4013. if( !is_numeric($aCoord) ) {
  4014. if( $aCoord != '' && $aCoord != '-' && $aCoord != 'x' ) {
  4015. JpGraphError::RaiseL(25070);//('Your data contains non-numeric values.');
  4016. }
  4017. return 0;
  4018. }
  4019. else {
  4020. return ($aCoord - $this->scale[0]) * $this->scale_factor;
  4021. }
  4022. }
  4023. // Restrict autoscaling to only use integers
  4024. function SetIntScale($aIntScale=true) {
  4025. $this->intscale=$aIntScale;
  4026. }
  4027. // Calculate an integer autoscale
  4028. function IntAutoScale($img,$min,$max,$maxsteps,$majend=true) {
  4029. // Make sure limits are integers
  4030. $min=floor($min);
  4031. $max=ceil($max);
  4032. if( abs($min-$max)==0 ) {
  4033. --$min; ++$max;
  4034. }
  4035. $maxsteps = floor($maxsteps);
  4036. $gracetop=round(($this->gracetop/100.0)*abs($max-$min));
  4037. $gracebottom=round(($this->gracebottom/100.0)*abs($max-$min));
  4038. if( is_numeric($this->autoscale_min) ) {
  4039. $min = ceil($this->autoscale_min);
  4040. if( $min >= $max ) {
  4041. JpGraphError::RaiseL(25071);//('You have specified a min value with SetAutoMin() which is larger than the maximum value used for the scale. This is not possible.');
  4042. }
  4043. }
  4044. if( is_numeric($this->autoscale_max) ) {
  4045. $max = ceil($this->autoscale_max);
  4046. if( $min >= $max ) {
  4047. JpGraphError::RaiseL(25072);//('You have specified a max value with SetAutoMax() which is smaller than the miminum value used for the scale. This is not possible.');
  4048. }
  4049. }
  4050. if( abs($min-$max ) == 0 ) {
  4051. ++$max;
  4052. --$min;
  4053. }
  4054. $min -= $gracebottom;
  4055. $max += $gracetop;
  4056. // First get tickmarks as multiples of 1, 10, ...
  4057. if( $majend ) {
  4058. list($num1steps,$adj1min,$adj1max,$maj1step) = $this->IntCalcTicks($maxsteps,$min,$max,1);
  4059. }
  4060. else {
  4061. $adj1min = $min;
  4062. $adj1max = $max;
  4063. list($num1steps,$maj1step) = $this->IntCalcTicksFreeze($maxsteps,$min,$max,1);
  4064. }
  4065. if( abs($min-$max) > 2 ) {
  4066. // Then get tick marks as 2:s 2, 20, ...
  4067. if( $majend ) {
  4068. list($num2steps,$adj2min,$adj2max,$maj2step) = $this->IntCalcTicks($maxsteps,$min,$max,5);
  4069. }
  4070. else {
  4071. $adj2min = $min;
  4072. $adj2max = $max;
  4073. list($num2steps,$maj2step) = $this->IntCalcTicksFreeze($maxsteps,$min,$max,5);
  4074. }
  4075. }
  4076. else {
  4077. $num2steps = 10000; // Dummy high value so we don't choose this
  4078. }
  4079. if( abs($min-$max) > 5 ) {
  4080. // Then get tickmarks as 5:s 5, 50, 500, ...
  4081. if( $majend ) {
  4082. list($num5steps,$adj5min,$adj5max,$maj5step) = $this->IntCalcTicks($maxsteps,$min,$max,2);
  4083. }
  4084. else {
  4085. $adj5min = $min;
  4086. $adj5max = $max;
  4087. list($num5steps,$maj5step) = $this->IntCalcTicksFreeze($maxsteps,$min,$max,2);
  4088. }
  4089. }
  4090. else {
  4091. $num5steps = 10000; // Dummy high value so we don't choose this
  4092. }
  4093. // Check to see whichof 1:s, 2:s or 5:s fit better with
  4094. // the requested number of major ticks
  4095. $match1=abs($num1steps-$maxsteps);
  4096. $match2=abs($num2steps-$maxsteps);
  4097. if( !empty($maj5step) && $maj5step > 1 ) {
  4098. $match5=abs($num5steps-$maxsteps);
  4099. }
  4100. else {
  4101. $match5=10000; // Dummy high value
  4102. }
  4103. // Compare these three values and see which is the closest match
  4104. // We use a 0.6 weight to gravitate towards multiple of 5:s
  4105. if( $match1 < $match2 ) {
  4106. if( $match1 < $match5 ) $r=1;
  4107. else $r=3;
  4108. }
  4109. else {
  4110. if( $match2 < $match5 ) $r=2;
  4111. else $r=3;
  4112. }
  4113. // Minsteps are always the same as maxsteps for integer scale
  4114. switch( $r ) {
  4115. case 1:
  4116. $this->ticks->Set($maj1step,$maj1step);
  4117. $this->Update($img,$adj1min,$adj1max);
  4118. break;
  4119. case 2:
  4120. $this->ticks->Set($maj2step,$maj2step);
  4121. $this->Update($img,$adj2min,$adj2max);
  4122. break;
  4123. case 3:
  4124. $this->ticks->Set($maj5step,$maj5step);
  4125. $this->Update($img,$adj5min,$adj5max);
  4126. break;
  4127. default:
  4128. JpGraphError::RaiseL(25073,$r);//('Internal error. Integer scale algorithm comparison out of bound (r=$r)');
  4129. }
  4130. }
  4131. // Calculate autoscale. Used if user hasn't given a scale and ticks
  4132. // $maxsteps is the maximum number of major tickmarks allowed.
  4133. function AutoScale($img,$min,$max,$maxsteps,$majend=true) {
  4134. if( !is_numeric($min) || !is_numeric($max) ) {
  4135. JpGraphError::Raise(25044);
  4136. }
  4137. if( $this->intscale ) {
  4138. $this->IntAutoScale($img,$min,$max,$maxsteps,$majend);
  4139. return;
  4140. }
  4141. if( abs($min-$max) < 0.00001 ) {
  4142. // We need some difference to be able to autoscale
  4143. // make it 5% above and 5% below value
  4144. if( $min==0 && $max==0 ) { // Special case
  4145. $min=-1; $max=1;
  4146. }
  4147. else {
  4148. $delta = (abs($max)+abs($min))*0.005;
  4149. $min -= $delta;
  4150. $max += $delta;
  4151. }
  4152. }
  4153. $gracetop=($this->gracetop/100.0)*abs($max-$min);
  4154. $gracebottom=($this->gracebottom/100.0)*abs($max-$min);
  4155. if( is_numeric($this->autoscale_min) ) {
  4156. $min = $this->autoscale_min;
  4157. if( $min >= $max ) {
  4158. JpGraphError::RaiseL(25071);//('You have specified a min value with SetAutoMin() which is larger than the maximum value used for the scale. This is not possible.');
  4159. }
  4160. if( abs($min-$max ) < 0.001 ) {
  4161. $max *= 1.2;
  4162. }
  4163. }
  4164. if( is_numeric($this->autoscale_max) ) {
  4165. $max = $this->autoscale_max;
  4166. if( $min >= $max ) {
  4167. JpGraphError::RaiseL(25072);//('You have specified a max value with SetAutoMax() which is smaller than the miminum value used for the scale. This is not possible.');
  4168. }
  4169. if( abs($min-$max ) < 0.001 ) {
  4170. $min *= 0.8;
  4171. }
  4172. }
  4173. $min -= $gracebottom;
  4174. $max += $gracetop;
  4175. // First get tickmarks as multiples of 0.1, 1, 10, ...
  4176. if( $majend ) {
  4177. list($num1steps,$adj1min,$adj1max,$min1step,$maj1step) = $this->CalcTicks($maxsteps,$min,$max,1,2);
  4178. }
  4179. else {
  4180. $adj1min=$min;
  4181. $adj1max=$max;
  4182. list($num1steps,$min1step,$maj1step) = $this->CalcTicksFreeze($maxsteps,$min,$max,1,2,false);
  4183. }
  4184. // Then get tick marks as 2:s 0.2, 2, 20, ...
  4185. if( $majend ) {
  4186. list($num2steps,$adj2min,$adj2max,$min2step,$maj2step) = $this->CalcTicks($maxsteps,$min,$max,5,2);
  4187. }
  4188. else {
  4189. $adj2min=$min;
  4190. $adj2max=$max;
  4191. list($num2steps,$min2step,$maj2step) = $this->CalcTicksFreeze($maxsteps,$min,$max,5,2,false);
  4192. }
  4193. // Then get tickmarks as 5:s 0.05, 0.5, 5, 50, ...
  4194. if( $majend ) {
  4195. list($num5steps,$adj5min,$adj5max,$min5step,$maj5step) = $this->CalcTicks($maxsteps,$min,$max,2,5);
  4196. }
  4197. else {
  4198. $adj5min=$min;
  4199. $adj5max=$max;
  4200. list($num5steps,$min5step,$maj5step) = $this->CalcTicksFreeze($maxsteps,$min,$max,2,5,false);
  4201. }
  4202. // Check to see whichof 1:s, 2:s or 5:s fit better with
  4203. // the requested number of major ticks
  4204. $match1=abs($num1steps-$maxsteps);
  4205. $match2=abs($num2steps-$maxsteps);
  4206. $match5=abs($num5steps-$maxsteps);
  4207. // Compare these three values and see which is the closest match
  4208. // We use a 0.8 weight to gravitate towards multiple of 5:s
  4209. $r=$this->MatchMin3($match1,$match2,$match5,0.8);
  4210. switch( $r ) {
  4211. case 1:
  4212. $this->Update($img,$adj1min,$adj1max);
  4213. $this->ticks->Set($maj1step,$min1step);
  4214. break;
  4215. case 2:
  4216. $this->Update($img,$adj2min,$adj2max);
  4217. $this->ticks->Set($maj2step,$min2step);
  4218. break;
  4219. case 3:
  4220. $this->Update($img,$adj5min,$adj5max);
  4221. $this->ticks->Set($maj5step,$min5step);
  4222. break;
  4223. }
  4224. }
  4225. //---------------
  4226. // PRIVATE METHODS
  4227. // This method recalculates all constants that are depending on the
  4228. // margins in the image. If the margins in the image are changed
  4229. // this method should be called for every scale that is registred with
  4230. // that image. Should really be installed as an observer of that image.
  4231. function InitConstants($img) {
  4232. if( $this->type=='x' ) {
  4233. $this->world_abs_size=$img->width - $img->left_margin - $img->right_margin;
  4234. $this->off=$img->left_margin;
  4235. $this->scale_factor = 0;
  4236. if( $this->world_size > 0 ) {
  4237. $this->scale_factor=$this->world_abs_size/($this->world_size*1.0);
  4238. }
  4239. }
  4240. else { // y scale
  4241. $this->world_abs_size=$img->height - $img->top_margin - $img->bottom_margin;
  4242. $this->off=$img->top_margin+$this->world_abs_size;
  4243. $this->scale_factor = 0;
  4244. if( $this->world_size > 0 ) {
  4245. $this->scale_factor=-$this->world_abs_size/($this->world_size*1.0);
  4246. }
  4247. }
  4248. $size = $this->world_size * $this->scale_factor;
  4249. $this->scale_abs=array($this->off,$this->off + $size);
  4250. }
  4251. // Initialize the conversion constants for this scale
  4252. // This tries to pre-calculate as much as possible to speed up the
  4253. // actual conversion (with Translate()) later on
  4254. // $start =scale start in absolute pixels (for x-scale this is an y-position
  4255. // and for an y-scale this is an x-position
  4256. // $len =absolute length in pixels of scale
  4257. function SetConstants($aStart,$aLen) {
  4258. $this->world_abs_size=$aLen;
  4259. $this->off=$aStart;
  4260. if( $this->world_size<=0 ) {
  4261. // This should never ever happen !!
  4262. JpGraphError::RaiseL(25074);
  4263. //("You have unfortunately stumbled upon a bug in JpGraph. It seems like the scale range is ".$this->world_size." [for ".$this->type." scale] <br> Please report Bug #01 to jpgraph@aditus.nu and include the script that gave this error. This problem could potentially be caused by trying to use \"illegal\" values in the input data arrays (like trying to send in strings or only NULL values) which causes the autoscaling to fail.");
  4264. }
  4265. // scale_factor = number of pixels per world unit
  4266. $this->scale_factor=$this->world_abs_size/($this->world_size*1.0);
  4267. // scale_abs = start and end points of scale in absolute pixels
  4268. $this->scale_abs=array($this->off,$this->off+$this->world_size*$this->scale_factor);
  4269. }
  4270. // Calculate number of ticks steps with a specific division
  4271. // $a is the divisor of 10**x to generate the first maj tick intervall
  4272. // $a=1, $b=2 give major ticks with multiple of 10, ...,0.1,1,10,...
  4273. // $a=5, $b=2 give major ticks with multiple of 2:s ...,0.2,2,20,...
  4274. // $a=2, $b=5 give major ticks with multiple of 5:s ...,0.5,5,50,...
  4275. // We return a vector of
  4276. // [$numsteps,$adjmin,$adjmax,$minstep,$majstep]
  4277. // If $majend==true then the first and last marks on the axis will be major
  4278. // labeled tick marks otherwise it will be adjusted to the closest min tick mark
  4279. function CalcTicks($maxsteps,$min,$max,$a,$b,$majend=true) {
  4280. $diff=$max-$min;
  4281. if( $diff==0 ) {
  4282. $ld=0;
  4283. }
  4284. else {
  4285. $ld=floor(log10($diff));
  4286. }
  4287. // Gravitate min towards zero if we are close
  4288. if( $min>0 && $min < pow(10,$ld) ) $min=0;
  4289. //$majstep=pow(10,$ld-1)/$a;
  4290. $majstep=pow(10,$ld)/$a;
  4291. $minstep=$majstep/$b;
  4292. $adjmax=ceil($max/$minstep)*$minstep;
  4293. $adjmin=floor($min/$minstep)*$minstep;
  4294. $adjdiff = $adjmax-$adjmin;
  4295. $numsteps=$adjdiff/$majstep;
  4296. while( $numsteps>$maxsteps ) {
  4297. $majstep=pow(10,$ld)/$a;
  4298. $numsteps=$adjdiff/$majstep;
  4299. ++$ld;
  4300. }
  4301. $minstep=$majstep/$b;
  4302. $adjmin=floor($min/$minstep)*$minstep;
  4303. $adjdiff = $adjmax-$adjmin;
  4304. if( $majend ) {
  4305. $adjmin = floor($min/$majstep)*$majstep;
  4306. $adjdiff = $adjmax-$adjmin;
  4307. $adjmax = ceil($adjdiff/$majstep)*$majstep+$adjmin;
  4308. }
  4309. else {
  4310. $adjmax=ceil($max/$minstep)*$minstep;
  4311. }
  4312. return array($numsteps,$adjmin,$adjmax,$minstep,$majstep);
  4313. }
  4314. function CalcTicksFreeze($maxsteps,$min,$max,$a,$b) {
  4315. // Same as CalcTicks but don't adjust min/max values
  4316. $diff=$max-$min;
  4317. if( $diff==0 ) {
  4318. $ld=0;
  4319. }
  4320. else {
  4321. $ld=floor(log10($diff));
  4322. }
  4323. //$majstep=pow(10,$ld-1)/$a;
  4324. $majstep=pow(10,$ld)/$a;
  4325. $minstep=$majstep/$b;
  4326. $numsteps=floor($diff/$majstep);
  4327. while( $numsteps > $maxsteps ) {
  4328. $majstep=pow(10,$ld)/$a;
  4329. $numsteps=floor($diff/$majstep);
  4330. ++$ld;
  4331. }
  4332. $minstep=$majstep/$b;
  4333. return array($numsteps,$minstep,$majstep);
  4334. }
  4335. function IntCalcTicks($maxsteps,$min,$max,$a,$majend=true) {
  4336. $diff=$max-$min;
  4337. if( $diff==0 ) {
  4338. JpGraphError::RaiseL(25075);//('Can\'t automatically determine ticks since min==max.');
  4339. }
  4340. else {
  4341. $ld=floor(log10($diff));
  4342. }
  4343. // Gravitate min towards zero if we are close
  4344. if( $min>0 && $min < pow(10,$ld) ) {
  4345. $min=0;
  4346. }
  4347. if( $ld == 0 ) {
  4348. $ld=1;
  4349. }
  4350. if( $a == 1 ) {
  4351. $majstep = 1;
  4352. }
  4353. else {
  4354. $majstep=pow(10,$ld)/$a;
  4355. }
  4356. $adjmax=ceil($max/$majstep)*$majstep;
  4357. $adjmin=floor($min/$majstep)*$majstep;
  4358. $adjdiff = $adjmax-$adjmin;
  4359. $numsteps=$adjdiff/$majstep;
  4360. while( $numsteps>$maxsteps ) {
  4361. $majstep=pow(10,$ld)/$a;
  4362. $numsteps=$adjdiff/$majstep;
  4363. ++$ld;
  4364. }
  4365. $adjmin=floor($min/$majstep)*$majstep;
  4366. $adjdiff = $adjmax-$adjmin;
  4367. if( $majend ) {
  4368. $adjmin = floor($min/$majstep)*$majstep;
  4369. $adjdiff = $adjmax-$adjmin;
  4370. $adjmax = ceil($adjdiff/$majstep)*$majstep+$adjmin;
  4371. }
  4372. else {
  4373. $adjmax=ceil($max/$majstep)*$majstep;
  4374. }
  4375. return array($numsteps,$adjmin,$adjmax,$majstep);
  4376. }
  4377. function IntCalcTicksFreeze($maxsteps,$min,$max,$a) {
  4378. // Same as IntCalcTick but don't change min/max values
  4379. $diff=$max-$min;
  4380. if( $diff==0 ) {
  4381. JpGraphError::RaiseL(25075);//('Can\'t automatically determine ticks since min==max.');
  4382. }
  4383. else {
  4384. $ld=floor(log10($diff));
  4385. }
  4386. if( $ld == 0 ) {
  4387. $ld=1;
  4388. }
  4389. if( $a == 1 ) {
  4390. $majstep = 1;
  4391. }
  4392. else {
  4393. $majstep=pow(10,$ld)/$a;
  4394. }
  4395. $numsteps=floor($diff/$majstep);
  4396. while( $numsteps > $maxsteps ) {
  4397. $majstep=pow(10,$ld)/$a;
  4398. $numsteps=floor($diff/$majstep);
  4399. ++$ld;
  4400. }
  4401. return array($numsteps,$majstep);
  4402. }
  4403. // Determine the minimum of three values witha weight for last value
  4404. function MatchMin3($a,$b,$c,$weight) {
  4405. if( $a < $b ) {
  4406. if( $a < ($c*$weight) ) {
  4407. return 1; // $a smallest
  4408. }
  4409. else {
  4410. return 3; // $c smallest
  4411. }
  4412. }
  4413. elseif( $b < ($c*$weight) ) {
  4414. return 2; // $b smallest
  4415. }
  4416. return 3; // $c smallest
  4417. }
  4418. } // Class
  4419. //===================================================
  4420. // CLASS DisplayValue
  4421. // Description: Used to print data values at data points
  4422. //===================================================
  4423. class DisplayValue {
  4424. public $margin=5;
  4425. public $show=false;
  4426. public $valign='',$halign='center';
  4427. public $format='%.1f',$negformat='';
  4428. private $ff=FF_FONT1,$fs=FS_NORMAL,$fsize=10;
  4429. private $iFormCallback='';
  4430. private $angle=0;
  4431. private $color='navy',$negcolor='';
  4432. private $iHideZero=false;
  4433. public $txt=null;
  4434. function __construct() {
  4435. $this->txt = new Text();
  4436. }
  4437. function Show($aFlag=true) {
  4438. $this->show=$aFlag;
  4439. }
  4440. function SetColor($aColor,$aNegcolor='') {
  4441. $this->color = $aColor;
  4442. $this->negcolor = $aNegcolor;
  4443. }
  4444. function SetFont($aFontFamily,$aFontStyle=FS_NORMAL,$aFontSize=10) {
  4445. $this->ff=$aFontFamily;
  4446. $this->fs=$aFontStyle;
  4447. $this->fsize=$aFontSize;
  4448. }
  4449. function ApplyFont($aImg) {
  4450. $aImg->SetFont($this->ff,$this->fs,$this->fsize);
  4451. }
  4452. function SetMargin($aMargin) {
  4453. $this->margin = $aMargin;
  4454. }
  4455. function SetAngle($aAngle) {
  4456. $this->angle = $aAngle;
  4457. }
  4458. function SetAlign($aHAlign,$aVAlign='') {
  4459. $this->halign = $aHAlign;
  4460. $this->valign = $aVAlign;
  4461. }
  4462. function SetFormat($aFormat,$aNegFormat='') {
  4463. $this->format= $aFormat;
  4464. $this->negformat= $aNegFormat;
  4465. }
  4466. function SetFormatCallback($aFunc) {
  4467. $this->iFormCallback = $aFunc;
  4468. }
  4469. function HideZero($aFlag=true) {
  4470. $this->iHideZero=$aFlag;
  4471. }
  4472. function Stroke($img,$aVal,$x,$y) {
  4473. if( $this->show )
  4474. {
  4475. if( $this->negformat=='' ) {
  4476. $this->negformat=$this->format;
  4477. }
  4478. if( $this->negcolor=='' ) {
  4479. $this->negcolor=$this->color;
  4480. }
  4481. if( $aVal===NULL || (is_string($aVal) && ($aVal=='' || $aVal=='-' || $aVal=='x' ) ) ) {
  4482. return;
  4483. }
  4484. if( is_numeric($aVal) && $aVal==0 && $this->iHideZero ) {
  4485. return;
  4486. }
  4487. // Since the value is used in different cirumstances we need to check what
  4488. // kind of formatting we shall use. For example, to display values in a line
  4489. // graph we simply display the formatted value, but in the case where the user
  4490. // has already specified a text string we don't fo anything.
  4491. if( $this->iFormCallback != '' ) {
  4492. $f = $this->iFormCallback;
  4493. $sval = call_user_func($f,$aVal);
  4494. }
  4495. elseif( is_numeric($aVal) ) {
  4496. if( $aVal >= 0 ) {
  4497. $sval=sprintf($this->format,$aVal);
  4498. }
  4499. else {
  4500. $sval=sprintf($this->negformat,$aVal);
  4501. }
  4502. }
  4503. else {
  4504. $sval=$aVal;
  4505. }
  4506. $y = $y-sign($aVal)*$this->margin;
  4507. $this->txt->Set($sval);
  4508. $this->txt->SetPos($x,$y);
  4509. $this->txt->SetFont($this->ff,$this->fs,$this->fsize);
  4510. if( $this->valign == '' ) {
  4511. if( $aVal >= 0 ) {
  4512. $valign = "bottom";
  4513. }
  4514. else {
  4515. $valign = "top";
  4516. }
  4517. }
  4518. else {
  4519. $valign = $this->valign;
  4520. }
  4521. $this->txt->Align($this->halign,$valign);
  4522. $this->txt->SetOrientation($this->angle);
  4523. if( $aVal > 0 ) {
  4524. $this->txt->SetColor($this->color);
  4525. }
  4526. else {
  4527. $this->txt->SetColor($this->negcolor);
  4528. }
  4529. $this->txt->Stroke($img);
  4530. }
  4531. }
  4532. }
  4533. //===================================================
  4534. // CLASS Plot
  4535. // Description: Abstract base class for all concrete plot classes
  4536. //===================================================
  4537. class Plot {
  4538. public $numpoints=0;
  4539. public $value;
  4540. public $legend='';
  4541. public $coords=array();
  4542. public $color='black';
  4543. public $hidelegend=false;
  4544. public $line_weight=1;
  4545. public $csimtargets=array(),$csimwintargets=array(); // Array of targets for CSIM
  4546. public $csimareas=''; // Resultant CSIM area tags
  4547. public $csimalts=null; // ALT:s for corresponding target
  4548. public $legendcsimtarget='',$legendcsimwintarget='';
  4549. public $legendcsimalt='';
  4550. protected $weight=1;
  4551. protected $center=false;
  4552. function __construct($aDatay,$aDatax=false) {
  4553. $this->numpoints = count($aDatay);
  4554. if( $this->numpoints==0 ) {
  4555. JpGraphError::RaiseL(25121);//("Empty input data array specified for plot. Must have at least one data point.");
  4556. }
  4557. $this->coords[0]=$aDatay;
  4558. if( is_array($aDatax) ) {
  4559. $this->coords[1]=$aDatax;
  4560. $n = count($aDatax);
  4561. for( $i=0; $i < $n; ++$i ) {
  4562. if( !is_numeric($aDatax[$i]) ) {
  4563. JpGraphError::RaiseL(25070);
  4564. }
  4565. }
  4566. }
  4567. $this->value = new DisplayValue();
  4568. }
  4569. // Stroke the plot
  4570. // "virtual" function which must be implemented by
  4571. // the subclasses
  4572. function Stroke($aImg,$aXScale,$aYScale) {
  4573. JpGraphError::RaiseL(25122);//("JpGraph: Stroke() must be implemented by concrete subclass to class Plot");
  4574. }
  4575. function HideLegend($f=true) {
  4576. $this->hidelegend = $f;
  4577. }
  4578. function DoLegend($graph) {
  4579. if( !$this->hidelegend )
  4580. $this->Legend($graph);
  4581. }
  4582. function StrokeDataValue($img,$aVal,$x,$y) {
  4583. $this->value->Stroke($img,$aVal,$x,$y);
  4584. }
  4585. // Set href targets for CSIM
  4586. function SetCSIMTargets($aTargets,$aAlts='',$aWinTargets='') {
  4587. $this->csimtargets=$aTargets;
  4588. $this->csimwintargets=$aWinTargets;
  4589. $this->csimalts=$aAlts;
  4590. }
  4591. // Get all created areas
  4592. function GetCSIMareas() {
  4593. return $this->csimareas;
  4594. }
  4595. // "Virtual" function which gets called before any scale
  4596. // or axis are stroked used to do any plot specific adjustment
  4597. function PreStrokeAdjust($aGraph) {
  4598. if( substr($aGraph->axtype,0,4) == "text" && (isset($this->coords[1])) ) {
  4599. JpGraphError::RaiseL(25123);//("JpGraph: You can't use a text X-scale with specified X-coords. Use a \"int\" or \"lin\" scale instead.");
  4600. }
  4601. return true;
  4602. }
  4603. // Virtual function to the the concrete plot class to make any changes to the graph
  4604. // and scale before the stroke process begins
  4605. function PreScaleSetup($aGraph) {
  4606. // Empty
  4607. }
  4608. // Get minimum values in plot
  4609. function Min() {
  4610. if( isset($this->coords[1]) ) {
  4611. $x=$this->coords[1];
  4612. }
  4613. else {
  4614. $x='';
  4615. }
  4616. if( $x != '' && count($x) > 0 ) {
  4617. $xm=min($x);
  4618. }
  4619. else {
  4620. $xm=0;
  4621. }
  4622. $y=$this->coords[0];
  4623. $cnt = count($y);
  4624. if( $cnt > 0 ) {
  4625. $i=0;
  4626. while( $i<$cnt && !is_numeric($ym=$y[$i]) ) {
  4627. $i++;
  4628. }
  4629. while( $i < $cnt) {
  4630. if( is_numeric($y[$i]) ) {
  4631. $ym=min($ym,$y[$i]);
  4632. }
  4633. ++$i;
  4634. }
  4635. }
  4636. else {
  4637. $ym='';
  4638. }
  4639. return array($xm,$ym);
  4640. }
  4641. // Get maximum value in plot
  4642. function Max() {
  4643. if( isset($this->coords[1]) ) {
  4644. $x=$this->coords[1];
  4645. }
  4646. else {
  4647. $x='';
  4648. }
  4649. if( $x!='' && count($x) > 0 ) {
  4650. $xm=max($x);
  4651. }
  4652. else {
  4653. $xm = $this->numpoints-1;
  4654. }
  4655. $y=$this->coords[0];
  4656. if( count($y) > 0 ) {
  4657. $cnt = count($y);
  4658. $i=0;
  4659. while( $i<$cnt && !is_numeric($ym=$y[$i]) ) {
  4660. $i++;
  4661. }
  4662. while( $i < $cnt ) {
  4663. if( is_numeric($y[$i]) ) {
  4664. $ym=max($ym,$y[$i]);
  4665. }
  4666. ++$i;
  4667. }
  4668. }
  4669. else {
  4670. $ym='';
  4671. }
  4672. return array($xm,$ym);
  4673. }
  4674. function SetColor($aColor) {
  4675. $this->color=$aColor;
  4676. }
  4677. function SetLegend($aLegend,$aCSIM='',$aCSIMAlt='',$aCSIMWinTarget='') {
  4678. $this->legend = $aLegend;
  4679. $this->legendcsimtarget = $aCSIM;
  4680. $this->legendcsimwintarget = $aCSIMWinTarget;
  4681. $this->legendcsimalt = $aCSIMAlt;
  4682. }
  4683. function SetWeight($aWeight) {
  4684. $this->weight=$aWeight;
  4685. }
  4686. function SetLineWeight($aWeight=1) {
  4687. $this->line_weight=$aWeight;
  4688. }
  4689. function SetCenter($aCenter=true) {
  4690. $this->center = $aCenter;
  4691. }
  4692. // This method gets called by Graph class to plot anything that should go
  4693. // into the margin after the margin color has been set.
  4694. function StrokeMargin($aImg) {
  4695. return true;
  4696. }
  4697. // Framework function the chance for each plot class to set a legend
  4698. function Legend($aGraph) {
  4699. if( $this->legend != '' ) {
  4700. $aGraph->legend->Add($this->legend,$this->color,'',0,$this->legendcsimtarget,$this->legendcsimalt,$this->legendcsimwintarget);
  4701. }
  4702. }
  4703. } // Class
  4704. // Provide a deterministic list of new colors whenever the getColor() method
  4705. // is called. Used to automatically set colors of plots.
  4706. class ColorFactory {
  4707. static private $iIdx = 0;
  4708. static private $iColorList = array(
  4709. 'black',
  4710. 'blue',
  4711. 'orange',
  4712. 'darkgreen',
  4713. 'red',
  4714. 'AntiqueWhite3',
  4715. 'aquamarine3',
  4716. 'azure4',
  4717. 'brown',
  4718. 'cadetblue3',
  4719. 'chartreuse4',
  4720. 'chocolate',
  4721. 'darkblue',
  4722. 'darkgoldenrod3',
  4723. 'darkorchid3',
  4724. 'darksalmon',
  4725. 'darkseagreen4',
  4726. 'deepskyblue2',
  4727. 'dodgerblue4',
  4728. 'gold3',
  4729. 'hotpink',
  4730. 'lawngreen',
  4731. 'lightcoral',
  4732. 'lightpink3',
  4733. 'lightseagreen',
  4734. 'lightslateblue',
  4735. 'mediumpurple',
  4736. 'olivedrab',
  4737. 'orangered1',
  4738. 'peru',
  4739. 'slategray',
  4740. 'yellow4',
  4741. 'springgreen2');
  4742. static private $iNum = 33;
  4743. static function getColor() {
  4744. if( ColorFactory::$iIdx >= ColorFactory::$iNum )
  4745. ColorFactory::$iIdx = 0;
  4746. return ColorFactory::$iColorList[ColorFactory::$iIdx++];
  4747. }
  4748. }
  4749. // <EOF>
  4750. ?>