1<?php 2//======================================================================= 3// File: JPGRAPH.PHP 4// Description: PHP Graph Plotting library. Base module. 5// Created: 2001-01-08 6// Author: Johan Persson (johanp@aditus.nu) 7// Ver: $Id: jpgraph.php,v 1.1 2006/04/27 11:33:03 k-fish Exp $ 8// 9// Copyright (c) Aditus Consulting. All rights reserved. 10//======================================================================== 11 12require_once('jpg-config.inc.php'); 13 14// Version info 15DEFINE('JPG_VERSION','2.1'); 16 17// For internal use only 18DEFINE("_JPG_DEBUG",false); 19DEFINE("_FORCE_IMGTOFILE",false); 20DEFINE("_FORCE_IMGDIR",'/tmp/jpgimg/'); 21 22 23// Should the image be a truecolor image? 24// Note 1: Has only effect with GD 2.0.1 and above. 25// Note 2: GD 2.0.1 + PHP 4.0.6 on Win32 crashes when trying to use 26// trucolor. 27// Note 3: MUST be enabled to get background images working with GD2 28DEFINE('USE_TRUECOLOR',true); 29 30//------------------------------------------------------------------------ 31// Automatic settings of path for cache and font directory 32// if they have not been previously specified 33//------------------------------------------------------------------------ 34if(USE_CACHE) { 35 if (!defined('CACHE_DIR')) { 36 if ( strstr( PHP_OS, 'WIN') ) { 37 if( empty($_SERVER['TEMP']) ) { 38 $t = new ErrMsgText(); 39 $msg = $t->Get(11,$file,$lineno); 40 die($msg); 41 } 42 else { 43 DEFINE('CACHE_DIR', $_SERVER['TEMP'] . '/'); 44 } 45 } else { 46 DEFINE('CACHE_DIR','/tmp/jpgraph_cache/'); 47 } 48 } 49} 50elseif( !defined('CACHE_DIR') ) { 51 DEFINE('CACHE_DIR', ''); 52} 53 54if (!defined('TTF_DIR')) { 55 if (strstr( PHP_OS, 'WIN') ) { 56 $sroot = getenv('SystemRoot'); 57 if( empty($sroot) ) { 58 $t = new ErrMsgText(); 59 $msg = $t->Get(12,$file,$lineno); 60 die($msg); 61 } 62 else { 63 DEFINE('TTF_DIR', $sroot.'/fonts/'); 64 } 65 } else { 66 DEFINE('TTF_DIR','/usr/X11R6/lib/X11/fonts/truetype/'); 67 } 68} 69 70//------------------------------------------------------------------ 71// Constants which are used as parameters for the method calls 72//------------------------------------------------------------------ 73 74// TTF Font families 75// Note: First font must be FF_COURIER and the last font family must 76// be given to _LAST_FONT. This is used for error checking in the text 77// handling routines. 78DEFINE("FF_COURIER",10); 79DEFINE("FF_VERDANA",11); 80DEFINE("FF_TIMES",12); 81DEFINE("FF_COMIC",14); 82DEFINE("FF_ARIAL",15); 83DEFINE("FF_GEORGIA",16); 84DEFINE("FF_TREBUCHE",17); 85 86// Gnome Vera font 87// Available from http://www.gnome.org/fonts/ 88DEFINE("FF_VERA",18); 89DEFINE("FF_VERAMONO",19); 90DEFINE("FF_VERASERIF",20); 91 92// Chinese font 93DEFINE("FF_SIMSUN",30); 94DEFINE("FF_CHINESE",31); 95DEFINE("FF_BIG5",31); 96 97// Japanese font 98DEFINE("FF_MINCHO",40); 99DEFINE("FF_PMINCHO",41); 100DEFINE("FF_GOTHIC",42); 101DEFINE("FF_PGOTHIC",43); 102 103// Limits for fonts 104DEFINE("_FIRST_FONT",10); 105DEFINE("_LAST_FONT",43); 106 107// TTF Font styles 108DEFINE("FS_NORMAL",9001); 109DEFINE("FS_BOLD",9002); 110DEFINE("FS_ITALIC",9003); 111DEFINE("FS_BOLDIT",9004); 112DEFINE("FS_BOLDITALIC",9004); 113 114//Definitions for internal font, new style 115DEFINE("FF_FONT0",1); 116DEFINE("FF_FONT1",2); 117DEFINE("FF_FONT2",4); 118 119// Tick density 120DEFINE("TICKD_DENSE",1); 121DEFINE("TICKD_NORMAL",2); 122DEFINE("TICKD_SPARSE",3); 123DEFINE("TICKD_VERYSPARSE",4); 124 125// Side for ticks and labels. 126DEFINE("SIDE_LEFT",-1); 127DEFINE("SIDE_RIGHT",1); 128DEFINE("SIDE_DOWN",-1); 129DEFINE("SIDE_BOTTOM",-1); 130DEFINE("SIDE_UP",1); 131DEFINE("SIDE_TOP",1); 132 133// Legend type stacked vertical or horizontal 134DEFINE("LEGEND_VERT",0); 135DEFINE("LEGEND_HOR",1); 136 137// Mark types for plot marks 138DEFINE("MARK_SQUARE",1); 139DEFINE("MARK_UTRIANGLE",2); 140DEFINE("MARK_DTRIANGLE",3); 141DEFINE("MARK_DIAMOND",4); 142DEFINE("MARK_CIRCLE",5); 143DEFINE("MARK_FILLEDCIRCLE",6); 144DEFINE("MARK_CROSS",7); 145DEFINE("MARK_STAR",8); 146DEFINE("MARK_X",9); 147DEFINE("MARK_LEFTTRIANGLE",10); 148DEFINE("MARK_RIGHTTRIANGLE",11); 149DEFINE("MARK_FLASH",12); 150DEFINE("MARK_IMG",13); 151DEFINE("MARK_FLAG1",14); 152DEFINE("MARK_FLAG2",15); 153DEFINE("MARK_FLAG3",16); 154DEFINE("MARK_FLAG4",17); 155 156// Builtin images 157DEFINE("MARK_IMG_PUSHPIN",50); 158DEFINE("MARK_IMG_SPUSHPIN",50); 159DEFINE("MARK_IMG_LPUSHPIN",51); 160DEFINE("MARK_IMG_DIAMOND",52); 161DEFINE("MARK_IMG_SQUARE",53); 162DEFINE("MARK_IMG_STAR",54); 163DEFINE("MARK_IMG_BALL",55); 164DEFINE("MARK_IMG_SBALL",55); 165DEFINE("MARK_IMG_MBALL",56); 166DEFINE("MARK_IMG_LBALL",57); 167DEFINE("MARK_IMG_BEVEL",58); 168 169// Inline defines 170DEFINE("INLINE_YES",1); 171DEFINE("INLINE_NO",0); 172 173// Format for background images 174DEFINE("BGIMG_FILLPLOT",1); 175DEFINE("BGIMG_FILLFRAME",2); 176DEFINE("BGIMG_COPY",3); 177DEFINE("BGIMG_CENTER",4); 178 179// Depth of objects 180DEFINE("DEPTH_BACK",0); 181DEFINE("DEPTH_FRONT",1); 182 183// Direction 184DEFINE("VERTICAL",1); 185DEFINE("HORIZONTAL",0); 186 187 188// Axis styles for scientific style axis 189DEFINE('AXSTYLE_SIMPLE',1); 190DEFINE('AXSTYLE_BOXIN',2); 191DEFINE('AXSTYLE_BOXOUT',3); 192DEFINE('AXSTYLE_YBOXIN',4); 193DEFINE('AXSTYLE_YBOXOUT',5); 194 195// Style for title backgrounds 196DEFINE('TITLEBKG_STYLE1',1); 197DEFINE('TITLEBKG_STYLE2',2); 198DEFINE('TITLEBKG_STYLE3',3); 199DEFINE('TITLEBKG_FRAME_NONE',0); 200DEFINE('TITLEBKG_FRAME_FULL',1); 201DEFINE('TITLEBKG_FRAME_BOTTOM',2); 202DEFINE('TITLEBKG_FRAME_BEVEL',3); 203DEFINE('TITLEBKG_FILLSTYLE_HSTRIPED',1); 204DEFINE('TITLEBKG_FILLSTYLE_VSTRIPED',2); 205DEFINE('TITLEBKG_FILLSTYLE_SOLID',3); 206 207// Style for background gradient fills 208DEFINE('BGRAD_FRAME',1); 209DEFINE('BGRAD_MARGIN',2); 210DEFINE('BGRAD_PLOT',3); 211 212// Width of tab titles 213DEFINE('TABTITLE_WIDTHFIT',0); 214DEFINE('TABTITLE_WIDTHFULL',-1); 215 216// Defines for 3D skew directions 217DEFINE('SKEW3D_UP',0); 218DEFINE('SKEW3D_DOWN',1); 219DEFINE('SKEW3D_LEFT',2); 220DEFINE('SKEW3D_RIGHT',3); 221 222// Line styles 223DEFINE('LINESTYLE_DOTTED',1); 224DEFINE('LINESTYLE_DASHED',2); 225DEFINE('LINESTYLE_LONGDASH',3); 226DEFINE('LINESTYLE_SOLID',4); 227 228// 229// Get hold of gradient class (In Version 2.x) 230// 231require_once 'jpgraph_gradient.php'; 232 233GLOBAL $__jpg_err_locale ; 234$__jpg_err_locale = 'en'; 235class ErrMsgText { 236 private $lt=NULL; 237 private $supportedLocales = array('en'); 238 function ErrMsgText() { 239 GLOBAL $__jpg_err_locale; 240 if( !in_array($__jpg_err_locale,$this->supportedLocales) ) 241 $aLoc = 'en'; 242 require_once('lang/'.$__jpg_err_locale.'.inc.php'); 243 $this->lt = $_jpg_messages; 244 } 245 246 function Get($errnbr,$a1=null,$a2=null,$a3=null,$a4=null,$a5=null) { 247 if( !isset($this->lt[$errnbr]) ) { 248 return 'Internal error: The specified error message does not exist in the chosen locale. (Please blame the translator...))'; 249 } 250 $ea = $this->lt[$errnbr]; 251 $j=0; 252 if( $a1 !== null ) { 253 $argv[$j++] = $a1; 254 if( $a2 !== null ) { 255 $argv[$j++] = $a2; 256 if( $a3 !== null ) { 257 $argv[$j++] = $a3; 258 if( $a4 !== null ) { 259 $argv[$j++] = $a4; 260 if( $a5 !== null ) { 261 $argv[$j++] = $a5; 262 } 263 } 264 } 265 } 266 } 267 $numargs = $j; 268 if( $ea[1] != $numargs ) { 269 // Error message argument count do not match. 270 // Just return the error message without arguments. 271 return $ea[0]; 272 } 273 switch( $numargs ) { 274 case 1: 275 $msg = sprintf($ea[0],$argv[0]); 276 break; 277 case 2: 278 $msg = sprintf($ea[0],$argv[0],$argv[1]); 279 break; 280 case 3: 281 $msg = sprintf($ea[0],$argv[0],$argv[1],$argv[2]); 282 break; 283 case 4: 284 $msg = sprintf($ea[0],$argv[0],$argv[1],$argv[2],$argv[3]); 285 break; 286 case 5: 287 $msg = sprintf($ea[0],$argv[0],$argv[1],$argv[2],$argv[3],$argv[4]); 288 break; 289 case 0: 290 default: 291 $msg = sprintf($ea[0]); 292 break; 293 } 294 return $msg; 295 } 296} 297 298// 299// A wrapper class that is used to access the specified error object 300// (to hide the global error parameter and avoid having a GLOBAL directive 301// in all methods. 302// 303class JpGraphError { 304 private static $__jpg_err; 305 public static function Install($aErrObject) { 306 self::$__jpg_err = new $aErrObject; 307 } 308 public static function Raise($aMsg,$aHalt=true){ 309 self::$__jpg_err->Raise($aMsg,$aHalt); 310 } 311 public static function RaiseL($errnbr,$a1=null,$a2=null,$a3=null,$a4=null,$a5=null) { 312 $t = new ErrMsgText(); 313 $msg = $t->Get($errnbr,$a1,$a2,$a3,$a4,$a5); 314 self::$__jpg_err->Raise($msg); 315 } 316} 317 318// 319// ... and install the default error handler 320// 321if( USE_IMAGE_ERROR_HANDLER ) { 322 JpGraphError::Install("JpGraphErrObjectImg"); 323} 324else { 325 JpGraphError::Install("JpGraphErrObject"); 326} 327 328// 329// Make GD sanity check 330// 331if( !function_exists("imagetypes") || !function_exists('imagecreatefromstring') ) { 332 JpGraphError::RaiseL(25001); 333//("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)"); 334} 335 336// 337// First of all set up a default error handler 338// 339 340//============================================================= 341// The default trivial text error handler. 342//============================================================= 343class JpGraphErrObject { 344 345 protected $iTitle = "JpGraph Error"; 346 protected $iDest = false; 347 348 349 function JpGraphErrObject() { 350 // Empty. Reserved for future use 351 } 352 353 function SetTitle($aTitle) { 354 $this->iTitle = $aTitle; 355 } 356 357 function SetStrokeDest($aDest) { 358 $this->iDest = $aDest; 359 } 360 361 // If aHalt is true then execution can't continue. Typical used for fatal errors 362 function Raise($aMsg,$aHalt=true) { 363 $aMsg = $this->iTitle.' '.$aMsg; 364 if ($this->iDest) { 365 $f = @fopen($this->iDest,'a'); 366 if( $f ) { 367 @fwrite($f,$aMsg); 368 @fclose($f); 369 } 370 } 371 else { 372 echo $aMsg; 373 } 374 if( $aHalt ) 375 die(); 376 } 377} 378 379//============================================================== 380// An image based error handler 381//============================================================== 382class JpGraphErrObjectImg extends JpGraphErrObject { 383 384 function Raise($aMsg,$aHalt=true) { 385 $img_iconerror = 386 'iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAMAAAC7IEhfAAAAaV'. 387 'BMVEX//////2Xy8mLl5V/Z2VvMzFi/v1WyslKlpU+ZmUyMjEh/'. 388 'f0VyckJlZT9YWDxMTDjAwMDy8sLl5bnY2K/MzKW/v5yyspKlpY'. 389 'iYmH+MjHY/PzV/f2xycmJlZVlZWU9MTEXY2Ms/PzwyMjLFTjea'. 390 'AAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACx'. 391 'IAAAsSAdLdfvwAAAAHdElNRQfTBgISOCqusfs5AAABLUlEQVR4'. 392 '2tWV3XKCMBBGWfkranCIVClKLd/7P2Q3QsgCxjDTq+6FE2cPH+'. 393 'xJ0Ogn2lQbsT+Wrs+buAZAV4W5T6Bs0YXBBwpKgEuIu+JERAX6'. 394 'wM2rHjmDdEITmsQEEmWADgZm6rAjhXsoMGY9B/NZBwJzBvn+e3'. 395 'wHntCAJdGu9SviwIwoZVDxPB9+Rc0TSEbQr0j3SA1gwdSn6Db0'. 396 '6Tm1KfV6yzWGQO7zdpvyKLKBDmRFjzeB3LYgK7r6A/noDAfjtS'. 397 'IXaIzbJSv6WgUebTMV4EoRB8a2mQiQjgtF91HdKDKZ1gtFtQjk'. 398 'YcWaR5OKOhkYt+ZsTFdJRfPAApOpQYJTNHvCRSJR6SJngQadfc'. 399 'vd69OLMddVOPCGVnmrFD8bVYd3JXfxXPtLR/+mtv59/ALWiiMx'. 400 'qL72fwAAAABJRU5ErkJggg==' ; 401 402 if( function_exists("imagetypes") ) 403 $supported = imagetypes(); 404 else 405 $supported = 0; 406 407 if( !function_exists('imagecreatefromstring') ) 408 $supported = 0; 409 410 if( ob_get_length() || headers_sent() || !($supported & IMG_PNG) ) { 411 // Special case for headers already sent or that the installation doesn't support 412 // the PNG format (which the error icon is encoded in). 413 // Dont return an image since it can't be displayed 414 die($this->iTitle.' '.$aMsg); 415 } 416 417 $aMsg = wordwrap($aMsg,55); 418 $lines = substr_count($aMsg,"\n"); 419 420 // Create the error icon GD 421 $erricon = Image::CreateFromString(base64_decode($img_iconerror)); 422 423 // Create an image that contains the error text. 424 $w=400; 425 $h=100 + 15*max(0,$lines-3); 426 427 $img = new Image($w,$h); 428 429 430 // Drop shadow 431 $img->SetColor("gray"); 432 $img->FilledRectangle(5,5,$w-1,$h-1,10); 433 $img->SetColor("gray:0.7"); 434 $img->FilledRectangle(5,5,$w-3,$h-3,10); 435 436 // Window background 437 $img->SetColor("lightblue"); 438 $img->FilledRectangle(1,1,$w-5,$h-5); 439 $img->CopyCanvasH($img->img,$erricon,5,30,0,0,40,40); 440 441 // Window border 442 $img->SetColor("black"); 443 $img->Rectangle(1,1,$w-5,$h-5); 444 $img->Rectangle(0,0,$w-4,$h-4); 445 446 // Window top row 447 $img->SetColor("darkred"); 448 for($y=3; $y < 18; $y += 2 ) 449 $img->Line(1,$y,$w-6,$y); 450 451 // "White shadow" 452 $img->SetColor("white"); 453 454 // Left window edge 455 $img->Line(2,2,2,$h-5); 456 $img->Line(2,2,$w-6,2); 457 458 // "Gray button shadow" 459 $img->SetColor("darkgray"); 460 461 // Gray window shadow 462 $img->Line(2,$h-6,$w-5,$h-6); 463 $img->Line(3,$h-7,$w-5,$h-7); 464 465 // Window title 466 $m = floor($w/2-5); 467 $l = 100; 468 $img->SetColor("lightgray:1.3"); 469 $img->FilledRectangle($m-$l,2,$m+$l,16); 470 471 // Stroke text 472 $img->SetColor("darkred"); 473 $img->SetFont(FF_FONT2,FS_BOLD); 474 $img->StrokeText($m-50,15,$this->iTitle); 475 $img->SetColor("black"); 476 $img->SetFont(FF_FONT1,FS_NORMAL); 477 $txt = new Text($aMsg,52,25); 478 $txt->Align("left","top"); 479 $txt->Stroke($img); 480 if ($this->iDest) { 481 $img->Stream($this->iDest); 482 } else { 483 $img->Headers(); 484 $img->Stream(); 485 } 486 if( $aHalt ) 487 die(); 488 } 489} 490 491// 492// Setup PHP error handler 493// 494function _phpErrorHandler($errno,$errmsg,$filename, $linenum, $vars) { 495 // Respect current error level 496 if( $errno & error_reporting() ) { 497 JpGraphError::RaiseL(25003,basename($filename),$linenum,$errmsg); 498 } 499} 500 501if( INSTALL_PHP_ERR_HANDLER ) { 502 set_error_handler("_phpErrorHandler"); 503} 504 505// 506//Check if there were any warnings, perhaps some wrong includes by the 507//user 508// 509if( isset($GLOBALS['php_errormsg']) && CATCH_PHPERRMSG && 510 !preg_match('|Deprecated|', $GLOBALS['php_errormsg']) ) { 511 JpGraphError::RaiseL(25004,$GLOBALS['php_errormsg']); 512} 513 514 515// Useful mathematical function 516function sign($a) {return $a >= 0 ? 1 : -1;} 517 518// Utility function to generate an image name based on the filename we 519// are running from and assuming we use auto detection of graphic format 520// (top level), i.e it is safe to call this function 521// from a script that uses JpGraph 522function GenImgName() { 523 // Determine what format we should use when we save the images 524 $supported = imagetypes(); 525 if( $supported & IMG_PNG ) $img_format="png"; 526 elseif( $supported & IMG_GIF ) $img_format="gif"; 527 elseif( $supported & IMG_JPG ) $img_format="jpeg"; 528 529 if( !isset($_SERVER['PHP_SELF']) ) 530 JpGraphError::RaiseL(25005); 531//(" 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."); 532 $fname = basename($_SERVER['PHP_SELF']); 533 if( !empty($_SERVER['QUERY_STRING']) ) { 534 $q = @$_SERVER['QUERY_STRING']; 535 $fname .= '?'.preg_replace("/\W/", "_", $q).'.'.$img_format; 536 } 537 else { 538 $fname = substr($fname,0,strlen($fname)-4).'.'.$img_format; 539 } 540 return $fname; 541} 542 543class LanguageConv { 544 private $g2312 = null ; 545 546 function Convert($aTxt,$aFF) { 547 if( LANGUAGE_CYRILLIC ) { 548 if( CYRILLIC_FROM_WINDOWS ) { 549 $aTxt = convert_cyr_string($aTxt, "w", "k"); 550 } 551 $isostring = convert_cyr_string($aTxt, "k", "i"); 552 $unistring = LanguageConv::iso2uni($isostring); 553 return $unistring; 554 } 555 elseif( $aFF === FF_SIMSUN ) { 556 // Do Chinese conversion 557 if( $this->g2312 == null ) { 558 include_once 'jpgraph_gb2312.php' ; 559 $this->g2312 = new GB2312toUTF8(); 560 } 561 return $this->g2312->gb2utf8($aTxt); 562 } 563 elseif( $aFF === FF_CHINESE ) { 564 if( !function_exists('iconv') ) { 565 JpGraphError::RaiseL(25006); 566//('Usage of FF_CHINESE (FF_BIG5) font family requires that your PHP setup has the iconv() function. By default this is not compiled into PHP (needs the "--width-iconv" when configured).'); 567 } 568 return iconv('BIG5','UTF-8',$aTxt); 569 } 570 else 571 return $aTxt; 572 } 573 574 // Translate iso encoding to unicode 575 function iso2uni ($isoline){ 576 $uniline=''; 577 for ($i=0; $i < strlen($isoline); $i++){ 578 $thischar=substr($isoline,$i,1); 579 $charcode=ord($thischar); 580 $uniline.=($charcode>175) ? "&#" . (1040+($charcode-176)). ";" : $thischar; 581 } 582 return $uniline; 583 } 584} 585 586//=================================================== 587// CLASS JpgTimer 588// Description: General timing utility class to handle 589// time measurement of generating graphs. Multiple 590// timers can be started. 591//=================================================== 592class JpgTimer { 593 private $start, $idx; 594//--------------- 595// CONSTRUCTOR 596 function JpgTimer() { 597 $this->idx=0; 598 } 599 600//--------------- 601// PUBLIC METHODS 602 603 // Push a new timer start on stack 604 function Push() { 605 list($ms,$s)=explode(" ",microtime()); 606 $this->start[$this->idx++]=floor($ms*1000) + 1000*$s; 607 } 608 609 // Pop the latest timer start and return the diff with the 610 // current time 611 function Pop() { 612 assert($this->idx>0); 613 list($ms,$s)=explode(" ",microtime()); 614 $etime=floor($ms*1000) + (1000*$s); 615 $this->idx--; 616 return $etime-$this->start[$this->idx]; 617 } 618} // Class 619 620$gJpgBrandTiming = BRAND_TIMING; 621//=================================================== 622// CLASS DateLocale 623// Description: Hold localized text used in dates 624//=================================================== 625class DateLocale { 626 627 public $iLocale = 'C'; // environmental locale be used by default 628 private $iDayAbb = null, $iShortDay = null, $iShortMonth = null, $iMonthName = null; 629 630//--------------- 631// CONSTRUCTOR 632 function DateLocale() { 633 settype($this->iDayAbb, 'array'); 634 settype($this->iShortDay, 'array'); 635 settype($this->iShortMonth, 'array'); 636 settype($this->iMonthName, 'array'); 637 638 639 $this->Set('C'); 640 } 641 642//--------------- 643// PUBLIC METHODS 644 function Set($aLocale) { 645 if ( in_array($aLocale, array_keys($this->iDayAbb)) ){ 646 $this->iLocale = $aLocale; 647 return TRUE; // already cached nothing else to do! 648 } 649 650 $pLocale = setlocale(LC_TIME, 0); // get current locale for LC_TIME 651 $res = @setlocale(LC_TIME, $aLocale); 652 if ( ! $res ){ 653 JpGraphError::RaiseL(25007,$aLocale); 654//("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."); 655 return FALSE; 656 } 657 658 $this->iLocale = $aLocale; 659 for ( $i = 0, $ofs = 0 - strftime('%w'); $i < 7; $i++, $ofs++ ){ 660 $day = strftime('%a', strtotime("$ofs day")); 661 $day[0] = strtoupper($day[0]); 662 $this->iDayAbb[$aLocale][]= $day[0]; 663 $this->iShortDay[$aLocale][]= $day; 664 } 665 666 for($i=1; $i<=12; ++$i) { 667 list($short ,$full) = explode('|', strftime("%b|%B",strtotime("2001-$i-01"))); 668 $this->iShortMonth[$aLocale][] = ucfirst($short); 669 $this->iMonthName [$aLocale][] = ucfirst($full); 670 } 671 672 setlocale(LC_TIME, $pLocale); 673 674 return TRUE; 675 } 676 677 678 function GetDayAbb() { 679 return $this->iDayAbb[$this->iLocale]; 680 } 681 682 function GetShortDay() { 683 return $this->iShortDay[$this->iLocale]; 684 } 685 686 function GetShortMonth() { 687 return $this->iShortMonth[$this->iLocale]; 688 } 689 690 function GetShortMonthName($aNbr) { 691 return $this->iShortMonth[$this->iLocale][$aNbr]; 692 } 693 694 function GetLongMonthName($aNbr) { 695 return $this->iMonthName[$this->iLocale][$aNbr]; 696 } 697 698 function GetMonth() { 699 return $this->iMonthName[$this->iLocale]; 700 } 701} 702 703$gDateLocale = new DateLocale(); 704$gJpgDateLocale = new DateLocale(); 705 706//======================================================= 707// CLASS Footer 708// Description: Encapsulates the footer line in the Graph 709//======================================================= 710class Footer { 711 public $iLeftMargin = 3, $iRightMargin = 3, $iBottomMargin = 3 ; 712 public $left,$center,$right; 713 714 function Footer() { 715 $this->left = new Text(); 716 $this->left->ParagraphAlign('left'); 717 $this->center = new Text(); 718 $this->center->ParagraphAlign('center'); 719 $this->right = new Text(); 720 $this->right->ParagraphAlign('right'); 721 } 722 723 function SetMargin($aLeft=3,$aRight=3,$aBottom=3) { 724 $this->iLeftMargin = $aLeft; 725 $this->iRightMargin = $aRight; 726 $this->iBottomMargin = $aBottom; 727 } 728 729 function Stroke($aImg) { 730 $y = $aImg->height - $this->iBottomMargin; 731 $x = $this->iLeftMargin; 732 $this->left->Align('left','bottom'); 733 $this->left->Stroke($aImg,$x,$y); 734 735 $x = ($aImg->width - $this->iLeftMargin - $this->iRightMargin)/2; 736 $this->center->Align('center','bottom'); 737 $this->center->Stroke($aImg,$x,$y); 738 739 $x = $aImg->width - $this->iRightMargin; 740 $this->right->Align('right','bottom'); 741 $this->right->Stroke($aImg,$x,$y); 742 } 743} 744 745 746//=================================================== 747// CLASS Graph 748// Description: Main class to handle graphs 749//=================================================== 750class Graph { 751 public $cache=null; // Cache object (singleton) 752 public $img=null; // Img object (singleton) 753 public $plots=array(); // Array of all plot object in the graph (for Y 1 axis) 754 public $y2plots=array();// Array of all plot object in the graph (for Y 2 axis) 755 public $ynplots=array(); 756 public $xscale=null; // X Scale object (could be instance of LinearScale or LogScale 757 public $yscale=null,$y2scale=null, $ynscale=array(); 758 public $iIcons = array(); // Array of Icons to add to 759 public $cache_name; // File name to be used for the current graph in the cache directory 760 public $xgrid=null; // X Grid object (linear or logarithmic) 761 public $ygrid=null,$y2grid=null; //dito for Y 762 public $doframe=true,$frame_color=array(0,0,0), $frame_weight=1; // Frame around graph 763 public $boxed=false, $box_color=array(0,0,0), $box_weight=1; // Box around plot area 764 public $doshadow=false,$shadow_width=4,$shadow_color=array(102,102,102); // Shadow for graph 765 public $xaxis=null; // X-axis (instane of Axis class) 766 public $yaxis=null, $y2axis=null, $ynaxis=array(); // Y axis (instance of Axis class) 767 public $margin_color=array(200,200,200); // Margin color of graph 768 public $plotarea_color=array(255,255,255); // Plot area color 769 public $title,$subtitle,$subsubtitle; // Title and subtitle(s) text object 770 public $axtype="linlin"; // Type of axis 771 public $xtick_factor; // Factot to determine the maximum number of ticks depending on the plot with 772 public $texts=null, $y2texts=null; // Text object to ge shown in the graph 773 public $lines=null, $y2lines=null; 774 public $bands=null, $y2bands=null; 775 public $text_scale_off=0, $text_scale_abscenteroff=-1; // Text scale in fractions and for centering bars 776 public $background_image="",$background_image_type=-1,$background_image_format="png"; 777 public $background_image_bright=0,$background_image_contr=0,$background_image_sat=0; 778 public $image_bright=0, $image_contr=0, $image_sat=0; 779 public $inline; 780 public $showcsim=0,$csimcolor="red"; //debug stuff, draw the csim boundaris on the image if <>0 781 public $grid_depth=DEPTH_BACK; // Draw grid under all plots as default 782 public $iAxisStyle = AXSTYLE_SIMPLE; 783 public $iCSIMdisplay=false,$iHasStroked = false; 784 public $footer; 785 public $csimcachename = '', $csimcachetimeout = 0; 786 public $iDoClipping = false; 787 public $y2orderback=true; 788 public $tabtitle; 789 public $bkg_gradtype=-1,$bkg_gradstyle=BGRAD_MARGIN; 790 public $bkg_gradfrom='navy', $bkg_gradto='silver'; 791 public $titlebackground = false; 792 public $titlebackground_color = 'lightblue', 793 $titlebackground_style = 1, 794 $titlebackground_framecolor = 'blue', 795 $titlebackground_framestyle = 2, 796 $titlebackground_frameweight = 1, 797 $titlebackground_bevelheight = 3 ; 798 public $titlebkg_fillstyle=TITLEBKG_FILLSTYLE_SOLID; 799 public $titlebkg_scolor1='black',$titlebkg_scolor2='white'; 800 public $framebevel = false, $framebeveldepth = 2 ; 801 public $framebevelborder = false, $framebevelbordercolor='black'; 802 public $framebevelcolor1='white@0.4', $framebevelcolor2='black@0.4'; 803 public $background_image_mix=100; 804 public $background_cflag = ''; 805 public $background_cflag_type = BGIMG_FILLPLOT; 806 public $background_cflag_mix = 100; 807 public $iImgTrans=false, 808 $iImgTransHorizon = 100,$iImgTransSkewDist=150, 809 $iImgTransDirection = 1, $iImgTransMinSize = true, 810 $iImgTransFillColor='white',$iImgTransHighQ=false, 811 $iImgTransBorder=false,$iImgTransHorizonPos=0.5; 812 protected $iYAxisDeltaPos=50; 813 protected $iIconDepth=DEPTH_BACK; 814 protected $iAxisLblBgType = 0, 815 $iXAxisLblBgFillColor = 'lightgray', $iXAxisLblBgColor = 'black', 816 $iYAxisLblBgFillColor = 'lightgray', $iYAxisLblBgColor = 'black'; 817 protected $iTables=NULL; 818 819//--------------- 820// CONSTRUCTOR 821 822 // aWIdth Width in pixels of image 823 // aHeight Height in pixels of image 824 // aCachedName Name for image file in cache directory 825 // aTimeOut Timeout in minutes for image in cache 826 // aInline If true the image is streamed back in the call to Stroke() 827 // If false the image is just created in the cache 828 function Graph($aWidth=300,$aHeight=200,$aCachedName="",$aTimeOut=0,$aInline=true) { 829 GLOBAL $gJpgBrandTiming; 830 // If timing is used create a new timing object 831 if( $gJpgBrandTiming ) { 832 global $tim; 833 $tim = new JpgTimer(); 834 $tim->Push(); 835 } 836 837 if( !is_numeric($aWidth) || !is_numeric($aHeight) ) { 838 JpGraphError::RaiseL(25008);//('Image width/height argument in Graph::Graph() must be numeric'); 839 } 840 841 // Automatically generate the image file name based on the name of the script that 842 // generates the graph 843 if( $aCachedName=="auto" ) 844 $aCachedName=GenImgName(); 845 846 // Should the image be streamed back to the browser or only to the cache? 847 $this->inline=$aInline; 848 849 $this->img = new RotImage($aWidth,$aHeight); 850 851 $this->cache = new ImgStreamCache($this->img); 852 $this->cache->SetTimeOut($aTimeOut); 853 854 $this->title = new Text(); 855 $this->title->ParagraphAlign('center'); 856 $this->title->SetFont(FF_FONT2,FS_BOLD); 857 $this->title->SetMargin(3); 858 $this->title->SetAlign('center'); 859 860 $this->subtitle = new Text(); 861 $this->subtitle->ParagraphAlign('center'); 862 $this->subtitle->SetMargin(2); 863 $this->subtitle->SetAlign('center'); 864 865 $this->subsubtitle = new Text(); 866 $this->subsubtitle->ParagraphAlign('center'); 867 $this->subsubtitle->SetMargin(2); 868 $this->subsubtitle->SetAlign('center'); 869 870 $this->legend = new Legend(); 871 $this->footer = new Footer(); 872 873 // Window doesn't like '?' in the file name so replace it with an '_' 874 $aCachedName = str_replace("?","_",$aCachedName); 875 876 // If the cached version exist just read it directly from the 877 // cache, stream it back to browser and exit 878 if( $aCachedName!="" && READ_CACHE && $aInline ) 879 if( $this->cache->GetAndStream($aCachedName) ) { 880 exit(); 881 } 882 883 $this->cache_name = $aCachedName; 884 $this->SetTickDensity(); // Normal density 885 886 $this->tabtitle = new GraphTabTitle(); 887 } 888//--------------- 889// PUBLIC METHODS 890 891 // Enable final image perspective transformation 892 function Set3DPerspective($aDir=1,$aHorizon=100,$aSkewDist=120,$aQuality=false,$aFillColor='#FFFFFF',$aBorder=false,$aMinSize=true,$aHorizonPos=0.5) { 893 $this->iImgTrans = true; 894 $this->iImgTransHorizon = $aHorizon; 895 $this->iImgTransSkewDist= $aSkewDist; 896 $this->iImgTransDirection = $aDir; 897 $this->iImgTransMinSize = $aMinSize; 898 $this->iImgTransFillColor=$aFillColor; 899 $this->iImgTransHighQ=$aQuality; 900 $this->iImgTransBorder=$aBorder; 901 $this->iImgTransHorizonPos=$aHorizonPos; 902 } 903 904 // Set Image format and optional quality 905 function SetImgFormat($aFormat,$aQuality=75) { 906 $this->img->SetImgFormat($aFormat,$aQuality); 907 } 908 909 // Should the grid be in front or back of the plot? 910 function SetGridDepth($aDepth) { 911 $this->grid_depth=$aDepth; 912 } 913 914 function SetIconDepth($aDepth) { 915 $this->iIconDepth=$aDepth; 916 } 917 918 // Specify graph angle 0-360 degrees. 919 function SetAngle($aAngle) { 920 $this->img->SetAngle($aAngle); 921 } 922 923 function SetAlphaBlending($aFlg=true) { 924 $this->img->SetAlphaBlending($aFlg); 925 } 926 927 // Shortcut to image margin 928 function SetMargin($lm,$rm,$tm,$bm) { 929 $this->img->SetMargin($lm,$rm,$tm,$bm); 930 } 931 932 function SetY2OrderBack($aBack=true) { 933 $this->y2orderback = $aBack; 934 } 935 936 // Rotate the graph 90 degrees and set the margin 937 // when we have done a 90 degree rotation 938 function Set90AndMargin($lm=0,$rm=0,$tm=0,$bm=0) { 939 $lm = $lm ==0 ? floor(0.2 * $this->img->width) : $lm ; 940 $rm = $rm ==0 ? floor(0.1 * $this->img->width) : $rm ; 941 $tm = $tm ==0 ? floor(0.2 * $this->img->height) : $tm ; 942 $bm = $bm ==0 ? floor(0.1 * $this->img->height) : $bm ; 943 944 $adj = ($this->img->height - $this->img->width)/2; 945 $this->img->SetMargin($tm-$adj,$bm-$adj,$rm+$adj,$lm+$adj); 946 $this->img->SetCenter(floor($this->img->width/2),floor($this->img->height/2)); 947 $this->SetAngle(90); 948 if( empty($this->yaxis) || empty($this->xaxis) ) { 949 JpgraphError::RaiseL(25009);//('You must specify what scale to use with a call to Graph::SetScale()'); 950 } 951 $this->xaxis->SetLabelAlign('right','center'); 952 $this->yaxis->SetLabelAlign('center','bottom'); 953 } 954 955 function SetClipping($aFlg=true) { 956 $this->iDoClipping = $aFlg ; 957 } 958 959 // Add a plot object to the graph 960 function Add($aPlot) { 961 if( $aPlot == null ) 962 JpGraphError::RaiseL(25010);//("Graph::Add() You tried to add a null plot to the graph."); 963 if( is_array($aPlot) && count($aPlot) > 0 ) 964 $cl = $aPlot[0]; 965 else 966 $cl = $aPlot; 967 968 if( $cl instanceof Text ) 969 $this->AddText($aPlot); 970 elseif( $cl instanceof PlotLine ) 971 $this->AddLine($aPlot); 972 elseif( class_exists('PlotBand',false) && ($cl instanceof PlotBand) ) 973 $this->AddBand($aPlot); 974 elseif( class_exists('IconPlot',false) && ($cl instanceof IconPlot) ) 975 $this->AddIcon($aPlot); 976 elseif( class_exists('GTextTable',false) && ($cl instanceof GTextTable) ) 977 $this->AddTable($aPlot); 978 else 979 $this->plots[] = $aPlot; 980 } 981 982 function AddTable($aTable) { 983 if( is_array($aTable) ) { 984 for($i=0; $i < count($aTable); ++$i ) 985 $this->iTables[]=$aTable[$i]; 986 } 987 else { 988 $this->iTables[] = $aTable ; 989 } 990 } 991 992 function AddIcon($aIcon) { 993 if( is_array($aIcon) ) { 994 for($i=0; $i < count($aIcon); ++$i ) 995 $this->iIcons[]=$aIcon[$i]; 996 } 997 else { 998 $this->iIcons[] = $aIcon ; 999 } 1000 } 1001 1002 // Add plot to second Y-scale 1003 function AddY2($aPlot) { 1004 if( $aPlot == null ) 1005 JpGraphError::RaiseL(25011);//("Graph::AddY2() You tried to add a null plot to the graph."); 1006 1007 if( is_array($aPlot) && count($aPlot) > 0 ) 1008 $cl = $aPlot[0]; 1009 else 1010 $cl = $aPlot; 1011 1012 if( $cl instanceof Text ) 1013 $this->AddText($aPlot,true); 1014 elseif( $cl instanceof PlotLine ) 1015 $this->AddLine($aPlot,true); 1016 elseif( class_exists('PlotBand',false) && ($cl instanceof PlotBand) ) 1017 $this->AddBand($aPlot,true); 1018 else 1019 $this->y2plots[] = $aPlot; 1020 } 1021 1022 // Add plot to the extra Y-axises 1023 function AddY($aN,$aPlot) { 1024 1025 if( $aPlot == null ) 1026 JpGraphError::RaiseL(25012);//("Graph::AddYN() You tried to add a null plot to the graph."); 1027 1028 if( is_array($aPlot) && count($aPlot) > 0 ) 1029 $cl = $aPlot[0]; 1030 else 1031 $cl = $aPlot; 1032 1033 if( ($cl instanceof Text) || ($cl instanceof PlotLine) || 1034 (class_exists('PlotBand',false) && ($cl instanceof PlotBand)) ) 1035 JpGraph::RaiseL(25013);//('You can only add standard plots to multiple Y-axis'); 1036 else 1037 $this->ynplots[$aN][] = $aPlot; 1038 } 1039 1040 // Add text object to the graph 1041 function AddText($aTxt,$aToY2=false) { 1042 if( $aTxt == null ) 1043 JpGraphError::RaiseL(25014);//("Graph::AddText() You tried to add a null text to the graph."); 1044 if( $aToY2 ) { 1045 if( is_array($aTxt) ) { 1046 for($i=0; $i < count($aTxt); ++$i ) 1047 $this->y2texts[]=$aTxt[$i]; 1048 } 1049 else 1050 $this->y2texts[] = $aTxt; 1051 } 1052 else { 1053 if( is_array($aTxt) ) { 1054 for($i=0; $i < count($aTxt); ++$i ) 1055 $this->texts[]=$aTxt[$i]; 1056 } 1057 else 1058 $this->texts[] = $aTxt; 1059 } 1060 } 1061 1062 // Add a line object (class PlotLine) to the graph 1063 function AddLine($aLine,$aToY2=false) { 1064 if( $aLine == null ) 1065 JpGraphError::RaiseL(25015);//("Graph::AddLine() You tried to add a null line to the graph."); 1066 1067 if( $aToY2 ) { 1068 if( is_array($aLine) ) { 1069 for($i=0; $i < count($aLine); ++$i ) 1070 $this->y2lines[]=$aLine[$i]; 1071 } 1072 else 1073 $this->y2lines[] = $aLine; 1074 } 1075 else { 1076 if( is_array($aLine) ) { 1077 for($i=0; $i<count($aLine); ++$i ) 1078 $this->lines[]=$aLine[$i]; 1079 } 1080 else 1081 $this->lines[] = $aLine; 1082 } 1083 } 1084 1085 // Add vertical or horizontal band 1086 function AddBand($aBand,$aToY2=false) { 1087 if( $aBand == null ) 1088 JpGraphError::RaiseL(25016);//(" Graph::AddBand() You tried to add a null band to the graph."); 1089 1090 if( $aToY2 ) { 1091 if( is_array($aBand) ) { 1092 for($i=0; $i < count($aBand); ++$i ) 1093 $this->y2bands[] = $aBand[$i]; 1094 } 1095 else 1096 $this->y2bands[] = $aBand; 1097 } 1098 else { 1099 if( is_array($aBand) ) { 1100 for($i=0; $i < count($aBand); ++$i ) 1101 $this->bands[] = $aBand[$i]; 1102 } 1103 else 1104 $this->bands[] = $aBand; 1105 } 1106 } 1107 1108 function SetBackgroundGradient($aFrom='navy',$aTo='silver',$aGradType=2,$aStyle=BGRAD_FRAME) { 1109 $this->bkg_gradtype=$aGradType; 1110 $this->bkg_gradstyle=$aStyle; 1111 $this->bkg_gradfrom = $aFrom; 1112 $this->bkg_gradto = $aTo; 1113 } 1114 1115 // Set a country flag in the background 1116 function SetBackgroundCFlag($aName,$aBgType=BGIMG_FILLPLOT,$aMix=100) { 1117 $this->background_cflag = $aName; 1118 $this->background_cflag_type = $aBgType; 1119 $this->background_cflag_mix = $aMix; 1120 } 1121 1122 // Alias for the above method 1123 function SetBackgroundCountryFlag($aName,$aBgType=BGIMG_FILLPLOT,$aMix=100) { 1124 $this->background_cflag = $aName; 1125 $this->background_cflag_type = $aBgType; 1126 $this->background_cflag_mix = $aMix; 1127 } 1128 1129 1130 // Specify a background image 1131 function SetBackgroundImage($aFileName,$aBgType=BGIMG_FILLPLOT,$aImgFormat="auto") { 1132 1133 if( !USE_TRUECOLOR ) { 1134 JpGraphError::RaiseL(25017);//("You are using GD 2.x and are trying to use a background images on a non truecolor image. To use background images with GD 2.x you <b>must</b> enable truecolor by setting the USE_TRUECOLOR constant to TRUE. Due to a bug in GD 2.0.1 using any truetype fonts with truecolor images will result in very poor quality fonts."); 1135 } 1136 1137 // Get extension to determine image type 1138 if( $aImgFormat == "auto" ) { 1139 $e = explode('.',$aFileName); 1140 if( !$e ) { 1141 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'); 1142 } 1143 1144 $valid_formats = array('png', 'jpg', 'gif'); 1145 $aImgFormat = strtolower($e[count($e)-1]); 1146 if ($aImgFormat == 'jpeg') { 1147 $aImgFormat = 'jpg'; 1148 } 1149 elseif (!in_array($aImgFormat, $valid_formats) ) { 1150 JpGraphError::RaiseL(25019,$aImgFormat);//('Unknown file extension ($aImgFormat) in Graph::SetBackgroundImage() for filename: '.$aFileName); 1151 } 1152 } 1153 1154 $this->background_image = $aFileName; 1155 $this->background_image_type=$aBgType; 1156 $this->background_image_format=$aImgFormat; 1157 } 1158 1159 function SetBackgroundImageMix($aMix) { 1160 $this->background_image_mix = $aMix ; 1161 } 1162 1163 // Adjust brightness and constrast for background image 1164 function AdjBackgroundImage($aBright,$aContr=0,$aSat=0) { 1165 $this->background_image_bright=$aBright; 1166 $this->background_image_contr=$aContr; 1167 $this->background_image_sat=$aSat; 1168 } 1169 1170 // Adjust brightness and constrast for image 1171 function AdjImage($aBright,$aContr=0,$aSat=0) { 1172 $this->image_bright=$aBright; 1173 $this->image_contr=$aContr; 1174 $this->image_sat=$aSat; 1175 } 1176 1177 // Specify axis style (boxed or single) 1178 function SetAxisStyle($aStyle) { 1179 $this->iAxisStyle = $aStyle ; 1180 } 1181 1182 // Set a frame around the plot area 1183 function SetBox($aDrawPlotFrame=true,$aPlotFrameColor=array(0,0,0),$aPlotFrameWeight=1) { 1184 $this->boxed = $aDrawPlotFrame; 1185 $this->box_weight = $aPlotFrameWeight; 1186 $this->box_color = $aPlotFrameColor; 1187 } 1188 1189 // Specify color for the plotarea (not the margins) 1190 function SetColor($aColor) { 1191 $this->plotarea_color=$aColor; 1192 } 1193 1194 // Specify color for the margins (all areas outside the plotarea) 1195 function SetMarginColor($aColor) { 1196 $this->margin_color=$aColor; 1197 } 1198 1199 // Set a frame around the entire image 1200 function SetFrame($aDrawImgFrame=true,$aImgFrameColor=array(0,0,0),$aImgFrameWeight=1) { 1201 $this->doframe = $aDrawImgFrame; 1202 $this->frame_color = $aImgFrameColor; 1203 $this->frame_weight = $aImgFrameWeight; 1204 } 1205 1206 function SetFrameBevel($aDepth=3,$aBorder=false,$aBorderColor='black',$aColor1='white@0.4',$aColor2='darkgray@0.4',$aFlg=true) { 1207 $this->framebevel = $aFlg ; 1208 $this->framebeveldepth = $aDepth ; 1209 $this->framebevelborder = $aBorder ; 1210 $this->framebevelbordercolor = $aBorderColor ; 1211 $this->framebevelcolor1 = $aColor1 ; 1212 $this->framebevelcolor2 = $aColor2 ; 1213 1214 $this->doshadow = false ; 1215 } 1216 1217 // Set the shadow around the whole image 1218 function SetShadow($aShowShadow=true,$aShadowWidth=5,$aShadowColor=array(102,102,102)) { 1219 $this->doshadow = $aShowShadow; 1220 $this->shadow_color = $aShadowColor; 1221 $this->shadow_width = $aShadowWidth; 1222 $this->footer->iBottomMargin += $aShadowWidth; 1223 $this->footer->iRightMargin += $aShadowWidth; 1224 } 1225 1226 // Specify x,y scale. Note that if you manually specify the scale 1227 // you must also specify the tick distance with a call to Ticks::Set() 1228 function SetScale($aAxisType,$aYMin=1,$aYMax=1,$aXMin=1,$aXMax=1) { 1229 $this->axtype = $aAxisType; 1230 1231 if( $aYMax < $aYMin || $aXMax < $aXMin ) 1232 JpGraphError::RaiseL(25020);//('Graph::SetScale(): Specified Max value must be larger than the specified Min value.'); 1233 1234 $yt=substr($aAxisType,-3,3); 1235 if( $yt=="lin" ) 1236 $this->yscale = new LinearScale($aYMin,$aYMax); 1237 elseif( $yt == "int" ) { 1238 $this->yscale = new LinearScale($aYMin,$aYMax); 1239 $this->yscale->SetIntScale(); 1240 } 1241 elseif( $yt=="log" ) 1242 $this->yscale = new LogScale($aYMin,$aYMax); 1243 else 1244 JpGraphError::RaiseL(25021,$aAxisType);//("Unknown scale specification for Y-scale. ($aAxisType)"); 1245 1246 $xt=substr($aAxisType,0,3); 1247 if( $xt == "lin" || $xt == "tex" ) { 1248 $this->xscale = new LinearScale($aXMin,$aXMax,"x"); 1249 $this->xscale->textscale = ($xt == "tex"); 1250 } 1251 elseif( $xt == "int" ) { 1252 $this->xscale = new LinearScale($aXMin,$aXMax,"x"); 1253 $this->xscale->SetIntScale(); 1254 } 1255 elseif( $xt == "dat" ) { 1256 $this->xscale = new DateScale($aXMin,$aXMax,"x"); 1257 } 1258 elseif( $xt == "log" ) 1259 $this->xscale = new LogScale($aXMin,$aXMax,"x"); 1260 else 1261 JpGraphError::RaiseL(25022,$aAxisType);//(" Unknown scale specification for X-scale. ($aAxisType)"); 1262 1263 $this->xaxis = new Axis($this->img,$this->xscale); 1264 $this->yaxis = new Axis($this->img,$this->yscale); 1265 $this->xgrid = new Grid($this->xaxis); 1266 $this->ygrid = new Grid($this->yaxis); 1267 $this->ygrid->Show(); 1268 } 1269 1270 // Specify secondary Y scale 1271 function SetY2Scale($aAxisType="lin",$aY2Min=1,$aY2Max=1) { 1272 if( $aAxisType=="lin" ) 1273 $this->y2scale = new LinearScale($aY2Min,$aY2Max); 1274 elseif( $aAxisType == "int" ) { 1275 $this->y2scale = new LinearScale($aY2Min,$aY2Max); 1276 $this->y2scale->SetIntScale(); 1277 } 1278 elseif( $aAxisType=="log" ) { 1279 $this->y2scale = new LogScale($aY2Min,$aY2Max); 1280 } 1281 else JpGraphError::RaiseL(25023,$aAxisType);//("JpGraph: Unsupported Y2 axis type: $aAxisType\nMust be one of (lin,log,int)"); 1282 1283 $this->y2axis = new Axis($this->img,$this->y2scale); 1284 $this->y2axis->scale->ticks->SetDirection(SIDE_LEFT); 1285 $this->y2axis->SetLabelSide(SIDE_RIGHT); 1286 $this->y2axis->SetPos('max'); 1287 $this->y2axis->SetTitleSide(SIDE_RIGHT); 1288 1289 // Deafult position is the max x-value 1290 $this->y2grid = new Grid($this->y2axis); 1291 } 1292 1293 // Set the delta position (in pixels) between the multiple Y-axis 1294 function SetYDeltaDist($aDist) { 1295 $this->iYAxisDeltaPos = $aDist; 1296 } 1297 1298 // Specify secondary Y scale 1299 function SetYScale($aN,$aAxisType="lin",$aYMin=1,$aYMax=1) { 1300 1301 if( $aAxisType=="lin" ) 1302 $this->ynscale[$aN] = new LinearScale($aYMin,$aYMax); 1303 elseif( $aAxisType == "int" ) { 1304 $this->ynscale[$aN] = new LinearScale($aYMin,$aYMax); 1305 $this->ynscale[$aN]->SetIntScale(); 1306 } 1307 elseif( $aAxisType=="log" ) { 1308 $this->ynscale[$aN] = new LogScale($aYMin,$aYMax); 1309 } 1310 else JpGraphError::RaiseL(25024,$aAxisType);//("JpGraph: Unsupported Y axis type: $aAxisType\nMust be one of (lin,log,int)"); 1311 1312 $this->ynaxis[$aN] = new Axis($this->img,$this->ynscale[$aN]); 1313 $this->ynaxis[$aN]->scale->ticks->SetDirection(SIDE_LEFT); 1314 $this->ynaxis[$aN]->SetLabelSide(SIDE_RIGHT); 1315 } 1316 1317 // Specify density of ticks when autoscaling 'normal', 'dense', 'sparse', 'verysparse' 1318 // The dividing factor have been determined heuristically according to my aesthetic 1319 // sense (or lack off) y.m.m.v ! 1320 function SetTickDensity($aYDensity=TICKD_NORMAL,$aXDensity=TICKD_NORMAL) { 1321 $this->xtick_factor=30; 1322 $this->ytick_factor=25; 1323 switch( $aYDensity ) { 1324 case TICKD_DENSE: 1325 $this->ytick_factor=12; 1326 break; 1327 case TICKD_NORMAL: 1328 $this->ytick_factor=25; 1329 break; 1330 case TICKD_SPARSE: 1331 $this->ytick_factor=40; 1332 break; 1333 case TICKD_VERYSPARSE: 1334 $this->ytick_factor=100; 1335 break; 1336 default: 1337 JpGraphError::RaiseL(25025,$densy);//("JpGraph: Unsupported Tick density: $densy"); 1338 } 1339 switch( $aXDensity ) { 1340 case TICKD_DENSE: 1341 $this->xtick_factor=15; 1342 break; 1343 case TICKD_NORMAL: 1344 $this->xtick_factor=30; 1345 break; 1346 case TICKD_SPARSE: 1347 $this->xtick_factor=45; 1348 break; 1349 case TICKD_VERYSPARSE: 1350 $this->xtick_factor=60; 1351 break; 1352 default: 1353 JpGraphError::RaiseL(25025,$densx);//("JpGraph: Unsupported Tick density: $densx"); 1354 } 1355 } 1356 1357 1358 // Get a string of all image map areas 1359 function GetCSIMareas() { 1360 if( !$this->iHasStroked ) 1361 $this->Stroke(_CSIM_SPECIALFILE); 1362 1363 $csim = $this->title->GetCSIMAreas(); 1364 $csim .= $this->subtitle->GetCSIMAreas(); 1365 $csim .= $this->subsubtitle->GetCSIMAreas(); 1366 $csim .= $this->legend->GetCSIMAreas(); 1367 1368 if( $this->y2axis != NULL ) { 1369 $csim .= $this->y2axis->title->GetCSIMAreas(); 1370 } 1371 1372 if( $this->texts != null ) { 1373 $n = count($this->texts); 1374 for($i=0; $i < $n; ++$i ) { 1375 $csim .= $this->texts[$i]->GetCSIMAreas(); 1376 } 1377 } 1378 1379 if( $this->y2texts != null && $this->y2scale != null ) { 1380 $n = count($this->y2texts); 1381 for($i=0; $i < $n; ++$i ) { 1382 $csim .= $this->y2texts[$i]->GetCSIMAreas(); 1383 } 1384 } 1385 1386 if( $this->yaxis != null && $this->xaxis != null ) { 1387 $csim .= $this->yaxis->title->GetCSIMAreas(); 1388 $csim .= $this->xaxis->title->GetCSIMAreas(); 1389 } 1390 1391 $n = count($this->plots); 1392 for( $i=0; $i < $n; ++$i ) 1393 $csim .= $this->plots[$i]->GetCSIMareas(); 1394 1395 $n = count($this->y2plots); 1396 for( $i=0; $i < $n; ++$i ) 1397 $csim .= $this->y2plots[$i]->GetCSIMareas(); 1398 1399 $n = count($this->ynaxis); 1400 for( $i=0; $i < $n; ++$i ) { 1401 $m = count($this->ynplots[$i]); 1402 for($j=0; $j < $m; ++$j ) { 1403 $csim .= $this->ynplots[$i][$j]->GetCSIMareas(); 1404 } 1405 } 1406 1407 $n = count($this->iTables); 1408 for( $i=0; $i < $n; ++$i ) { 1409 $csim .= $this->iTables[$i]->GetCSIMareas(); 1410 } 1411 1412 return $csim; 1413 } 1414 1415 // Get a complete <MAP>..</MAP> tag for the final image map 1416 function GetHTMLImageMap($aMapName) { 1417 //$im = "<map name=\"$aMapName\" id=\"$aMapName\">\n"; 1418 $im = "<map name=\"$aMapName\" />\n"; 1419 $im .= $this->GetCSIMareas(); 1420 $im .= "</map>"; 1421 return $im; 1422 } 1423 1424 function CheckCSIMCache($aCacheName,$aTimeOut=60) { 1425 global $_SERVER; 1426 1427 if( $aCacheName=='auto' ) 1428 $aCacheName=basename($_SERVER['PHP_SELF']); 1429 1430 $this->csimcachename = CSIMCACHE_DIR.$aCacheName; 1431 $this->csimcachetimeout = $aTimeOut; 1432 1433 // First determine if we need to check for a cached version 1434 // This differs from the standard cache in the sense that the 1435 // image and CSIM map HTML file is written relative to the directory 1436 // the script executes in and not the specified cache directory. 1437 // The reason for this is that the cache directory is not necessarily 1438 // accessible from the HTTP server. 1439 if( $this->csimcachename != '' ) { 1440 $dir = dirname($this->csimcachename); 1441 $base = basename($this->csimcachename); 1442 $base = strtok($base,'.'); 1443 $suffix = strtok('.'); 1444 $basecsim = $dir.'/'.$base.'_csim_.html'; 1445 $baseimg = $dir.'/'.$base.'.'.$this->img->img_format; 1446 1447 $timedout=false; 1448 1449 // Does it exist at all ? 1450 1451 if( file_exists($basecsim) && file_exists($baseimg) ) { 1452 // Check that it hasn't timed out 1453 $diff=time()-filemtime($basecsim); 1454 if( $this->csimcachetimeout>0 && ($diff > $this->csimcachetimeout*60) ) { 1455 $timedout=true; 1456 @unlink($basecsim); 1457 @unlink($baseimg); 1458 } 1459 else { 1460 if ($fh = @fopen($basecsim, "r")) { 1461 fpassthru($fh); 1462 return true; 1463 } 1464 else 1465 JpGraphError::RaiseL(25027,$basecsim);//(" Can't open cached CSIM \"$basecsim\" for reading."); 1466 } 1467 } 1468 } 1469 return false; 1470 } 1471 1472 function StrokeCSIM($aScriptName='auto',$aCSIMName='',$aBorder=0) { 1473 if( $aCSIMName=='' ) { 1474 // create a random map name 1475 srand ((double) microtime() * 1000000); 1476 $r = rand(0,100000); 1477 $aCSIMName='__mapname'.$r.'__'; 1478 } 1479 1480 if( $aScriptName=='auto' ) 1481 $aScriptName=basename($_SERVER['PHP_SELF']); 1482 1483 if( empty($_GET[_CSIM_DISPLAY]) ) { 1484 // First determine if we need to check for a cached version 1485 // This differs from the standard cache in the sense that the 1486 // image and CSIM map HTML file is written relative to the directory 1487 // the script executes in and not the specified cache directory. 1488 // The reason for this is that the cache directory is not necessarily 1489 // accessible from the HTTP server. 1490 if( $this->csimcachename != '' ) { 1491 $dir = dirname($this->csimcachename); 1492 $base = basename($this->csimcachename); 1493 $base = strtok($base,'.'); 1494 $suffix = strtok('.'); 1495 $basecsim = $dir.'/'.$base.'_csim_.html'; 1496 $baseimg = $base.'.'.$this->img->img_format; 1497 1498 // Check that apache can write to directory specified 1499 1500 if( file_exists($dir) && !is_writeable($dir) ) { 1501 JpgraphError::RaiseL(25028,$dir);//('Apache/PHP does not have permission to write to the CSIM cache directory ('.$dir.'). Check permissions.'); 1502 } 1503 1504 // Make sure directory exists 1505 $this->cache->MakeDirs($dir); 1506 1507 // Write the image file 1508 $this->Stroke(CSIMCACHE_DIR.$baseimg); 1509 1510 // Construct wrapper HTML and write to file and send it back to browser 1511 $htmlwrap = $this->GetHTMLImageMap($aCSIMName)."\n". 1512 '<img src="'.htmlentities(CSIMCACHE_HTTP_DIR.$baseimg).'" ismap usemap="#'.$aCSIMName.'" border='.$aBorder.' width='.$this->img->width.' height='.$this->img->height." alt=\"\" />\n"; 1513 if($fh = @fopen($basecsim,'w') ) { 1514 fwrite($fh,$htmlwrap); 1515 fclose($fh); 1516 echo $htmlwrap; 1517 } 1518 else 1519 JpGraphError::RaiseL(25029,$basecsim);//(" Can't write CSIM \"$basecsim\" for writing. Check free space and permissions."); 1520 } 1521 else { 1522 1523 if( $aScriptName=='' ) { 1524 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().'); 1525 exit(); 1526 } 1527 1528 1529 // This is a JPGRAPH internal defined that prevents 1530 // us from recursively coming here again 1531 $urlarg='?'._CSIM_DISPLAY.'=1'; 1532 1533 // Now reconstruct any user URL argument 1534 reset($_GET); 1535 while( list($key,$value) = each($_GET) ) { 1536 if( is_array($value) ) { 1537 $n = count($value); 1538 for( $i=0; $i < $n; ++$i ) { 1539 $urlarg .= '&'.$key.'%5B%5D='.urlencode($value[$i]); 1540 } 1541 } 1542 else { 1543 $urlarg .= '&'.$key.'='.urlencode($value); 1544 } 1545 } 1546 1547 // It's not ideal to convert POST argument to GET arguments 1548 // but there is little else we can do. One idea for the 1549 // future might be recreate the POST header in case. 1550 reset($_POST); 1551 while( list($key,$value) = each($_POST) ) { 1552 if( is_array($value) ) { 1553 $n = count($value); 1554 for( $i=0; $i < $n; ++$i ) { 1555 $urlarg .= '&'.$key.'%5B%5D='.urlencode($value[$i]); 1556 } 1557 } 1558 else { 1559 $urlarg .= '&'.$key.'='.urlencode($value); 1560 } 1561 } 1562 1563 echo $this->GetHTMLImageMap($aCSIMName); 1564 1565 echo "<img src='".htmlentities($aScriptName.$urlarg)."' ISMAP USEMAP='#".$aCSIMName.'\' border='.$aBorder.' width='.$this->img->width.' height='.$this->img->height." alt=\"\" />\n"; 1566 } 1567 } 1568 else { 1569 $this->Stroke(); 1570 } 1571 } 1572 1573 function GetTextsYMinMax($aY2=false) { 1574 if( $aY2 ) 1575 $txts = $this->y2texts; 1576 else 1577 $txts = $this->texts; 1578 $n = count($txts); 1579 $min=null; 1580 $max=null; 1581 for( $i=0; $i < $n; ++$i ) { 1582 if( $txts[$i]->iScalePosY !== null && 1583 $txts[$i]->iScalePosX !== null ) { 1584 if( $min === null ) { 1585 $min = $max = $txts[$i]->iScalePosY ; 1586 } 1587 else { 1588 $min = min($min,$txts[$i]->iScalePosY); 1589 $max = max($max,$txts[$i]->iScalePosY); 1590 } 1591 } 1592 } 1593 if( $min !== null ) { 1594 return array($min,$max); 1595 } 1596 else 1597 return null; 1598 } 1599 1600 function GetTextsXMinMax($aY2=false) { 1601 if( $aY2 ) 1602 $txts = $this->y2texts; 1603 else 1604 $txts = $this->texts; 1605 $n = count($txts); 1606 $min=null; 1607 $max=null; 1608 for( $i=0; $i < $n; ++$i ) { 1609 if( $txts[$i]->iScalePosY !== null && 1610 $txts[$i]->iScalePosX !== null ) { 1611 if( $min === null ) { 1612 $min = $max = $txts[$i]->iScalePosX ; 1613 } 1614 else { 1615 $min = min($min,$txts[$i]->iScalePosX); 1616 $max = max($max,$txts[$i]->iScalePosX); 1617 } 1618 } 1619 } 1620 if( $min !== null ) { 1621 return array($min,$max); 1622 } 1623 else 1624 return null; 1625 } 1626 1627 function GetXMinMax() { 1628 list($min,$ymin) = $this->plots[0]->Min(); 1629 list($max,$ymax) = $this->plots[0]->Max(); 1630 foreach( $this->plots as $p ) { 1631 list($xmin,$ymin) = $p->Min(); 1632 list($xmax,$ymax) = $p->Max(); 1633 $min = Min($xmin,$min); 1634 $max = Max($xmax,$max); 1635 } 1636 if( $this->y2axis != null ) { 1637 foreach( $this->y2plots as $p ) { 1638 list($xmin,$ymin) = $p->Min(); 1639 list($xmax,$ymax) = $p->Max(); 1640 $min = Min($xmin,$min); 1641 $max = Max($xmax,$max); 1642 } 1643 } 1644 1645 $n = count($this->ynaxis); 1646 for( $i=0; $i < $n; ++$i ) { 1647 if( $this->ynaxis[$i] != null) { 1648 foreach( $this->ynplots[$i] as $p ) { 1649 list($xmin,$ymin) = $p->Min(); 1650 list($xmax,$ymax) = $p->Max(); 1651 $min = Min($xmin,$min); 1652 $max = Max($xmax,$max); 1653 } 1654 } 1655 } 1656 return array($min,$max); 1657 } 1658 1659 function AdjustMarginsForTitles() { 1660 $totrequired = 1661 ($this->title->t != '' ? 1662 $this->title->GetTextHeight($this->img) + $this->title->margin + 5 : 0 ) + 1663 ($this->subtitle->t != '' ? 1664 $this->subtitle->GetTextHeight($this->img) + $this->subtitle->margin + 5 : 0 ) + 1665 ($this->subsubtitle->t != '' ? 1666 $this->subsubtitle->GetTextHeight($this->img) + $this->subsubtitle->margin + 5 : 0 ) ; 1667 1668 1669 $btotrequired = 0; 1670 if($this->xaxis != null && !$this->xaxis->hide && !$this->xaxis->hide_labels ) { 1671 // Minimum bottom margin 1672 if( $this->xaxis->title->t != '' ) { 1673 if( $this->img->a == 90 ) 1674 $btotrequired = $this->yaxis->title->GetTextHeight($this->img) + 5 ; 1675 else 1676 $btotrequired = $this->xaxis->title->GetTextHeight($this->img) + 5 ; 1677 } 1678 else 1679 $btotrequired = 0; 1680 1681 if( $this->img->a == 90 ) { 1682 $this->img->SetFont($this->yaxis->font_family,$this->yaxis->font_style, 1683 $this->yaxis->font_size); 1684 $lh = $this->img->GetTextHeight('Mg',$this->yaxis->label_angle); 1685 } 1686 else { 1687 $this->img->SetFont($this->xaxis->font_family,$this->xaxis->font_style, 1688 $this->xaxis->font_size); 1689 $lh = $this->img->GetTextHeight('Mg',$this->xaxis->label_angle); 1690 } 1691 1692 $btotrequired += $lh + 5; 1693 } 1694 1695 if( $this->img->a == 90 ) { 1696 // DO Nothing. It gets too messy to do this properly for 90 deg... 1697 } 1698 else{ 1699 if( $this->img->top_margin < $totrequired ) { 1700 $this->SetMargin($this->img->left_margin,$this->img->right_margin, 1701 $totrequired,$this->img->bottom_margin); 1702 } 1703 if( $this->img->bottom_margin < $btotrequired ) { 1704 $this->SetMargin($this->img->left_margin,$this->img->right_margin, 1705 $this->img->top_margin,$btotrequired); 1706 } 1707 } 1708 } 1709 1710 // Stroke the graph 1711 // $aStrokeFileName If != "" the image will be written to this file and NOT 1712 // streamed back to the browser 1713 function Stroke($aStrokeFileName="") { 1714 1715 // Fist make a sanity check that user has specified a scale 1716 if( empty($this->yscale) ) { 1717 JpGraphError::RaiseL(25031);//('You must specify what scale to use with a call to Graph::SetScale().'); 1718 } 1719 1720 // Start by adjusting the margin so that potential titles will fit. 1721 $this->AdjustMarginsForTitles(); 1722 1723 // Setup scale constants 1724 if( $this->yscale ) $this->yscale->InitConstants($this->img); 1725 if( $this->xscale ) $this->xscale->InitConstants($this->img); 1726 if( $this->y2scale ) $this->y2scale->InitConstants($this->img); 1727 1728 $n=count($this->ynscale); 1729 for($i=0; $i < $n; ++$i) { 1730 if( $this->ynscale[$i] ) $this->ynscale[$i]->InitConstants($this->img); 1731 } 1732 1733 // If the filename is the predefined value = '_csim_special_' 1734 // we assume that the call to stroke only needs to do enough 1735 // to correctly generate the CSIM maps. 1736 // We use this variable to skip things we don't strictly need 1737 // to do to generate the image map to improve performance 1738 // a best we can. Therefor you will see a lot of tests !$_csim in the 1739 // code below. 1740 $_csim = ($aStrokeFileName===_CSIM_SPECIALFILE); 1741 1742 // We need to know if we have stroked the plot in the 1743 // GetCSIMareas. Otherwise the CSIM hasn't been generated 1744 // and in the case of GetCSIM called before stroke to generate 1745 // CSIM without storing an image to disk GetCSIM must call Stroke. 1746 $this->iHasStroked = true; 1747 1748 // Do any pre-stroke adjustment that is needed by the different plot types 1749 // (i.e bar plots want's to add an offset to the x-labels etc) 1750 for($i=0; $i < count($this->plots) ; ++$i ) { 1751 $this->plots[$i]->PreStrokeAdjust($this); 1752 $this->plots[$i]->DoLegend($this); 1753 } 1754 1755 // Any plots on the second Y scale? 1756 if( $this->y2scale != null ) { 1757 for($i=0; $i<count($this->y2plots) ; ++$i ) { 1758 $this->y2plots[$i]->PreStrokeAdjust($this); 1759 $this->y2plots[$i]->DoLegend($this); 1760 } 1761 } 1762 1763 // Any plots on the extra Y axises? 1764 $n = count($this->ynaxis); 1765 for($i=0; $i<$n ; ++$i ) { 1766 if( $this->ynplots == null || $this->ynplots[$i] == null) { 1767 JpGraphError::RaiseL(25032,$i);//("No plots for Y-axis nbr:$i"); 1768 } 1769 $m = count($this->ynplots[$i]); 1770 for($j=0; $j < $m; ++$j ) { 1771 $this->ynplots[$i][$j]->PreStrokeAdjust($this); 1772 $this->ynplots[$i][$j]->DoLegend($this); 1773 } 1774 } 1775 1776 // Bail out if any of the Y-axis not been specified and 1777 // has no plots. (This means it is impossible to do autoscaling and 1778 // no other scale was given so we can't possible draw anything). If you use manual 1779 // scaling you also have to supply the tick steps as well. 1780 if( (!$this->yscale->IsSpecified() && count($this->plots)==0) || 1781 ($this->y2scale!=null && !$this->y2scale->IsSpecified() && count($this->y2plots)==0) ) { 1782 //$e = "n=".count($this->y2plots)."\n"; 1783 // $e = "Can't draw unspecified Y-scale.<br>\nYou have either:<br>\n"; 1784 // $e .= "1. Specified an Y axis for autoscaling but have not supplied any plots<br>\n"; 1785 // $e .= "2. Specified a scale manually but have forgot to specify the tick steps"; 1786 JpGraphError::RaiseL(25026); 1787 } 1788 1789 // Bail out if no plots and no specified X-scale 1790 if( (!$this->xscale->IsSpecified() && count($this->plots)==0 && count($this->y2plots)==0) ) 1791 JpGraphError::RaiseL(25034);//("<strong>JpGraph: Can't draw unspecified X-scale.</strong><br>No plots.<br>"); 1792 1793 //Check if we should autoscale y-axis 1794 if( !$this->yscale->IsSpecified() && count($this->plots)>0 ) { 1795 list($min,$max) = $this->GetPlotsYMinMax($this->plots); 1796 $lres = $this->GetLinesYMinMax($this->lines); 1797 if( is_array($lres) ) { 1798 list($linmin,$linmax) = $lres ; 1799 $min = min($min,$linmin); 1800 $max = max($max,$linmax); 1801 } 1802 $tres = $this->GetTextsYMinMax(); 1803 if( is_array($tres) ) { 1804 list($tmin,$tmax) = $tres ; 1805 $min = min($min,$tmin); 1806 $max = max($max,$tmax); 1807 } 1808 $this->yscale->AutoScale($this->img,$min,$max, 1809 $this->img->plotheight/$this->ytick_factor); 1810 } 1811 elseif( $this->yscale->IsSpecified() && 1812 ( $this->yscale->auto_ticks || !$this->yscale->ticks->IsSpecified()) ) { 1813 // The tick calculation will use the user suplied min/max values to determine 1814 // the ticks. If auto_ticks is false the exact user specifed min and max 1815 // values will be used for the scale. 1816 // If auto_ticks is true then the scale might be slightly adjusted 1817 // so that the min and max values falls on an even major step. 1818 $min = $this->yscale->scale[0]; 1819 $max = $this->yscale->scale[1]; 1820 $this->yscale->AutoScale($this->img,$min,$max, 1821 $this->img->plotheight/$this->ytick_factor, 1822 $this->yscale->auto_ticks); 1823 } 1824 1825 if( $this->y2scale != null) { 1826 if( !$this->y2scale->IsSpecified() && count($this->y2plots)>0 ) { 1827 list($min,$max) = $this->GetPlotsYMinMax($this->y2plots); 1828 1829 $lres = $this->GetLinesYMinMax($this->y2lines); 1830 if( is_array($lres) ) { 1831 list($linmin,$linmax) = $lres ; 1832 $min = min($min,$linmin); 1833 $max = max($max,$linmax); 1834 } 1835 $tres = $this->GetTextsYMinMax(true); 1836 if( is_array($tres) ) { 1837 list($tmin,$tmax) = $tres ; 1838 $min = min($min,$tmin); 1839 $max = max($max,$tmax); 1840 } 1841 $this->y2scale->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor); 1842 } 1843 elseif( $this->y2scale->IsSpecified() && 1844 ( $this->y2scale->auto_ticks || !$this->y2scale->ticks->IsSpecified()) ) { 1845 // The tick calculation will use the user suplied min/max values to determine 1846 // the ticks. If auto_ticks is false the exact user specifed min and max 1847 // values will be used for the scale. 1848 // If auto_ticks is true then the scale might be slightly adjusted 1849 // so that the min and max values falls on an even major step. 1850 $min = $this->y2scale->scale[0]; 1851 $max = $this->y2scale->scale[1]; 1852 $this->y2scale->AutoScale($this->img,$min,$max, 1853 $this->img->plotheight/$this->ytick_factor, 1854 $this->y2scale->auto_ticks); 1855 } 1856 } 1857 1858 // 1859 // Autoscale the extra Y-axises 1860 // 1861 $n = count($this->ynaxis); 1862 for( $i=0; $i < $n; ++$i ) { 1863 if( $this->ynscale[$i] != null) { 1864 if( !$this->ynscale[$i]->IsSpecified() && count($this->ynplots[$i])>0 ) { 1865 list($min,$max) = $this->GetPlotsYMinMax($this->ynplots[$i]); 1866 $this->ynscale[$i]->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor); 1867 } 1868 elseif( $this->ynscale[$i]->IsSpecified() && 1869 ( $this->ynscale[$i]->auto_ticks || !$this->ynscale[$i]->ticks->IsSpecified()) ) { 1870 // The tick calculation will use the user suplied min/max values to determine 1871 // the ticks. If auto_ticks is false the exact user specifed min and max 1872 // values will be used for the scale. 1873 // If auto_ticks is true then the scale might be slightly adjusted 1874 // so that the min and max values falls on an even major step. 1875 $min = $this->ynscale[$i]->scale[0]; 1876 $max = $this->ynscale[$i]->scale[1]; 1877 $this->ynscale[$i]->AutoScale($this->img,$min,$max, 1878 $this->img->plotheight/$this->ytick_factor, 1879 $this->ynscale[$i]->auto_ticks); 1880 } 1881 } 1882 } 1883 1884 //Check if we should autoscale x-axis 1885 if( !$this->xscale->IsSpecified() ) { 1886 if( substr($this->axtype,0,4) == "text" ) { 1887 $max=0; 1888 $n = count($this->plots); 1889 for($i=0; $i < $n; ++$i ) { 1890 $p = $this->plots[$i]; 1891 // We need some unfortunate sub class knowledge here in order 1892 // to increase number of data points in case it is a line plot 1893 // which has the barcenter set. If not it could mean that the 1894 // last point of the data is outside the scale since the barcenter 1895 // settings means that we will shift the entire plot half a tick step 1896 // to the right in oder to align with the center of the bars. 1897 if( class_exists('BarPlot',false) ) { 1898 $cl = strtolower(get_class($p)); 1899 if( (class_exists('BarPlot',false) && ($p instanceof BarPlot)) || 1900 empty($p->barcenter) ) 1901 $max=max($max,$p->numpoints-1); 1902 else { 1903 $max=max($max,$p->numpoints); 1904 } 1905 } 1906 else { 1907 if( empty($p->barcenter) ) { 1908 $max=max($max,$p->numpoints-1); 1909 } 1910 else { 1911 $max=max($max,$p->numpoints); 1912 } 1913 } 1914 } 1915 $min=0; 1916 if( $this->y2axis != null ) { 1917 foreach( $this->y2plots as $p ) { 1918 $max=max($max,$p->numpoints-1); 1919 } 1920 } 1921 $n = count($this->ynaxis); 1922 for( $i=0; $i < $n; ++$i ) { 1923 if( $this->ynaxis[$i] != null) { 1924 foreach( $this->ynplots[$i] as $p ) { 1925 $max=max($max,$p->numpoints-1); 1926 } 1927 } 1928 } 1929 1930 $this->xscale->Update($this->img,$min,$max); 1931 $this->xscale->ticks->Set($this->xaxis->tick_step,1); 1932 $this->xscale->ticks->SupressMinorTickMarks(); 1933 } 1934 else { 1935 list($min,$max) = $this->GetXMinMax(); 1936 $lres = $this->GetLinesXMinMax($this->lines); 1937 if( $lres ) { 1938 list($linmin,$linmax) = $lres ; 1939 $min = min($min,$linmin); 1940 $max = max($max,$linmax); 1941 } 1942 1943 $lres = $this->GetLinesXMinMax($this->y2lines); 1944 if( $lres ) { 1945 list($linmin,$linmax) = $lres ; 1946 $min = min($min,$linmin); 1947 $max = max($max,$linmax); 1948 } 1949 1950 $tres = $this->GetTextsXMinMax(); 1951 if( $tres ) { 1952 list($tmin,$tmax) = $tres ; 1953 $min = min($min,$tmin); 1954 $max = max($max,$tmax); 1955 } 1956 1957 $tres = $this->GetTextsXMinMax(true); 1958 if( $tres ) { 1959 list($tmin,$tmax) = $tres ; 1960 $min = min($min,$tmin); 1961 $max = max($max,$tmax); 1962 } 1963 1964 $this->xscale->AutoScale($this->img,$min,$max,round($this->img->plotwidth/$this->xtick_factor)); 1965 } 1966 1967 //Adjust position of y-axis and y2-axis to minimum/maximum of x-scale 1968 if( !is_numeric($this->yaxis->pos) && !is_string($this->yaxis->pos) ) 1969 $this->yaxis->SetPos($this->xscale->GetMinVal()); 1970 if( $this->y2axis != null ) { 1971 if( !is_numeric($this->y2axis->pos) && !is_string($this->y2axis->pos) ) 1972 $this->y2axis->SetPos($this->xscale->GetMaxVal()); 1973 $this->y2axis->SetTitleSide(SIDE_RIGHT); 1974 } 1975 $n = count($this->ynaxis); 1976 $nY2adj = $this->y2axis != null ? $this->iYAxisDeltaPos : 0; 1977 for( $i=0; $i < $n; ++$i ) { 1978 if( $this->ynaxis[$i] != null ) { 1979 if( !is_numeric($this->ynaxis[$i]->pos) && !is_string($this->ynaxis[$i]->pos) ) { 1980 $this->ynaxis[$i]->SetPos($this->xscale->GetMaxVal()); 1981 $this->ynaxis[$i]->SetPosAbsDelta($i*$this->iYAxisDeltaPos + $nY2adj); 1982 } 1983 $this->ynaxis[$i]->SetTitleSide(SIDE_RIGHT); 1984 } 1985 } 1986 1987 } 1988 elseif( $this->xscale->IsSpecified() && 1989 ( $this->xscale->auto_ticks || !$this->xscale->ticks->IsSpecified()) ) { 1990 // The tick calculation will use the user suplied min/max values to determine 1991 // the ticks. If auto_ticks is false the exact user specifed min and max 1992 // values will be used for the scale. 1993 // If auto_ticks is true then the scale might be slightly adjusted 1994 // so that the min and max values falls on an even major step. 1995 $min = $this->xscale->scale[0]; 1996 $max = $this->xscale->scale[1]; 1997 $this->xscale->AutoScale($this->img,$min,$max, 1998 round($this->img->plotwidth/$this->xtick_factor), 1999 false); 2000 2001 if( $this->y2axis != null ) { 2002 if( !is_numeric($this->y2axis->pos) && !is_string($this->y2axis->pos) ) 2003 $this->y2axis->SetPos($this->xscale->GetMaxVal()); 2004 $this->y2axis->SetTitleSide(SIDE_RIGHT); 2005 } 2006 2007 } 2008 2009 // If we have a negative values and x-axis position is at 0 2010 // we need to supress the first and possible the last tick since 2011 // they will be drawn on top of the y-axis (and possible y2 axis) 2012 // The test below might seem strange the reasone being that if 2013 // the user hasn't specified a value for position this will not 2014 // be set until we do the stroke for the axis so as of now it 2015 // is undefined. 2016 // For X-text scale we ignore all this since the tick are usually 2017 // much further in and not close to the Y-axis. Hence the test 2018 // for 'text' 2019 2020 if( ($this->yaxis->pos==$this->xscale->GetMinVal() || 2021 (is_string($this->yaxis->pos) && $this->yaxis->pos=='min')) && 2022 !is_numeric($this->xaxis->pos) && $this->yscale->GetMinVal() < 0 && 2023 substr($this->axtype,0,4) != 'text' && $this->xaxis->pos!="min" ) { 2024 2025 //$this->yscale->ticks->SupressZeroLabel(false); 2026 $this->xscale->ticks->SupressFirst(); 2027 if( $this->y2axis != null ) { 2028 $this->xscale->ticks->SupressLast(); 2029 } 2030 } 2031 elseif( !is_numeric($this->yaxis->pos) && $this->yaxis->pos=='max' ) { 2032 $this->xscale->ticks->SupressLast(); 2033 } 2034 2035 2036 if( !$_csim ) { 2037 $this->StrokePlotArea(); 2038 if( $this->iIconDepth == DEPTH_BACK ) { 2039 $this->StrokeIcons(); 2040 } 2041 } 2042 $this->StrokeAxis(); 2043 2044 // Stroke bands 2045 if( $this->bands != null && !$_csim) 2046 for($i=0; $i < count($this->bands); ++$i) { 2047 // Stroke all bands that asks to be in the background 2048 if( $this->bands[$i]->depth == DEPTH_BACK ) 2049 $this->bands[$i]->Stroke($this->img,$this->xscale,$this->yscale); 2050 } 2051 2052 if( $this->y2bands != null && $this->y2scale != null && !$_csim ) 2053 for($i=0; $i < count($this->y2bands); ++$i) { 2054 // Stroke all bands that asks to be in the foreground 2055 if( $this->y2bands[$i]->depth == DEPTH_BACK ) 2056 $this->y2bands[$i]->Stroke($this->img,$this->xscale,$this->y2scale); 2057 } 2058 2059 2060 if( $this->grid_depth == DEPTH_BACK && !$_csim) { 2061 $this->ygrid->Stroke(); 2062 $this->xgrid->Stroke(); 2063 } 2064 2065 // Stroke Y2-axis 2066 if( $this->y2axis != null && !$_csim) { 2067 $this->y2axis->Stroke($this->xscale); 2068 $this->y2grid->Stroke(); 2069 } 2070 2071 // Stroke yn-axis 2072 $n = count($this->ynaxis); 2073 for( $i=0; $i < $n; ++$i ) { 2074 $this->ynaxis[$i]->Stroke($this->xscale); 2075 } 2076 2077 $oldoff=$this->xscale->off; 2078 if(substr($this->axtype,0,4)=="text") { 2079 if( $this->text_scale_abscenteroff > -1 ) { 2080 // For a text scale the scale factor is the number of pixel per step. 2081 // Hence we can use the scale factor as a substitute for number of pixels 2082 // per major scale step and use that in order to adjust the offset so that 2083 // an object of width "abscenteroff" becomes centered. 2084 $this->xscale->off += round($this->xscale->scale_factor/2)-round($this->text_scale_abscenteroff/2); 2085 } 2086 else { 2087 $this->xscale->off += 2088 ceil($this->xscale->scale_factor*$this->text_scale_off*$this->xscale->ticks->minor_step); 2089 } 2090 } 2091 2092 if( $this->iDoClipping ) { 2093 $oldimage = $this->img->CloneCanvasH(); 2094 } 2095 2096 if( ! $this->y2orderback ) { 2097 // Stroke all plots for Y1 axis 2098 for($i=0; $i < count($this->plots); ++$i) { 2099 $this->plots[$i]->Stroke($this->img,$this->xscale,$this->yscale); 2100 $this->plots[$i]->StrokeMargin($this->img); 2101 } 2102 } 2103 2104 // Stroke all plots for Y2 axis 2105 if( $this->y2scale != null ) 2106 for($i=0; $i< count($this->y2plots); ++$i ) { 2107 $this->y2plots[$i]->Stroke($this->img,$this->xscale,$this->y2scale); 2108 } 2109 2110 if( $this->y2orderback ) { 2111 // Stroke all plots for Y1 axis 2112 for($i=0; $i < count($this->plots); ++$i) { 2113 $this->plots[$i]->Stroke($this->img,$this->xscale,$this->yscale); 2114 $this->plots[$i]->StrokeMargin($this->img); 2115 } 2116 } 2117 2118 $n = count($this->ynaxis); 2119 for( $i=0; $i < $n; ++$i ) { 2120 $m = count($this->ynplots[$i]); 2121 for( $j=0; $j < $m; ++$j ) { 2122 $this->ynplots[$i][$j]->Stroke($this->img,$this->xscale,$this->ynscale[$i]); 2123 $this->ynplots[$i][$j]->StrokeMargin($this->img); 2124 } 2125 } 2126 2127 if( $this->iIconDepth == DEPTH_FRONT) { 2128 $this->StrokeIcons(); 2129 } 2130 2131 if( $this->iDoClipping ) { 2132 // Clipping only supports graphs at 0 and 90 degrees 2133 if( $this->img->a == 0 ) { 2134 $this->img->CopyCanvasH($oldimage,$this->img->img, 2135 $this->img->left_margin,$this->img->top_margin, 2136 $this->img->left_margin,$this->img->top_margin, 2137 $this->img->plotwidth+1,$this->img->plotheight); 2138 } 2139 elseif( $this->img->a == 90 ) { 2140 $adj = ($this->img->height - $this->img->width)/2; 2141 $this->img->CopyCanvasH($oldimage,$this->img->img, 2142 $this->img->bottom_margin-$adj,$this->img->left_margin+$adj, 2143 $this->img->bottom_margin-$adj,$this->img->left_margin+$adj, 2144 $this->img->plotheight+1,$this->img->plotwidth); 2145 } 2146 else { 2147 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.'); 2148 } 2149 $this->img->Destroy(); 2150 $this->img->SetCanvasH($oldimage); 2151 } 2152 2153 $this->xscale->off=$oldoff; 2154 2155 if( $this->grid_depth == DEPTH_FRONT && !$_csim ) { 2156 $this->ygrid->Stroke(); 2157 $this->xgrid->Stroke(); 2158 } 2159 2160 // Stroke bands 2161 if( $this->bands!= null ) 2162 for($i=0; $i < count($this->bands); ++$i) { 2163 // Stroke all bands that asks to be in the foreground 2164 if( $this->bands[$i]->depth == DEPTH_FRONT ) 2165 $this->bands[$i]->Stroke($this->img,$this->xscale,$this->yscale); 2166 } 2167 2168 if( $this->y2bands!= null && $this->y2scale != null ) 2169 for($i=0; $i < count($this->y2bands); ++$i) { 2170 // Stroke all bands that asks to be in the foreground 2171 if( $this->y2bands[$i]->depth == DEPTH_FRONT ) 2172 $this->y2bands[$i]->Stroke($this->img,$this->xscale,$this->y2scale); 2173 } 2174 2175 2176 // Stroke any lines added 2177 if( $this->lines != null ) { 2178 for($i=0; $i < count($this->lines); ++$i) { 2179 $this->lines[$i]->Stroke($this->img,$this->xscale,$this->yscale); 2180 } 2181 } 2182 2183 if( $this->y2lines != null && $this->y2scale != null ) { 2184 for($i=0; $i < count($this->y2lines); ++$i) { 2185 $this->y2lines[$i]->Stroke($this->img,$this->xscale,$this->y2scale); 2186 } 2187 } 2188 2189 // Finally draw the axis again since some plots may have nagged 2190 // the axis in the edges. 2191 if( !$_csim ) 2192 $this->StrokeAxis(false); 2193 2194 if( $this->y2scale != null && !$_csim ) 2195 $this->y2axis->Stroke($this->xscale,false); 2196 2197 if( !$_csim ) { 2198 $this->StrokePlotBox(); 2199 } 2200 2201 // The titles and legends never gets rotated so make sure 2202 // that the angle is 0 before stroking them 2203 $aa = $this->img->SetAngle(0); 2204 $this->StrokeTitles(); 2205 $this->footer->Stroke($this->img); 2206 $this->legend->Stroke($this->img); 2207 $this->img->SetAngle($aa); 2208 $this->StrokeTexts(); 2209 $this->StrokeTables(); 2210 2211 if( !$_csim ) { 2212 2213 $this->img->SetAngle($aa); 2214 2215 // Draw an outline around the image map 2216 if(_JPG_DEBUG) { 2217 $this->DisplayClientSideaImageMapAreas(); 2218 } 2219 2220 // Adjust the appearance of the image 2221 $this->AdjustSaturationBrightnessContrast(); 2222 2223 // Should we do any final image transformation 2224 if( $this->iImgTrans ) { 2225 if( !class_exists('ImgTrans',false) ) { 2226 require_once('jpgraph_imgtrans.php'); 2227 //JpGraphError::Raise('In order to use image transformation you must include the file jpgraph_imgtrans.php in your script.'); 2228 } 2229 2230 $tform = new ImgTrans($this->img->img); 2231 $this->img->img = $tform->Skew3D($this->iImgTransHorizon,$this->iImgTransSkewDist, 2232 $this->iImgTransDirection,$this->iImgTransHighQ, 2233 $this->iImgTransMinSize,$this->iImgTransFillColor, 2234 $this->iImgTransBorder); 2235 } 2236 2237 // If the filename is given as the special "__handle" 2238 // then the image handler is returned and the image is NOT 2239 // streamed back 2240 if( $aStrokeFileName == _IMG_HANDLER ) { 2241 return $this->img->img; 2242 } 2243 else { 2244 // Finally stream the generated picture 2245 $this->cache->PutAndStream($this->img,$this->cache_name,$this->inline,$aStrokeFileName); 2246 } 2247 } 2248 } 2249 2250 function SetAxisLabelBackground($aType,$aXFColor='lightgray',$aXColor='black',$aYFColor='lightgray',$aYColor='black') { 2251 $this->iAxisLblBgType = $aType; 2252 $this->iXAxisLblBgFillColor = $aXFColor; 2253 $this->iXAxisLblBgColor = $aXColor; 2254 $this->iYAxisLblBgFillColor = $aYFColor; 2255 $this->iYAxisLblBgColor = $aYColor; 2256 } 2257 2258//--------------- 2259// PRIVATE METHODS 2260 2261 function StrokeAxisLabelBackground() { 2262 // Types 2263 // 0 = No background 2264 // 1 = Only X-labels, length of axis 2265 // 2 = Only Y-labels, length of axis 2266 // 3 = As 1 but extends to width of graph 2267 // 4 = As 2 but extends to height of graph 2268 // 5 = Combination of 3 & 4 2269 // 6 = Combination of 1 & 2 2270 2271 $t = $this->iAxisLblBgType ; 2272 if( $t < 1 ) return; 2273 // Stroke optional X-axis label background color 2274 if( $t == 1 || $t == 3 || $t == 5 || $t == 6 ) { 2275 $this->img->PushColor($this->iXAxisLblBgFillColor); 2276 if( $t == 1 || $t == 6 ) { 2277 $xl = $this->img->left_margin; 2278 $yu = $this->img->height - $this->img->bottom_margin + 1; 2279 $xr = $this->img->width - $this->img->right_margin ; 2280 $yl = $this->img->height-1-$this->frame_weight; 2281 } 2282 else { // t==3 || t==5 2283 $xl = $this->frame_weight; 2284 $yu = $this->img->height - $this->img->bottom_margin + 1; 2285 $xr = $this->img->width - 1 - $this->frame_weight; 2286 $yl = $this->img->height-1-$this->frame_weight; 2287 } 2288 2289 $this->img->FilledRectangle($xl,$yu,$xr,$yl); 2290 $this->img->PopColor(); 2291 2292 // Check if we should add the vertical lines at left and right edge 2293 if( $this->iXAxisLblBgColor !== '' ) { 2294 $this->img->PushColor($this->iXAxisLblBgColor); 2295 if( $t == 1 || $t == 6 ) { 2296 $this->img->Line($xl,$yu,$xl,$yl); 2297 $this->img->Line($xr,$yu,$xr,$yl); 2298 } 2299 else { 2300 $xl = $this->img->width - $this->img->right_margin ; 2301 $this->img->Line($xl,$yu-1,$xr,$yu-1); 2302 } 2303 $this->img->PopColor(); 2304 } 2305 } 2306 2307 if( $t == 2 || $t == 4 || $t == 5 || $t == 6 ) { 2308 $this->img->PushColor($this->iYAxisLblBgFillColor); 2309 if( $t == 2 || $t == 6 ) { 2310 $xl = $this->frame_weight; 2311 $yu = $this->frame_weight+$this->img->top_margin; 2312 $xr = $this->img->left_margin - 1; 2313 $yl = $this->img->height - $this->img->bottom_margin + 1; 2314 } 2315 else { 2316 $xl = $this->frame_weight; 2317 $yu = $this->frame_weight; 2318 $xr = $this->img->left_margin - 1; 2319 $yl = $this->img->height-1-$this->frame_weight; 2320 } 2321 2322 $this->img->FilledRectangle($xl,$yu,$xr,$yl); 2323 $this->img->PopColor(); 2324 2325 // Check if we should add the vertical lines at left and right edge 2326 if( $this->iXAxisLblBgColor !== '' ) { 2327 $this->img->PushColor($this->iXAxisLblBgColor); 2328 if( $t == 2 || $t == 6 ) { 2329 $this->img->Line($xl,$yu-1,$xr,$yu-1); 2330 $this->img->Line($xl,$yl-1,$xr,$yl-1); 2331 } 2332 else { 2333 $this->img->Line($xr+1,$yu,$xr+1,$this->img->top_margin); 2334 } 2335 $this->img->PopColor(); 2336 } 2337 2338 } 2339 } 2340 2341 function StrokeAxis($aStrokeLabels=true) { 2342 2343 if( $aStrokeLabels ) { 2344 $this->StrokeAxisLabelBackground(); 2345 } 2346 2347 // Stroke axis 2348 if( $this->iAxisStyle != AXSTYLE_SIMPLE ) { 2349 switch( $this->iAxisStyle ) { 2350 case AXSTYLE_BOXIN : 2351 $toppos = SIDE_DOWN; 2352 $bottompos = SIDE_UP; 2353 $leftpos = SIDE_RIGHT; 2354 $rightpos = SIDE_LEFT; 2355 break; 2356 case AXSTYLE_BOXOUT : 2357 $toppos = SIDE_UP; 2358 $bottompos = SIDE_DOWN; 2359 $leftpos = SIDE_LEFT; 2360 $rightpos = SIDE_RIGHT; 2361 break; 2362 case AXSTYLE_YBOXIN: 2363 $toppos = FALSE; 2364 $bottompos = SIDE_UP; 2365 $leftpos = SIDE_RIGHT; 2366 $rightpos = SIDE_LEFT; 2367 break; 2368 case AXSTYLE_YBOXOUT: 2369 $toppos = FALSE; 2370 $bottompos = SIDE_DOWN; 2371 $leftpos = SIDE_LEFT; 2372 $rightpos = SIDE_RIGHT; 2373 break; 2374 default: 2375 JpGRaphError::RaiseL(25036,$this->iAxisStyle); //('Unknown AxisStyle() : '.$this->iAxisStyle); 2376 break; 2377 } 2378 2379 // By default we hide the first label so it doesn't cross the 2380 // Y-axis in case the positon hasn't been set by the user. 2381 // However, if we use a box we always want the first value 2382 // displayed so we make sure it will be displayed. 2383 $this->xscale->ticks->SupressFirst(false); 2384 2385 // Now draw the bottom X-axis 2386 $this->xaxis->SetPos('min'); 2387 $this->xaxis->SetLabelSide(SIDE_DOWN); 2388 $this->xaxis->scale->ticks->SetSide($bottompos); 2389 $this->xaxis->Stroke($this->yscale,$aStrokeLabels); 2390 2391 if( $toppos !== FALSE ) { 2392 // We also want a top X-axis 2393 $this->xaxis = $this->xaxis; 2394 $this->xaxis->SetPos('max'); 2395 $this->xaxis->SetLabelSide(SIDE_UP); 2396 // No title for the top X-axis 2397 $this->title->Set(''); 2398 $this->xaxis->scale->ticks->SetSide($toppos); 2399 $this->xaxis->Stroke($this->yscale,$aStrokeLabels); 2400 } 2401 2402 // Stroke the left Y-axis 2403 $this->yaxis->SetPos('min'); 2404 $this->yaxis->SetLabelSide(SIDE_LEFT); 2405 $this->yaxis->scale->ticks->SetSide($leftpos); 2406 $this->yaxis->Stroke($this->xscale,$aStrokeLabels); 2407 2408 // Stroke the right Y-axis 2409 $this->yaxis->SetPos('max'); 2410 // No title for the right side 2411 $this->title->Set(''); 2412 $this->yaxis->SetLabelSide(SIDE_RIGHT); 2413 $this->yaxis->scale->ticks->SetSide($rightpos); 2414 $this->yaxis->Stroke($this->xscale,$aStrokeLabels); 2415 } 2416 else { 2417 $this->xaxis->Stroke($this->yscale,$aStrokeLabels); 2418 $this->yaxis->Stroke($this->xscale,$aStrokeLabels); 2419 } 2420 } 2421 2422 2423 // Private helper function for backgound image 2424 function LoadBkgImage($aImgFormat='',$aFile='',$aImgStr='') { 2425 if( $aImgStr != '' ) { 2426 return Image::CreateFromString($aImgStr); 2427 } 2428 if( $aFile == '' ) 2429 $aFile = $this->background_image; 2430 // Remove case sensitivity and setup appropriate function to create image 2431 // Get file extension. This should be the LAST '.' separated part of the filename 2432 $e = explode('.',$aFile); 2433 $ext = strtolower($e[count($e)-1]); 2434 if ($ext == "jpeg") { 2435 $ext = "jpg"; 2436 } 2437 2438 if( trim($ext) == '' ) 2439 $ext = 'png'; // Assume PNG if no extension specified 2440 2441 if( $aImgFormat == '' ) 2442 $imgtag = $ext; 2443 else 2444 $imgtag = $aImgFormat; 2445 2446 $supported = imagetypes(); 2447 if( ( $ext == 'jpg' && !($supported & IMG_JPG) ) || 2448 ( $ext == 'gif' && !($supported & IMG_GIF) ) || 2449 ( $ext == 'png' && !($supported & IMG_PNG) ) ) { 2450 JpGraphError::RaiseL(25037,$aFile);//('The image format of your background image ('.$aFile.') is not supported in your system configuration. '); 2451 } 2452 2453 2454 if( $imgtag == "jpg" || $imgtag == "jpeg") 2455 { 2456 $f = "imagecreatefromjpeg"; 2457 $imgtag = "jpg"; 2458 } 2459 else 2460 { 2461 $f = "imagecreatefrom".$imgtag; 2462 } 2463 2464 // Compare specified image type and file extension 2465 if( $imgtag != $ext ) { 2466 //$t = "Background image seems to be of different type (has different file extension) than specified imagetype. Specified: '".$aImgFormat."'File: '".$aFile."'"; 2467 JpGraphError::RaiseL(25038, $aImgFormat, $aFile); 2468 } 2469 2470 $img = @$f($aFile); 2471 if( !$img ) { 2472 JpGraphError::RaiseL(25039,$aFile);//(" Can't read background image: '".$aFile."'"); 2473 } 2474 return $img; 2475 } 2476 2477 function StrokeBackgroundGrad() { 2478 if( $this->bkg_gradtype < 0 ) 2479 return; 2480 $grad = new Gradient($this->img); 2481 if( $this->bkg_gradstyle == BGRAD_PLOT ) { 2482 $xl = $this->img->left_margin; 2483 $yt = $this->img->top_margin; 2484 $xr = $xl + $this->img->plotwidth+1 ; 2485 $yb = $yt + $this->img->plotheight ; 2486 $grad->FilledRectangle($xl,$yt,$xr,$yb,$this->bkg_gradfrom,$this->bkg_gradto,$this->bkg_gradtype); 2487 } 2488 else { 2489 $xl = 0; 2490 $yt = 0; 2491 $xr = $xl + $this->img->width - 1; 2492 $yb = $yt + $this->img->height ; 2493 if( $this->doshadow ) { 2494 $xr -= $this->shadow_width; 2495 $yb -= $this->shadow_width; 2496 } 2497 if( $this->doframe ) { 2498 $yt += $this->frame_weight; 2499 $yb -= $this->frame_weight; 2500 $xl += $this->frame_weight; 2501 $xr -= $this->frame_weight; 2502 } 2503 $aa = $this->img->SetAngle(0); 2504 $grad->FilledRectangle($xl,$yt,$xr,$yb,$this->bkg_gradfrom,$this->bkg_gradto,$this->bkg_gradtype); 2505 $aa = $this->img->SetAngle($aa); 2506 } 2507 } 2508 2509 function StrokeFrameBackground() { 2510 if( $this->background_image != "" && $this->background_cflag != "" ) { 2511 JpGraphError::RaiseL(25040);//('It is not possible to specify both a background image and a background country flag.'); 2512 } 2513 if( $this->background_image != "" ) { 2514 $bkgimg = $this->LoadBkgImage($this->background_image_format); 2515 $this->img->_AdjBrightContrast($bkgimg,$this->background_image_bright, 2516 $this->background_image_contr); 2517 $this->img->_AdjSat($bkgimg,$this->background_image_sat); 2518 } 2519 elseif( $this->background_cflag != "" ) { 2520 if( ! class_exists('FlagImages',false) ) { 2521 JpGraphError::RaiseL(25041);//('In order to use Country flags as backgrounds you must include the "jpgraph_flags.php" file.'); 2522 } 2523 $fobj = new FlagImages(FLAGSIZE4); 2524 $dummy=''; 2525 $bkgimg = $fobj->GetImgByName($this->background_cflag,$dummy); 2526 $this->background_image_mix = $this->background_cflag_mix; 2527 $this->background_image_type = $this->background_cflag_type; 2528 } 2529 else { 2530 return ; 2531 } 2532 2533 $bw = ImageSX($bkgimg); 2534 $bh = ImageSY($bkgimg); 2535 2536 // No matter what the angle is we always stroke the image and frame 2537 // assuming it is 0 degree 2538 $aa = $this->img->SetAngle(0); 2539 2540 switch( $this->background_image_type ) { 2541 case BGIMG_FILLPLOT: // Resize to just fill the plotarea 2542 $this->FillMarginArea(); 2543 $this->StrokeFrame(); 2544 $this->FillPlotArea(); 2545 $this->img->CopyMerge($bkgimg, 2546 $this->img->left_margin,$this->img->top_margin, 2547 0,0,$this->img->plotwidth+1,$this->img->plotheight, 2548 $bw,$bh,$this->background_image_mix); 2549 break; 2550 case BGIMG_FILLFRAME: // Fill the whole area from upper left corner, resize to just fit 2551 $hadj=0; $vadj=0; 2552 if( $this->doshadow ) { 2553 $hadj = $this->shadow_width; 2554 $vadj = $this->shadow_width; 2555 } 2556 $this->FillMarginArea(); 2557 $this->FillPlotArea(); 2558 $this->img->CopyMerge($bkgimg,0,0,0,0,$this->img->width-$hadj,$this->img->height-$vadj, 2559 $bw,$bh,$this->background_image_mix); 2560 $this->StrokeFrame(); 2561 break; 2562 case BGIMG_COPY: // Just copy the image from left corner, no resizing 2563 $this->FillMarginArea(); 2564 $this->FillPlotArea(); 2565 $this->img->CopyMerge($bkgimg,0,0,0,0,$bw,$bh, 2566 $bw,$bh,$this->background_image_mix); 2567 $this->StrokeFrame(); 2568 break; 2569 case BGIMG_CENTER: // Center original image in the plot area 2570 $this->FillMarginArea(); 2571 $this->FillPlotArea(); 2572 $centerx = round($this->img->plotwidth/2+$this->img->left_margin-$bw/2); 2573 $centery = round($this->img->plotheight/2+$this->img->top_margin-$bh/2); 2574 $this->img->CopyMerge($bkgimg,$centerx,$centery,0,0,$bw,$bh, 2575 $bw,$bh,$this->background_image_mix); 2576 $this->StrokeFrame(); 2577 break; 2578 default: 2579 JpGraphError::RaiseL(25042);//(" Unknown background image layout"); 2580 } 2581 $this->img->SetAngle($aa); 2582 } 2583 2584 // Private 2585 // Draw a frame around the image 2586 function StrokeFrame() { 2587 if( !$this->doframe ) return; 2588 2589 if( $this->background_image_type <= 1 && 2590 ($this->bkg_gradtype < 0 || ($this->bkg_gradtype > 0 && $this->bkg_gradstyle==BGRAD_PLOT)) ) { 2591 $c = $this->margin_color; 2592 } 2593 else { 2594 $c = false; 2595 } 2596 2597 if( $this->doshadow ) { 2598 $this->img->SetColor($this->frame_color); 2599 $this->img->ShadowRectangle(0,0,$this->img->width,$this->img->height, 2600 $c,$this->shadow_width,$this->shadow_color); 2601 } 2602 elseif( $this->framebevel ) { 2603 if( $c ) { 2604 $this->img->SetColor($this->margin_color); 2605 $this->img->FilledRectangle(0,0,$this->img->width-1,$this->img->height-1); 2606 } 2607 $this->img->Bevel(1,1,$this->img->width-2,$this->img->height-2, 2608 $this->framebeveldepth, 2609 $this->framebevelcolor1,$this->framebevelcolor2); 2610 if( $this->framebevelborder ) { 2611 $this->img->SetColor($this->framebevelbordercolor); 2612 $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1); 2613 } 2614 } 2615 else { 2616 $this->img->SetLineWeight($this->frame_weight); 2617 if( $c ) { 2618 $this->img->SetColor($this->margin_color); 2619 $this->img->FilledRectangle(0,0,$this->img->width-1,$this->img->height-1); 2620 } 2621 $this->img->SetColor($this->frame_color); 2622 $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1); 2623 } 2624 } 2625 2626 function FillMarginArea() { 2627 $hadj=0; $vadj=0; 2628 if( $this->doshadow ) { 2629 $hadj = $this->shadow_width; 2630 $vadj = $this->shadow_width; 2631 } 2632 2633 $this->img->SetColor($this->margin_color); 2634// $this->img->FilledRectangle(0,0,$this->img->width-1-$hadj,$this->img->height-1-$vadj); 2635 2636 $this->img->FilledRectangle(0,0,$this->img->width-1-$hadj,$this->img->top_margin); 2637 $this->img->FilledRectangle(0,$this->img->top_margin,$this->img->left_margin,$this->img->height-1-$hadj); 2638 $this->img->FilledRectangle($this->img->left_margin+1, 2639 $this->img->height-$this->img->bottom_margin, 2640 $this->img->width-1-$hadj, 2641 $this->img->height-1-$hadj); 2642 $this->img->FilledRectangle($this->img->width-$this->img->right_margin, 2643 $this->img->top_margin+1, 2644 $this->img->width-1-$hadj, 2645 $this->img->height-$this->img->bottom_margin-1); 2646 } 2647 2648 function FillPlotArea() { 2649 $this->img->PushColor($this->plotarea_color); 2650 $this->img->FilledRectangle($this->img->left_margin, 2651 $this->img->top_margin, 2652 $this->img->width-$this->img->right_margin, 2653 $this->img->height-$this->img->bottom_margin); 2654 $this->img->PopColor(); 2655 } 2656 2657 // Stroke the plot area with either a solid color or a background image 2658 function StrokePlotArea() { 2659 // Note: To be consistent we really should take a possible shadow 2660 // into account. However, that causes some problem for the LinearScale class 2661 // since in the current design it does not have any links to class Graph which 2662 // means it has no way of compensating for the adjusted plotarea in case of a 2663 // shadow. So, until I redesign LinearScale we can't compensate for this. 2664 // So just set the two adjustment parameters to zero for now. 2665 $boxadj = 0; //$this->doframe ? $this->frame_weight : 0 ; 2666 $adj = 0; //$this->doshadow ? $this->shadow_width : 0 ; 2667 2668 if( $this->background_image != "" || $this->background_cflag != "" ) { 2669 $this->StrokeFrameBackground(); 2670 } 2671 else { 2672 $aa = $this->img->SetAngle(0); 2673 $this->StrokeFrame(); 2674 $aa = $this->img->SetAngle($aa); 2675 $this->StrokeBackgroundGrad(); 2676 if( $this->bkg_gradtype < 0 || 2677 ($this->bkg_gradtype > 0 && $this->bkg_gradstyle==BGRAD_MARGIN) ) { 2678 $this->FillPlotArea(); 2679 } 2680 } 2681 } 2682 2683 function StrokeIcons() { 2684 $n = count($this->iIcons); 2685 for( $i=0; $i < $n; ++$i ) { 2686 $this->iIcons[$i]->StrokeWithScale($this->img,$this->xscale,$this->yscale); 2687 } 2688 } 2689 2690 function StrokePlotBox() { 2691 // Should we draw a box around the plot area? 2692 if( $this->boxed ) { 2693 $this->img->SetLineWeight(1); 2694 $this->img->SetLineStyle('solid'); 2695 $this->img->SetColor($this->box_color); 2696 for($i=0; $i < $this->box_weight; ++$i ) { 2697 $this->img->Rectangle( 2698 $this->img->left_margin-$i,$this->img->top_margin-$i, 2699 $this->img->width-$this->img->right_margin+$i, 2700 $this->img->height-$this->img->bottom_margin+$i); 2701 } 2702 } 2703 } 2704 2705 function SetTitleBackgroundFillStyle($aStyle,$aColor1='black',$aColor2='white') { 2706 $this->titlebkg_fillstyle = $aStyle; 2707 $this->titlebkg_scolor1 = $aColor1; 2708 $this->titlebkg_scolor2 = $aColor2; 2709 } 2710 2711 function SetTitleBackground($aBackColor='gray', $aStyle=TITLEBKG_STYLE1, $aFrameStyle=TITLEBKG_FRAME_NONE, $aFrameColor='black', $aFrameWeight=1, $aBevelHeight=3, $aEnable=true) { 2712 $this->titlebackground = $aEnable; 2713 $this->titlebackground_color = $aBackColor; 2714 $this->titlebackground_style = $aStyle; 2715 $this->titlebackground_framecolor = $aFrameColor; 2716 $this->titlebackground_framestyle = $aFrameStyle; 2717 $this->titlebackground_frameweight = $aFrameWeight; 2718 $this->titlebackground_bevelheight = $aBevelHeight ; 2719 } 2720 2721 2722 function StrokeTitles() { 2723 2724 $margin=3; 2725 2726 if( $this->titlebackground ) { 2727 2728 // Find out height 2729 $this->title->margin += 2 ; 2730 $h = $this->title->GetTextHeight($this->img)+$this->title->margin+$margin; 2731 if( $this->subtitle->t != "" && !$this->subtitle->hide ) { 2732 $h += $this->subtitle->GetTextHeight($this->img)+$margin+ 2733 $this->subtitle->margin; 2734 $h += 2; 2735 } 2736 if( $this->subsubtitle->t != "" && !$this->subsubtitle->hide ) { 2737 $h += $this->subsubtitle->GetTextHeight($this->img)+$margin+ 2738 $this->subsubtitle->margin; 2739 $h += 2; 2740 } 2741 $this->img->PushColor($this->titlebackground_color); 2742 if( $this->titlebackground_style === TITLEBKG_STYLE1 ) { 2743 // Inside the frame 2744 if( $this->framebevel ) { 2745 $x1 = $y1 = $this->framebeveldepth + 1 ; 2746 $x2 = $this->img->width - $this->framebeveldepth - 2 ; 2747 $this->title->margin += $this->framebeveldepth + 1 ; 2748 $h += $y1 ; 2749 $h += 2; 2750 } 2751 else { 2752 $x1 = $y1 = $this->frame_weight; 2753 $x2 = $this->img->width - 2*$x1; 2754 } 2755 } 2756 elseif( $this->titlebackground_style === TITLEBKG_STYLE2 ) { 2757 // Cover the frame as well 2758 $x1 = $y1 = 0; 2759 $x2 = $this->img->width - 1 ; 2760 } 2761 elseif( $this->titlebackground_style === TITLEBKG_STYLE3 ) { 2762 // Cover the frame as well (the difference is that 2763 // for style==3 a bevel frame border is on top 2764 // of the title background) 2765 $x1 = $y1 = 0; 2766 $x2 = $this->img->width - 1 ; 2767 $h += $this->framebeveldepth ; 2768 $this->title->margin += $this->framebeveldepth ; 2769 } 2770 else { 2771 JpGraphError::RaiseL(25043);//('Unknown title background style.'); 2772 } 2773 2774 if( $this->titlebackground_framestyle === 3 ) { 2775 $h += $this->titlebackground_bevelheight*2 + 1 ; 2776 $this->title->margin += $this->titlebackground_bevelheight ; 2777 } 2778 2779 if( $this->doshadow ) { 2780 $x2 -= $this->shadow_width ; 2781 } 2782 2783 $indent=0; 2784 if( $this->titlebackground_framestyle == TITLEBKG_FRAME_BEVEL ) { 2785 $ind = $this->titlebackground_bevelheight; 2786 } 2787 2788 if( $this->titlebkg_fillstyle==TITLEBKG_FILLSTYLE_HSTRIPED ) { 2789 $this->img->FilledRectangle2($x1+$ind,$y1+$ind,$x2-$ind,$h-$ind, 2790 $this->titlebkg_scolor1, 2791 $this->titlebkg_scolor2); 2792 } 2793 elseif( $this->titlebkg_fillstyle==TITLEBKG_FILLSTYLE_VSTRIPED ) { 2794 $this->img->FilledRectangle2($x1+$ind,$y1+$ind,$x2-$ind,$h-$ind, 2795 $this->titlebkg_scolor1, 2796 $this->titlebkg_scolor2,2); 2797 } 2798 else { 2799 // Solid fill 2800 $this->img->FilledRectangle($x1,$y1,$x2,$h); 2801 } 2802 $this->img->PopColor(); 2803 2804 $this->img->PushColor($this->titlebackground_framecolor); 2805 $this->img->SetLineWeight($this->titlebackground_frameweight); 2806 if( $this->titlebackground_framestyle == TITLEBKG_FRAME_FULL ) { 2807 // Frame background 2808 $this->img->Rectangle($x1,$y1,$x2,$h); 2809 } 2810 elseif( $this->titlebackground_framestyle == TITLEBKG_FRAME_BOTTOM ) { 2811 // Bottom line only 2812 $this->img->Line($x1,$h,$x2,$h); 2813 } 2814 elseif( $this->titlebackground_framestyle == TITLEBKG_FRAME_BEVEL ) { 2815 $this->img->Bevel($x1,$y1,$x2,$h,$this->titlebackground_bevelheight); 2816 } 2817 $this->img->PopColor(); 2818 2819 // This is clumsy. But we neeed to stroke the whole graph frame if it is 2820 // set to bevel to get the bevel shading on top of the text background 2821 if( $this->framebevel && $this->doframe && 2822 $this->titlebackground_style === 3 ) { 2823 $this->img->Bevel(1,1,$this->img->width-2,$this->img->height-2, 2824 $this->framebeveldepth, 2825 $this->framebevelcolor1,$this->framebevelcolor2); 2826 if( $this->framebevelborder ) { 2827 $this->img->SetColor($this->framebevelbordercolor); 2828 $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1); 2829 } 2830 } 2831 } 2832 2833 // Stroke title 2834 $y = $this->title->margin; 2835 if( $this->title->halign == 'center' ) 2836 $this->title->Center(0,$this->img->width,$y); 2837 elseif( $this->title->halign == 'left' ) { 2838 $this->title->SetPos($this->title->margin+2,$y); 2839 } 2840 elseif( $this->title->halign == 'right' ) { 2841 $indent = 0; 2842 if( $this->doshadow ) 2843 $indent = $this->shadow_width+2; 2844 $this->title->SetPos($this->img->width-$this->title->margin-$indent,$y,'right'); 2845 } 2846 $this->title->Stroke($this->img); 2847 2848 // ... and subtitle 2849 $y += $this->title->GetTextHeight($this->img) + $margin + $this->subtitle->margin; 2850 if( $this->subtitle->halign == 'center' ) 2851 $this->subtitle->Center(0,$this->img->width,$y); 2852 elseif( $this->subtitle->halign == 'left' ) { 2853 $this->subtitle->SetPos($this->subtitle->margin+2,$y); 2854 } 2855 elseif( $this->subtitle->halign == 'right' ) { 2856 $indent = 0; 2857 if( $this->doshadow ) 2858 $indent = $this->shadow_width+2; 2859 $this->subtitle->SetPos($this->img->width-$this->subtitle->margin-$indent,$y,'right'); 2860 } 2861 $this->subtitle->Stroke($this->img); 2862 2863 // ... and subsubtitle 2864 $y += $this->subtitle->GetTextHeight($this->img) + $margin + $this->subsubtitle->margin; 2865 if( $this->subsubtitle->halign == 'center' ) 2866 $this->subsubtitle->Center(0,$this->img->width,$y); 2867 elseif( $this->subsubtitle->halign == 'left' ) { 2868 $this->subsubtitle->SetPos($this->subsubtitle->margin+2,$y); 2869 } 2870 elseif( $this->subsubtitle->halign == 'right' ) { 2871 $indent = 0; 2872 if( $this->doshadow ) 2873 $indent = $this->shadow_width+2; 2874 $this->subsubtitle->SetPos($this->img->width-$this->subsubtitle->margin-$indent,$y,'right'); 2875 } 2876 $this->subsubtitle->Stroke($this->img); 2877 2878 // ... and fancy title 2879 $this->tabtitle->Stroke($this->img); 2880 2881 } 2882 2883 function StrokeTexts() { 2884 // Stroke any user added text objects 2885 if( $this->texts != null ) { 2886 for($i=0; $i < count($this->texts); ++$i) { 2887 $this->texts[$i]->StrokeWithScale($this->img,$this->xscale,$this->yscale); 2888 } 2889 } 2890 2891 if( $this->y2texts != null && $this->y2scale != null ) { 2892 for($i=0; $i < count($this->y2texts); ++$i) { 2893 $this->y2texts[$i]->StrokeWithScale($this->img,$this->xscale,$this->y2scale); 2894 } 2895 } 2896 2897 } 2898 2899 function StrokeTables() { 2900 if( $this->iTables != null ) { 2901 $n = count($this->iTables); 2902 for( $i=0; $i < $n; ++$i ) { 2903 $this->iTables[$i]->StrokeWithScale($this->img,$this->xscale,$this->yscale); 2904 } 2905 } 2906 } 2907 2908 function DisplayClientSideaImageMapAreas() { 2909 // Debug stuff - display the outline of the image map areas 2910 $csim=''; 2911 foreach ($this->plots as $p) { 2912 $csim.= $p->GetCSIMareas(); 2913 } 2914 $csim .= $this->legend->GetCSIMareas(); 2915 if (preg_match_all("/area shape=\"(\w+)\" coords=\"([0-9\, ]+)\"/", $csim, $coords)) { 2916 $this->img->SetColor($this->csimcolor); 2917 $n = count($coords[0]); 2918 for ($i=0; $i < $n; $i++) { 2919 if ($coords[1][$i]=="poly") { 2920 preg_match_all('/\s*([0-9]+)\s*,\s*([0-9]+)\s*,*/',$coords[2][$i],$pts); 2921 $this->img->SetStartPoint($pts[1][count($pts[0])-1],$pts[2][count($pts[0])-1]); 2922 $m = count($pts[0]); 2923 for ($j=0; $j < $m; $j++) { 2924 $this->img->LineTo($pts[1][$j],$pts[2][$j]); 2925 } 2926 } else if ($coords[1][$i]=="rect") { 2927 $pts = preg_split('/,/', $coords[2][$i]); 2928 $this->img->SetStartPoint($pts[0],$pts[1]); 2929 $this->img->LineTo($pts[2],$pts[1]); 2930 $this->img->LineTo($pts[2],$pts[3]); 2931 $this->img->LineTo($pts[0],$pts[3]); 2932 $this->img->LineTo($pts[0],$pts[1]); 2933 } 2934 } 2935 } 2936 } 2937 2938 function AdjustSaturationBrightnessContrast() { 2939 // Adjust the brightness and contrast of the image 2940 if( $this->image_contr || $this->image_bright ) 2941 $this->img->AdjBrightContrast($this->image_bright,$this->image_contr); 2942 if( $this->image_sat ) 2943 $this->img->AdjSat($this->image_sat); 2944 } 2945 2946 // Text scale offset in world coordinates 2947 function SetTextScaleOff($aOff) { 2948 $this->text_scale_off = $aOff; 2949 $this->xscale->text_scale_off = $aOff; 2950 } 2951 2952 // Text width of bar to be centered in absolute pixels 2953 function SetTextScaleAbsCenterOff($aOff) { 2954 $this->text_scale_abscenteroff = $aOff; 2955 } 2956 2957 // Get Y min and max values for added lines 2958 function GetLinesYMinMax( $aLines ) { 2959 $n = count($aLines); 2960 if( $n == 0 ) return false; 2961 $min = $aLines[0]->scaleposition ; 2962 $max = $min ; 2963 $flg = false; 2964 for( $i=0; $i < $n; ++$i ) { 2965 if( $aLines[$i]->direction == HORIZONTAL ) { 2966 $flg = true ; 2967 $v = $aLines[$i]->scaleposition ; 2968 if( $min > $v ) $min = $v ; 2969 if( $max < $v ) $max = $v ; 2970 } 2971 } 2972 return $flg ? array($min,$max) : false ; 2973 } 2974 2975 // Get X min and max values for added lines 2976 function GetLinesXMinMax( $aLines ) { 2977 $n = count($aLines); 2978 if( $n == 0 ) return false ; 2979 $min = $aLines[0]->scaleposition ; 2980 $max = $min ; 2981 $flg = false; 2982 for( $i=0; $i < $n; ++$i ) { 2983 if( $aLines[$i]->direction == VERTICAL ) { 2984 $flg = true ; 2985 $v = $aLines[$i]->scaleposition ; 2986 if( $min > $v ) $min = $v ; 2987 if( $max < $v ) $max = $v ; 2988 } 2989 } 2990 return $flg ? array($min,$max) : false ; 2991 } 2992 2993 // Get min and max values for all included plots 2994 function GetPlotsYMinMax($aPlots) { 2995 $n = count($aPlots); 2996 $i=0; 2997 do { 2998 list($xmax,$max) = $aPlots[$i]->Max(); 2999 } while( ++$i < $n && !is_numeric($max) ); 3000 3001 $i=0; 3002 do { 3003 list($xmin,$min) = $aPlots[$i]->Min(); 3004 } while( ++$i < $n && !is_numeric($min) ); 3005 3006 if( !is_numeric($min) || !is_numeric($max) ) { 3007 JpGraphError::RaiseL(25044);//('Cannot use autoscaling since it is impossible to determine a valid min/max value of the Y-axis (only null values).'); 3008 } 3009 3010 for($i=0; $i < $n; ++$i ) { 3011 list($xmax,$ymax)=$aPlots[$i]->Max(); 3012 list($xmin,$ymin)=$aPlots[$i]->Min(); 3013 if (is_numeric($ymax)) $max=max($max,$ymax); 3014 if (is_numeric($ymin)) $min=min($min,$ymin); 3015 } 3016 if( $min == '' ) $min = 0; 3017 if( $max == '' ) $max = 0; 3018 if( $min == 0 && $max == 0 ) { 3019 // Special case if all values are 0 3020 $min=0;$max=1; 3021 } 3022 return array($min,$max); 3023 } 3024 3025} // Class 3026 3027 3028//=================================================== 3029// CLASS TTF 3030// Description: Handle TTF font names 3031//=================================================== 3032class TTF { 3033 private $font_files,$style_names; 3034//--------------- 3035// CONSTRUCTOR 3036 function TTF() { 3037 $this->style_names=array(FS_NORMAL=>'normal',FS_BOLD=>'bold',FS_ITALIC=>'italic',FS_BOLDITALIC=>'bolditalic'); 3038 // File names for available fonts 3039 $this->font_files=array( 3040 FF_COURIER => array(FS_NORMAL=>'cour.ttf', FS_BOLD=>'courbd.ttf', FS_ITALIC=>'couri.ttf', FS_BOLDITALIC=>'courbi.ttf' ), 3041 FF_GEORGIA => array(FS_NORMAL=>'georgia.ttf', FS_BOLD=>'georgiab.ttf', FS_ITALIC=>'georgiai.ttf', FS_BOLDITALIC=>'' ), 3042 FF_TREBUCHE =>array(FS_NORMAL=>'trebuc.ttf', FS_BOLD=>'trebucbd.ttf', FS_ITALIC=>'trebucit.ttf', FS_BOLDITALIC=>'trebucbi.ttf' ), 3043 FF_VERDANA => array(FS_NORMAL=>'verdana.ttf', FS_BOLD=>'verdanab.ttf', FS_ITALIC=>'verdanai.ttf', FS_BOLDITALIC=>'' ), 3044 FF_TIMES => array(FS_NORMAL=>'times.ttf', FS_BOLD=>'timesbd.ttf', FS_ITALIC=>'timesi.ttf', FS_BOLDITALIC=>'timesbi.ttf' ), 3045 FF_COMIC => array(FS_NORMAL=>'comic.ttf', FS_BOLD=>'comicbd.ttf', FS_ITALIC=>'', FS_BOLDITALIC=>'' ), 3046 FF_ARIAL => array(FS_NORMAL=>'arial.ttf', FS_BOLD=>'arialbd.ttf', FS_ITALIC=>'ariali.ttf', FS_BOLDITALIC=>'arialbi.ttf' ) , 3047 FF_VERA => array(FS_NORMAL=>'Vera.ttf', FS_BOLD=>'VeraBd.ttf', FS_ITALIC=>'VeraIt.ttf', FS_BOLDITALIC=>'VeraBI.ttf' ), 3048 FF_VERAMONO => array(FS_NORMAL=>'VeraMono.ttf', FS_BOLD=>'VeraMoBd.ttf', FS_ITALIC=>'VeraMoIt.ttf', FS_BOLDITALIC=>'VeraMoBI.ttf' ), 3049 FF_VERASERIF => array(FS_NORMAL=>'VeraSe.ttf', FS_BOLD=>'VeraSeBd.ttf', FS_ITALIC=>'', FS_BOLDITALIC=>'' ) , 3050 FF_SIMSUN => array(FS_NORMAL=>'simsun.ttc', FS_BOLD=>'simhei.ttf', FS_ITALIC=>'', FS_BOLDITALIC=>'' ), 3051 FF_CHINESE => array(FS_NORMAL=>CHINESE_TTF_FONT, FS_BOLD=>'', FS_ITALIC=>'', FS_BOLDITALIC=>'' ), 3052 FF_MINCHO => array(FS_NORMAL=>MINCHO_TTF_FONT, FS_BOLD=>'', FS_ITALIC=>'', FS_BOLDITALIC=>'' ), 3053 FF_PMINCHO => array(FS_NORMAL=>PMINCHO_TTF_FONT, FS_BOLD=>'', FS_ITALIC=>'', FS_BOLDITALIC=>'' ), 3054 FF_GOTHIC => array(FS_NORMAL=>GOTHIC_TTF_FONT, FS_BOLD=>'', FS_ITALIC=>'', FS_BOLDITALIC=>'' ), 3055 FF_PGOTHIC => array(FS_NORMAL=>PGOTHIC_TTF_FONT, FS_BOLD=>'', FS_ITALIC=>'', FS_BOLDITALIC=>'' ), 3056 FF_MINCHO => array(FS_NORMAL=>PMINCHO_TTF_FONT, FS_BOLD=>'', FS_ITALIC=>'', FS_BOLDITALIC=>'' ) 3057); 3058 } 3059 3060//--------------- 3061// PUBLIC METHODS 3062 // Create the TTF file from the font specification 3063 function File($family,$style=FS_NORMAL) { 3064 $fam = @$this->font_files[$family]; 3065 if( !$fam ) { 3066 JpGraphError::RaiseL(25046,$family);//("Specified TTF font family (id=$family) is unknown or does not exist. Please note that TTF fonts are not distributed with JpGraph for copyright reasons. You can find the MS TTF WEB-fonts (arial, courier etc) for download at http://corefonts.sourceforge.net/"); 3067 } 3068 $f = @$fam[$style]; 3069 3070 if( $f==='' ) 3071 JpGraphError::RaiseL(25047,$this->style_names[$style],$this->font_files[$family][FS_NORMAL]);//('Style "'.$this->style_names[$style].'" is not available for font family '.$this->font_files[$family][FS_NORMAL].'.'); 3072 if( !$f ) { 3073 JpGraphError::RaiseL(25048,$fam);//("Unknown font style specification [$fam]."); 3074 } 3075 3076 if ($family >= FF_MINCHO && $family <= FF_PGOTHIC) { 3077 $f = MBTTF_DIR.$f; 3078 } else { 3079 $f = TTF_DIR.$f; 3080 } 3081 3082 if( file_exists($f) === false || is_readable($f) === false ) { 3083 JpGraphError::RaiseL(25049,$f);//("Font file \"$f\" is not readable or does not exist."); 3084 } 3085 return $f; 3086 } 3087} // Class 3088 3089//=================================================== 3090// CLASS LineProperty 3091// Description: Holds properties for a line 3092//=================================================== 3093class LineProperty { 3094 public $iWeight=1, $iColor="black",$iStyle="solid",$iShow=true; 3095 3096//--------------- 3097// PUBLIC METHODS 3098 function SetColor($aColor) { 3099 $this->iColor = $aColor; 3100 } 3101 3102 function SetWeight($aWeight) { 3103 $this->iWeight = $aWeight; 3104 } 3105 3106 function SetStyle($aStyle) { 3107 $this->iStyle = $aStyle; 3108 } 3109 3110 function Show($aShow=true) { 3111 $this->iShow=$aShow; 3112 } 3113 3114 function Stroke($aImg,$aX1,$aY1,$aX2,$aY2) { 3115 if( $this->iShow ) { 3116 $aImg->PushColor($this->iColor); 3117 $oldls = $aImg->line_style; 3118 $oldlw = $aImg->line_weight; 3119 $aImg->SetLineWeight($this->iWeight); 3120 $aImg->SetLineStyle($this->iStyle); 3121 $aImg->StyleLine($aX1,$aY1,$aX2,$aY2); 3122 $aImg->PopColor($this->iColor); 3123 $aImg->line_style = $oldls; 3124 $aImg->line_weight = $oldlw; 3125 3126 } 3127 } 3128} 3129 3130 3131//=================================================== 3132// CLASS Text 3133// Description: Arbitrary text object that can be added to the graph 3134//=================================================== 3135class Text { 3136 public $t,$margin=0; 3137 public $x=0,$y=0,$halign="left",$valign="top",$color=array(0,0,0); 3138 public $hide=false, $dir=0; 3139 public $iScalePosY=null,$iScalePosX=null; 3140 public $iWordwrap=0; 3141 protected $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12; 3142 protected $boxed=false; // Should the text be boxed 3143 protected $paragraph_align="left"; 3144 protected $icornerradius=0,$ishadowwidth=3; 3145 protected $fcolor='white',$bcolor='black',$shadow=false; 3146 protected $iCSIMarea='',$iCSIMalt='',$iCSIMtarget=''; 3147 3148//--------------- 3149// CONSTRUCTOR 3150 3151 // Create new text at absolute pixel coordinates 3152 function Text($aTxt="",$aXAbsPos=0,$aYAbsPos=0) { 3153 if( ! is_string($aTxt) ) { 3154 JpGraphError::RaiseL(25050);//('First argument to Text::Text() must be s atring.'); 3155 } 3156 $this->t = $aTxt; 3157 $this->x = round($aXAbsPos); 3158 $this->y = round($aYAbsPos); 3159 $this->margin = 0; 3160 } 3161//--------------- 3162// PUBLIC METHODS 3163 // Set the string in the text object 3164 function Set($aTxt) { 3165 $this->t = $aTxt; 3166 } 3167 3168 // Alias for Pos() 3169 function SetPos($aXAbsPos=0,$aYAbsPos=0,$aHAlign="left",$aVAlign="top") { 3170 //$this->Pos($aXAbsPos,$aYAbsPos,$aHAlign,$aVAlign); 3171 $this->x = $aXAbsPos; 3172 $this->y = $aYAbsPos; 3173 $this->halign = $aHAlign; 3174 $this->valign = $aVAlign; 3175 } 3176 3177 function SetScalePos($aX,$aY) { 3178 $this->iScalePosX = $aX; 3179 $this->iScalePosY = $aY; 3180 } 3181 3182 // Specify alignment for the text 3183 function Align($aHAlign,$aVAlign="top",$aParagraphAlign="") { 3184 $this->halign = $aHAlign; 3185 $this->valign = $aVAlign; 3186 if( $aParagraphAlign != "" ) 3187 $this->paragraph_align = $aParagraphAlign; 3188 } 3189 3190 // Alias 3191 function SetAlign($aHAlign,$aVAlign="top",$aParagraphAlign="") { 3192 $this->Align($aHAlign,$aVAlign,$aParagraphAlign); 3193 } 3194 3195 // Specifies the alignment for a multi line text 3196 function ParagraphAlign($aAlign) { 3197 $this->paragraph_align = $aAlign; 3198 } 3199 3200 // Specifies the alignment for a multi line text 3201 function SetParagraphAlign($aAlign) { 3202 $this->paragraph_align = $aAlign; 3203 } 3204 3205 function SetShadow($aShadowColor='gray',$aShadowWidth=3) { 3206 $this->ishadowwidth=$aShadowWidth; 3207 $this->shadow=$aShadowColor; 3208 $this->boxed=true; 3209 } 3210 3211 function SetWordWrap($aCol) { 3212 $this->iWordwrap = $aCol ; 3213 } 3214 3215 // Specify that the text should be boxed. fcolor=frame color, bcolor=border color, 3216 // $shadow=drop shadow should be added around the text. 3217 function SetBox($aFrameColor=array(255,255,255),$aBorderColor=array(0,0,0),$aShadowColor=false,$aCornerRadius=4,$aShadowWidth=3) { 3218 if( $aFrameColor==false ) 3219 $this->boxed=false; 3220 else 3221 $this->boxed=true; 3222 $this->fcolor=$aFrameColor; 3223 $this->bcolor=$aBorderColor; 3224 // For backwards compatibility when shadow was just true or false 3225 if( $aShadowColor === true ) 3226 $aShadowColor = 'gray'; 3227 $this->shadow=$aShadowColor; 3228 $this->icornerradius=$aCornerRadius; 3229 $this->ishadowwidth=$aShadowWidth; 3230 } 3231 3232 // Hide the text 3233 function Hide($aHide=true) { 3234 $this->hide=$aHide; 3235 } 3236 3237 // This looks ugly since it's not a very orthogonal design 3238 // but I added this "inverse" of Hide() to harmonize 3239 // with some classes which I designed more recently (especially) 3240 // jpgraph_gantt 3241 function Show($aShow=true) { 3242 $this->hide=!$aShow; 3243 } 3244 3245 // Specify font 3246 function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) { 3247 $this->font_family=$aFamily; 3248 $this->font_style=$aStyle; 3249 $this->font_size=$aSize; 3250 } 3251 3252 // Center the text between $left and $right coordinates 3253 function Center($aLeft,$aRight,$aYAbsPos=false) { 3254 $this->x = $aLeft + ($aRight-$aLeft )/2; 3255 $this->halign = "center"; 3256 if( is_numeric($aYAbsPos) ) 3257 $this->y = $aYAbsPos; 3258 } 3259 3260 // Set text color 3261 function SetColor($aColor) { 3262 $this->color = $aColor; 3263 } 3264 3265 function SetAngle($aAngle) { 3266 $this->SetOrientation($aAngle); 3267 } 3268 3269 // Orientation of text. Note only TTF fonts can have an arbitrary angle 3270 function SetOrientation($aDirection=0) { 3271 if( is_numeric($aDirection) ) 3272 $this->dir=$aDirection; 3273 elseif( $aDirection=="h" ) 3274 $this->dir = 0; 3275 elseif( $aDirection=="v" ) 3276 $this->dir = 90; 3277 else JpGraphError::RaiseL(25051);//(" Invalid direction specified for text."); 3278 } 3279 3280 // Total width of text 3281 function GetWidth($aImg) { 3282 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size); 3283 $w = $aImg->GetTextWidth($this->t,$this->dir); 3284 return $w; 3285 } 3286 3287 // Hight of font 3288 function GetFontHeight($aImg) { 3289 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size); 3290 $h = $aImg->GetFontHeight(); 3291 return $h; 3292 3293 } 3294 3295 function GetTextHeight($aImg) { 3296 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size); 3297 $h = $aImg->GetTextHeight($this->t,$this->dir); 3298 return $h; 3299 } 3300 3301 function GetHeight($aImg) { 3302 // Synonym for GetTextHeight() 3303 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size); 3304 $h = $aImg->GetTextHeight($this->t,$this->dir); 3305 return $h; 3306 } 3307 3308 // Set the margin which will be interpretated differently depending 3309 // on the context. 3310 function SetMargin($aMarg) { 3311 $this->margin = $aMarg; 3312 } 3313 3314 function StrokeWithScale($aImg,$axscale,$ayscale) { 3315 if( $this->iScalePosX === null || 3316 $this->iScalePosY === null ) { 3317 $this->Stroke($aImg); 3318 } 3319 else { 3320 $this->Stroke($aImg, 3321 round($axscale->Translate($this->iScalePosX)), 3322 round($ayscale->Translate($this->iScalePosY))); 3323 } 3324 } 3325 3326 function SetCSIMTarget($aTarget,$aAlt=null) { 3327 $this->iCSIMtarget = $aTarget; 3328 $this->iCSIMalt = $aAlt; 3329 } 3330 3331 function GetCSIMareas() { 3332 if( $this->iCSIMtarget !== '' ) 3333 return $this->iCSIMarea; 3334 else 3335 return ''; 3336 } 3337 3338 // Display text in image 3339 function Stroke($aImg,$x=null,$y=null) { 3340 3341 if( !empty($x) ) $this->x = round($x); 3342 if( !empty($y) ) $this->y = round($y); 3343 3344 // Insert newlines 3345 if( $this->iWordwrap > 0 ) { 3346 $this->t = wordwrap($this->t,$this->iWordwrap,"\n"); 3347 } 3348 3349 // If position been given as a fraction of the image size 3350 // calculate the absolute position 3351 if( $this->x < 1 && $this->x > 0 ) $this->x *= $aImg->width; 3352 if( $this->y < 1 && $this->y > 0 ) $this->y *= $aImg->height; 3353 3354 $aImg->PushColor($this->color); 3355 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size); 3356 $aImg->SetTextAlign($this->halign,$this->valign); 3357 if( $this->boxed ) { 3358 if( $this->fcolor=="nofill" ) 3359 $this->fcolor=false; 3360 $aImg->SetLineWeight(1); 3361 $bbox = $aImg->StrokeBoxedText($this->x,$this->y,$this->t, 3362 $this->dir,$this->fcolor,$this->bcolor,$this->shadow, 3363 $this->paragraph_align,5,5,$this->icornerradius, 3364 $this->ishadowwidth); 3365 } 3366 else { 3367 $bbox = $aImg->StrokeText($this->x,$this->y,$this->t,$this->dir,$this->paragraph_align); 3368 } 3369 3370 // Create CSIM targets 3371 $coords = $bbox[0].','.$bbox[1].','.$bbox[2].','.$bbox[3].','.$bbox[4].','.$bbox[5].','.$bbox[6].','.$bbox[7]; 3372 $this->iCSIMarea = "<area shape=\"poly\" coords=\"$coords\" href=\"".$this->iCSIMtarget."\""; 3373 $this->iCSIMarea .= " alt=\"".$this->iCSIMalt."\" title=\"".$this->iCSIMalt."\" />\n"; 3374 3375 $aImg->PopColor($this->color); 3376 3377 } 3378} // Class 3379 3380class GraphTabTitle extends Text{ 3381 private $corner = 6 , $posx = 7, $posy = 4; 3382 private $fillcolor='lightyellow',$bordercolor='black'; 3383 private $align = 'left', $width=TABTITLE_WIDTHFIT; 3384 function GraphTabTitle() { 3385 $this->t = ''; 3386 $this->font_style = FS_BOLD; 3387 $this->hide = true; 3388 $this->color = 'darkred'; 3389 } 3390 3391 function SetColor($aTxtColor,$aFillColor='lightyellow',$aBorderColor='black') { 3392 $this->color = $aTxtColor; 3393 $this->fillcolor = $aFillColor; 3394 $this->bordercolor = $aBorderColor; 3395 } 3396 3397 function SetFillColor($aFillColor) { 3398 $this->fillcolor = $aFillColor; 3399 } 3400 3401 function SetTabAlign($aAlign) { 3402 $this->align = $aAlign; 3403 } 3404 3405 function SetWidth($aWidth) { 3406 $this->width = $aWidth ; 3407 } 3408 3409 function Set($t) { 3410 $this->t = $t; 3411 $this->hide = false; 3412 } 3413 3414 function SetCorner($aD) { 3415 $this->corner = $aD ; 3416 } 3417 3418 function Stroke($aImg,$aDummy1=null,$aDummy2=null) { 3419 if( $this->hide ) 3420 return; 3421 $this->boxed = false; 3422 $w = $this->GetWidth($aImg) + 2*$this->posx; 3423 $h = $this->GetTextHeight($aImg) + 2*$this->posy; 3424 3425 $x = $aImg->left_margin; 3426 $y = $aImg->top_margin; 3427 3428 if( $this->width === TABTITLE_WIDTHFIT ) { 3429 if( $this->align == 'left' ) { 3430 $p = array($x, $y, 3431 $x, $y-$h+$this->corner, 3432 $x + $this->corner,$y-$h, 3433 $x + $w - $this->corner, $y-$h, 3434 $x + $w, $y-$h+$this->corner, 3435 $x + $w, $y); 3436 } 3437 elseif( $this->align == 'center' ) { 3438 $x += round($aImg->plotwidth/2) - round($w/2); 3439 $p = array($x, $y, 3440 $x, $y-$h+$this->corner, 3441 $x + $this->corner, $y-$h, 3442 $x + $w - $this->corner, $y-$h, 3443 $x + $w, $y-$h+$this->corner, 3444 $x + $w, $y); 3445 } 3446 else { 3447 $x += $aImg->plotwidth -$w; 3448 $p = array($x, $y, 3449 $x, $y-$h+$this->corner, 3450 $x + $this->corner,$y-$h, 3451 $x + $w - $this->corner, $y-$h, 3452 $x + $w, $y-$h+$this->corner, 3453 $x + $w, $y); 3454 } 3455 } 3456 else { 3457 if( $this->width === TABTITLE_WIDTHFULL ) 3458 $w = $aImg->plotwidth ; 3459 else 3460 $w = $this->width ; 3461 3462 // Make the tab fit the width of the plot area 3463 $p = array($x, $y, 3464 $x, $y-$h+$this->corner, 3465 $x + $this->corner,$y-$h, 3466 $x + $w - $this->corner, $y-$h, 3467 $x + $w, $y-$h+$this->corner, 3468 $x + $w, $y); 3469 3470 } 3471 if( $this->halign == 'left' ) { 3472 $aImg->SetTextAlign('left','bottom'); 3473 $x += $this->posx; 3474 $y -= $this->posy; 3475 } 3476 elseif( $this->halign == 'center' ) { 3477 $aImg->SetTextAlign('center','bottom'); 3478 $x += $w/2; 3479 $y -= $this->posy; 3480 } 3481 else { 3482 $aImg->SetTextAlign('right','bottom'); 3483 $x += $w - $this->posx; 3484 $y -= $this->posy; 3485 } 3486 3487 $aImg->SetColor($this->fillcolor); 3488 $aImg->FilledPolygon($p); 3489 3490 $aImg->SetColor($this->bordercolor); 3491 $aImg->Polygon($p,true); 3492 3493 $aImg->SetColor($this->color); 3494 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size); 3495 $aImg->StrokeText($x,$y,$this->t,0,'center'); 3496 } 3497 3498} 3499 3500//=================================================== 3501// CLASS SuperScriptText 3502// Description: Format a superscript text 3503//=================================================== 3504class SuperScriptText extends Text { 3505 private $iSuper=""; 3506 private $sfont_family="",$sfont_style="",$sfont_size=8; 3507 private $iSuperMargin=2,$iVertOverlap=4,$iSuperScale=0.65; 3508 private $iSDir=0; 3509 private $iSimple=false; 3510 3511 function SuperScriptText($aTxt="",$aSuper="",$aXAbsPos=0,$aYAbsPos=0) { 3512 parent::Text($aTxt,$aXAbsPos,$aYAbsPos); 3513 $this->iSuper = $aSuper; 3514 } 3515 3516 function FromReal($aVal,$aPrecision=2) { 3517 // Convert a floating point number to scientific notation 3518 $neg=1.0; 3519 if( $aVal < 0 ) { 3520 $neg = -1.0; 3521 $aVal = -$aVal; 3522 } 3523 3524 $l = floor(log10($aVal)); 3525 $a = sprintf("%0.".$aPrecision."f",round($aVal / pow(10,$l),$aPrecision)); 3526 $a *= $neg; 3527 if( $this->iSimple && ($a == 1 || $a==-1) ) $a = ''; 3528 3529 if( $a != '' ) 3530 $this->t = $a.' * 10'; 3531 else { 3532 if( $neg == 1 ) 3533 $this->t = '10'; 3534 else 3535 $this->t = '-10'; 3536 } 3537 $this->iSuper = $l; 3538 } 3539 3540 function Set($aTxt,$aSuper="") { 3541 $this->t = $aTxt; 3542 $this->iSuper = $aSuper; 3543 } 3544 3545 function SetSuperFont($aFontFam,$aFontStyle=FS_NORMAL,$aFontSize=8) { 3546 $this->sfont_family = $aFontFam; 3547 $this->sfont_style = $aFontStyle; 3548 $this->sfont_size = $aFontSize; 3549 } 3550 3551 // Total width of text 3552 function GetWidth($aImg) { 3553 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size); 3554 $w = $aImg->GetTextWidth($this->t); 3555 $aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size); 3556 $w += $aImg->GetTextWidth($this->iSuper); 3557 $w += $this->iSuperMargin; 3558 return $w; 3559 } 3560 3561 // Hight of font (approximate the height of the text) 3562 function GetFontHeight($aImg) { 3563 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size); 3564 $h = $aImg->GetFontHeight(); 3565 $aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size); 3566 $h += $aImg->GetFontHeight(); 3567 return $h; 3568 } 3569 3570 // Hight of text 3571 function GetTextHeight($aImg) { 3572 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size); 3573 $h = $aImg->GetTextHeight($this->t); 3574 $aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size); 3575 $h += $aImg->GetTextHeight($this->iSuper); 3576 return $h; 3577 } 3578 3579 function Stroke($aImg,$ax=-1,$ay=-1) { 3580 3581 // To position the super script correctly we need different 3582 // cases to handle the alignmewnt specified since that will 3583 // determine how we can interpret the x,y coordinates 3584 3585 $w = parent::GetWidth($aImg); 3586 $h = parent::GetTextHeight($aImg); 3587 switch( $this->valign ) { 3588 case 'top': 3589 $sy = $this->y; 3590 break; 3591 case 'center': 3592 $sy = $this->y - $h/2; 3593 break; 3594 case 'bottom': 3595 $sy = $this->y - $h; 3596 break; 3597 default: 3598 JpGraphError::RaiseL(25052);//('PANIC: Internal error in SuperScript::Stroke(). Unknown vertical alignment for text'); 3599 exit(); 3600 } 3601 3602 switch( $this->halign ) { 3603 case 'left': 3604 $sx = $this->x + $w; 3605 break; 3606 case 'center': 3607 $sx = $this->x + $w/2; 3608 break; 3609 case 'right': 3610 $sx = $this->x; 3611 break; 3612 default: 3613 JpGraphError::RaiseL(25053);//('PANIC: Internal error in SuperScript::Stroke(). Unknown horizontal alignment for text'); 3614 exit(); 3615 } 3616 3617 $sx += $this->iSuperMargin; 3618 $sy += $this->iVertOverlap; 3619 3620 // Should we automatically determine the font or 3621 // has the user specified it explicetly? 3622 if( $this->sfont_family == "" ) { 3623 if( $this->font_family <= FF_FONT2 ) { 3624 if( $this->font_family == FF_FONT0 ) { 3625 $sff = FF_FONT0; 3626 } 3627 elseif( $this->font_family == FF_FONT1 ) { 3628 if( $this->font_style == FS_NORMAL ) 3629 $sff = FF_FONT0; 3630 else 3631 $sff = FF_FONT1; 3632 } 3633 else { 3634 $sff = FF_FONT1; 3635 } 3636 $sfs = $this->font_style; 3637 $sfz = $this->font_size; 3638 } 3639 else { 3640 // TTF fonts 3641 $sff = $this->font_family; 3642 $sfs = $this->font_style; 3643 $sfz = floor($this->font_size*$this->iSuperScale); 3644 if( $sfz < 8 ) $sfz = 8; 3645 } 3646 $this->sfont_family = $sff; 3647 $this->sfont_style = $sfs; 3648 $this->sfont_size = $sfz; 3649 } 3650 else { 3651 $sff = $this->sfont_family; 3652 $sfs = $this->sfont_style; 3653 $sfz = $this->sfont_size; 3654 } 3655 3656 parent::Stroke($aImg,$ax,$ay); 3657 3658 3659 // For the builtin fonts we need to reduce the margins 3660 // since the bounding bx reported for the builtin fonts 3661 // are much larger than for the TTF fonts. 3662 if( $sff <= FF_FONT2 ) { 3663 $sx -= 2; 3664 $sy += 3; 3665 } 3666 3667 $aImg->SetTextAlign('left','bottom'); 3668 $aImg->SetFont($sff,$sfs,$sfz); 3669 $aImg->PushColor($this->color); 3670 $aImg->StrokeText($sx,$sy,$this->iSuper,$this->iSDir,'left'); 3671 $aImg->PopColor(); 3672 } 3673} 3674 3675 3676//=================================================== 3677// CLASS Grid 3678// Description: responsible for drawing grid lines in graph 3679//=================================================== 3680class Grid { 3681 protected $img; 3682 protected $scale; 3683 protected $grid_color='#DDDDDD',$grid_mincolor='#DDDDDD'; 3684 protected $type="solid"; 3685 protected $show=false, $showMinor=false,$weight=1; 3686 protected $fill=false,$fillcolor=array('#EFEFEF','#BBCCFF'); 3687//--------------- 3688// CONSTRUCTOR 3689 function Grid($aAxis) { 3690 $this->scale = $aAxis->scale; 3691 $this->img = $aAxis->img; 3692 } 3693//--------------- 3694// PUBLIC METHODS 3695 function SetColor($aMajColor,$aMinColor=false) { 3696 $this->grid_color=$aMajColor; 3697 if( $aMinColor === false ) 3698 $aMinColor = $aMajColor ; 3699 $this->grid_mincolor = $aMinColor; 3700 } 3701 3702 function SetWeight($aWeight) { 3703 $this->weight=$aWeight; 3704 } 3705 3706 // Specify if grid should be dashed, dotted or solid 3707 function SetLineStyle($aType) { 3708 $this->type = $aType; 3709 } 3710 3711 // Decide if both major and minor grid should be displayed 3712 function Show($aShowMajor=true,$aShowMinor=false) { 3713 $this->show=$aShowMajor; 3714 $this->showMinor=$aShowMinor; 3715 } 3716 3717 function SetFill($aFlg=true,$aColor1='lightgray',$aColor2='lightblue') { 3718 $this->fill = $aFlg; 3719 $this->fillcolor = array( $aColor1, $aColor2 ); 3720 } 3721 3722 // Display the grid 3723 function Stroke() { 3724 if( $this->showMinor ) { 3725 $tmp = $this->grid_color; 3726 $this->grid_color = $this->grid_mincolor; 3727 $this->DoStroke($this->scale->ticks->ticks_pos); 3728 3729 $this->grid_color = $tmp; 3730 $this->DoStroke($this->scale->ticks->maj_ticks_pos); 3731 } 3732 else { 3733 $this->DoStroke($this->scale->ticks->maj_ticks_pos); 3734 } 3735 } 3736 3737//-------------- 3738// Private methods 3739 // Draw the grid 3740 function DoStroke($aTicksPos) { 3741 if( !$this->show ) 3742 return; 3743 $nbrgrids = count($aTicksPos); 3744 3745 if( $this->scale->type=="y" ) { 3746 $xl=$this->img->left_margin; 3747 $xr=$this->img->width-$this->img->right_margin; 3748 3749 if( $this->fill ) { 3750 // Draw filled areas 3751 $y2 = $aTicksPos[0]; 3752 $i=1; 3753 while( $i < $nbrgrids ) { 3754 $y1 = $y2; 3755 $y2 = $aTicksPos[$i++]; 3756 $this->img->SetColor($this->fillcolor[$i & 1]); 3757 $this->img->FilledRectangle($xl,$y1,$xr,$y2); 3758 } 3759 } 3760 3761 $this->img->SetColor($this->grid_color); 3762 $this->img->SetLineWeight($this->weight); 3763 3764 // Draw grid lines 3765 for($i=0; $i<$nbrgrids; ++$i) { 3766 $y=$aTicksPos[$i]; 3767 if( $this->type == "solid" ) 3768 $this->img->Line($xl,$y,$xr,$y); 3769 elseif( $this->type == "dotted" ) 3770 $this->img->DashedLine($xl,$y,$xr,$y,1,6); 3771 elseif( $this->type == "dashed" ) 3772 $this->img->DashedLine($xl,$y,$xr,$y,2,4); 3773 elseif( $this->type == "longdashed" ) 3774 $this->img->DashedLine($xl,$y,$xr,$y,8,6); 3775 } 3776 } 3777 elseif( $this->scale->type=="x" ) { 3778 $yu=$this->img->top_margin; 3779 $yl=$this->img->height-$this->img->bottom_margin; 3780 $limit=$this->img->width-$this->img->right_margin; 3781 3782 if( $this->fill ) { 3783 // Draw filled areas 3784 $x2 = $aTicksPos[0]; 3785 $i=1; 3786 while( $i < $nbrgrids ) { 3787 $x1 = $x2; 3788 $x2 = min($aTicksPos[$i++],$limit) ; 3789 $this->img->SetColor($this->fillcolor[$i & 1]); 3790 $this->img->FilledRectangle($x1,$yu,$x2,$yl); 3791 } 3792 } 3793 3794 $this->img->SetColor($this->grid_color); 3795 $this->img->SetLineWeight($this->weight); 3796 3797 // We must also test for limit since we might have 3798 // an offset and the number of ticks is calculated with 3799 // assumption offset==0 so we might end up drawing one 3800 // to many gridlines 3801 $i=0; 3802 $x=$aTicksPos[$i]; 3803 while( $i<count($aTicksPos) && ($x=$aTicksPos[$i]) <= $limit ) { 3804 if( $this->type == "solid" ) 3805 $this->img->Line($x,$yl,$x,$yu); 3806 elseif( $this->type == "dotted" ) 3807 $this->img->DashedLine($x,$yl,$x,$yu,1,6); 3808 elseif( $this->type == "dashed" ) 3809 $this->img->DashedLine($x,$yl,$x,$yu,2,4); 3810 elseif( $this->type == "longdashed" ) 3811 $this->img->DashedLine($x,$yl,$x,$yu,8,6); 3812 ++$i; 3813 } 3814 } 3815 else { 3816 JpGraphError::RaiseL(25054,$this->scale->type);//('Internal error: Unknown grid axis ['.$this->scale->type.']'); 3817 } 3818 return true; 3819 } 3820} // Class 3821 3822//=================================================== 3823// CLASS Axis 3824// Description: Defines X and Y axis. Notes that at the 3825// moment the code is not really good since the axis on 3826// several occasion must know wheter it's an X or Y axis. 3827// This was a design decision to make the code easier to 3828// follow. 3829//=================================================== 3830class AxisPrototype { 3831 public $scale=null; 3832 public $img=null; 3833 public $hide=false,$hide_labels=false; 3834 public $title=null; 3835 public $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12,$label_angle=0; 3836 public $tick_step=1; 3837 public $pos = false; 3838 protected $weight=1; 3839 protected $color=array(0,0,0),$label_color=array(0,0,0); 3840 protected $ticks_label=false, $ticks_label_colors=null; 3841 protected $show_first_label=true,$show_last_label=true; 3842 protected $label_step=1; // Used by a text axis to specify what multiple of major steps 3843 // should be labeled. 3844 protected $labelPos=0; // Which side of the axis should the labels be? 3845 protected $title_adjust,$title_margin,$title_side=SIDE_LEFT; 3846 protected $tick_label_margin=5; 3847 protected $label_halign = '',$label_valign = '', $label_para_align='left'; 3848 protected $hide_line=false; 3849 protected $iDeltaAbsPos=0; 3850 3851 3852//--------------- 3853// CONSTRUCTOR 3854 function Axis($img,$aScale,$color=array(0,0,0)) { 3855 $this->img = $img; 3856 $this->scale = $aScale; 3857 $this->color = $color; 3858 $this->title=new Text(""); 3859 3860 if( $aScale->type=="y" ) { 3861 $this->title_margin = 25; 3862 $this->title_adjust="middle"; 3863 $this->title->SetOrientation(90); 3864 $this->tick_label_margin=7; 3865 $this->labelPos=SIDE_LEFT; 3866 } 3867 else { 3868 $this->title_margin = 5; 3869 $this->title_adjust="high"; 3870 $this->title->SetOrientation(0); 3871 $this->tick_label_margin=5; 3872 $this->labelPos=SIDE_DOWN; 3873 $this->title_side=SIDE_DOWN; 3874 } 3875 } 3876//--------------- 3877// PUBLIC METHODS 3878 3879 function SetLabelFormat($aFormStr) { 3880 $this->scale->ticks->SetLabelFormat($aFormStr); 3881 } 3882 3883 function SetLabelFormatString($aFormStr,$aDate=false) { 3884 $this->scale->ticks->SetLabelFormat($aFormStr,$aDate); 3885 } 3886 3887 function SetLabelFormatCallback($aFuncName) { 3888 $this->scale->ticks->SetFormatCallback($aFuncName); 3889 } 3890 3891 function SetLabelAlign($aHAlign,$aVAlign="top",$aParagraphAlign='left') { 3892 $this->label_halign = $aHAlign; 3893 $this->label_valign = $aVAlign; 3894 $this->label_para_align = $aParagraphAlign; 3895 } 3896 3897 // Don't display the first label 3898 function HideFirstTickLabel($aShow=false) { 3899 $this->show_first_label=$aShow; 3900 } 3901 3902 function HideLastTickLabel($aShow=false) { 3903 $this->show_last_label=$aShow; 3904 } 3905 3906 // Manually specify the major and (optional) minor tick position and labels 3907 function SetTickPositions($aMajPos,$aMinPos=NULL,$aLabels=NULL) { 3908 $this->scale->ticks->SetTickPositions($aMajPos,$aMinPos,$aLabels); 3909 } 3910 3911 // Manually specify major tick positions and optional labels 3912 function SetMajTickPositions($aMajPos,$aLabels=NULL) { 3913 $this->scale->ticks->SetTickPositions($aMajPos,NULL,$aLabels); 3914 } 3915 3916 // Hide minor or major tick marks 3917 function HideTicks($aHideMinor=true,$aHideMajor=true) { 3918 $this->scale->ticks->SupressMinorTickMarks($aHideMinor); 3919 $this->scale->ticks->SupressTickMarks($aHideMajor); 3920 } 3921 3922 // Hide zero label 3923 function HideZeroLabel($aFlag=true) { 3924 $this->scale->ticks->SupressZeroLabel(); 3925 } 3926 3927 function HideFirstLastLabel() { 3928 // The two first calls to ticks method will supress 3929 // automatically generated scale values. However, that 3930 // will not affect manually specified value, e.g text-scales. 3931 // therefor we also make a kludge here to supress manually 3932 // specified scale labels. 3933 $this->scale->ticks->SupressLast(); 3934 $this->scale->ticks->SupressFirst(); 3935 $this->show_first_label = false; 3936 $this->show_last_label = false; 3937 } 3938 3939 // Hide the axis 3940 function Hide($aHide=true) { 3941 $this->hide=$aHide; 3942 } 3943 3944 // Hide the actual axis-line, but still print the labels 3945 function HideLine($aHide=true) { 3946 $this->hide_line = $aHide; 3947 } 3948 3949 function HideLabels($aHide=true) { 3950 $this->hide_labels = $aHide; 3951 } 3952 3953 3954 // Weight of axis 3955 function SetWeight($aWeight) { 3956 $this->weight = $aWeight; 3957 } 3958 3959 // Axis color 3960 function SetColor($aColor,$aLabelColor=false) { 3961 $this->color = $aColor; 3962 if( !$aLabelColor ) $this->label_color = $aColor; 3963 else $this->label_color = $aLabelColor; 3964 } 3965 3966 // Title on axis 3967 function SetTitle($aTitle,$aAdjustAlign="high") { 3968 $this->title->Set($aTitle); 3969 $this->title_adjust=$aAdjustAlign; 3970 } 3971 3972 // Specify distance from the axis 3973 function SetTitleMargin($aMargin) { 3974 $this->title_margin=$aMargin; 3975 } 3976 3977 // Which side of the axis should the axis title be? 3978 function SetTitleSide($aSideOfAxis) { 3979 $this->title_side = $aSideOfAxis; 3980 } 3981 3982 // Utility function to set the direction for tick marks 3983 function SetTickDirection($aDir) { 3984 // Will be deprecated from 1.7 3985 if( ERR_DEPRECATED ) 3986 JpGraphError::RaiseL(25055);//('Axis::SetTickDirection() is deprecated. Use Axis::SetTickSide() instead'); 3987 $this->scale->ticks->SetSide($aDir); 3988 } 3989 3990 function SetTickSide($aDir) { 3991 $this->scale->ticks->SetSide($aDir); 3992 } 3993 3994 // Specify text labels for the ticks. One label for each data point 3995 function SetTickLabels($aLabelArray,$aLabelColorArray=null) { 3996 $this->ticks_label = $aLabelArray; 3997 $this->ticks_label_colors = $aLabelColorArray; 3998 } 3999 4000 // How far from the axis should the labels be drawn 4001 function SetTickLabelMargin($aMargin) { 4002 if( ERR_DEPRECATED ) 4003 JpGraphError::RaiseL(25056);//('SetTickLabelMargin() is deprecated. Use Axis::SetLabelMargin() instead.'); 4004 $this->tick_label_margin=$aMargin; 4005 } 4006 4007 function SetLabelMargin($aMargin) { 4008 $this->tick_label_margin=$aMargin; 4009 } 4010 4011 // Specify that every $step of the ticks should be displayed starting 4012 // at $start 4013 // DEPRECATED FUNCTION: USE SetTextTickInterval() INSTEAD 4014 function SetTextTicks($step,$start=0) { 4015 JpGraphError::RaiseL(25057);//(" SetTextTicks() is deprecated. Use SetTextTickInterval() instead."); 4016 } 4017 4018 // Specify that every $step of the ticks should be displayed starting 4019 // at $start 4020 function SetTextTickInterval($aStep,$aStart=0) { 4021 $this->scale->ticks->SetTextLabelStart($aStart); 4022 $this->tick_step=$aStep; 4023 } 4024 4025 // Specify that every $step tick mark should have a label 4026 // should be displayed starting 4027 function SetTextLabelInterval($aStep) { 4028 if( $aStep < 1 ) 4029 JpGraphError::RaiseL(25058);//(" Text label interval must be specified >= 1."); 4030 $this->label_step=$aStep; 4031 } 4032 4033 // Which side of the axis should the labels be on? 4034 function SetLabelPos($aSidePos) { 4035 // This will be deprecated from 1.7 4036 if( ERR_DEPRECATED ) 4037 JpGraphError::RaiseL(25059);//('SetLabelPos() is deprecated. Use Axis::SetLabelSide() instead.'); 4038 $this->labelPos=$aSidePos; 4039 } 4040 4041 function SetLabelSide($aSidePos) { 4042 $this->labelPos=$aSidePos; 4043 } 4044 4045 // Set the font 4046 function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) { 4047 $this->font_family = $aFamily; 4048 $this->font_style = $aStyle; 4049 $this->font_size = $aSize; 4050 } 4051 4052 // Position for axis line on the "other" scale 4053 function SetPos($aPosOnOtherScale) { 4054 $this->pos=$aPosOnOtherScale; 4055 } 4056 4057 // Set the position of the axis to be X-pixels delta to the right 4058 // of the max X-position (used to position the multiple Y-axis) 4059 function SetPosAbsDelta($aDelta) { 4060 $this->iDeltaAbsPos=$aDelta; 4061 } 4062 4063 // Specify the angle for the tick labels 4064 function SetLabelAngle($aAngle) { 4065 $this->label_angle = $aAngle; 4066 } 4067 4068} // Class 4069 4070 4071//=================================================== 4072// CLASS Axis 4073// Description: Defines X and Y axis. Notes that at the 4074// moment the code is not really good since the axis on 4075// several occasion must know wheter it's an X or Y axis. 4076// This was a design decision to make the code easier to 4077// follow. 4078//=================================================== 4079class Axis extends AxisPrototype { 4080 4081 function Axis($img,$aScale,$color=array(0,0,0)) { 4082 parent::Axis($img,$aScale,$color); 4083 } 4084 4085 // Stroke the axis. 4086 function Stroke($aOtherAxisScale,$aStrokeLabels=true) { 4087 if( $this->hide ) return; 4088 if( is_numeric($this->pos) ) { 4089 $pos=$aOtherAxisScale->Translate($this->pos); 4090 } 4091 else { // Default to minimum of other scale if pos not set 4092 if( ($aOtherAxisScale->GetMinVal() >= 0 && $this->pos==false) || $this->pos=="min" ) { 4093 $pos = $aOtherAxisScale->scale_abs[0]; 4094 } 4095 elseif($this->pos == "max") { 4096 $pos = $aOtherAxisScale->scale_abs[1]; 4097 } 4098 else { // If negative set x-axis at 0 4099 $this->pos=0; 4100 $pos=$aOtherAxisScale->Translate(0); 4101 } 4102 } 4103 $pos += $this->iDeltaAbsPos; 4104 $this->img->SetLineWeight($this->weight); 4105 $this->img->SetColor($this->color); 4106 $this->img->SetFont($this->font_family,$this->font_style,$this->font_size); 4107 if( $this->scale->type == "x" ) { 4108 if( !$this->hide_line ) 4109 $this->img->FilledRectangle($this->img->left_margin,$pos, 4110 $this->img->width-$this->img->right_margin,$pos+$this->weight-1); 4111 if( $this->title_side == SIDE_DOWN ) { 4112 $y = $pos + $this->img->GetFontHeight() + $this->title_margin + $this->title->margin; 4113 $yalign = 'top'; 4114 } 4115 else { 4116 $y = $pos - $this->img->GetFontHeight() - $this->title_margin - $this->title->margin; 4117 $yalign = 'bottom'; 4118 } 4119 4120 if( $this->title_adjust=='high' ) 4121 $this->title->SetPos($this->img->width-$this->img->right_margin,$y,'right',$yalign); 4122 elseif( $this->title_adjust=='middle' || $this->title_adjust=='center' ) 4123 $this->title->SetPos(($this->img->width-$this->img->left_margin-$this->img->right_margin)/2+$this->img->left_margin,$y,'center',$yalign); 4124 elseif($this->title_adjust=='low') 4125 $this->title->SetPos($this->img->left_margin,$y,'left',$yalign); 4126 else { 4127 JpGraphError::RaiseL(25060,$this->title_adjust);//('Unknown alignment specified for X-axis title. ('.$this->title_adjust.')'); 4128 } 4129 } 4130 elseif( $this->scale->type == "y" ) { 4131 // Add line weight to the height of the axis since 4132 // the x-axis could have a width>1 and we want the axis to fit nicely together. 4133 if( !$this->hide_line ) 4134 $this->img->FilledRectangle($pos-$this->weight+1,$this->img->top_margin, 4135 $pos,$this->img->height-$this->img->bottom_margin+$this->weight-1); 4136 $x=$pos ; 4137 if( $this->title_side == SIDE_LEFT ) { 4138 $x -= $this->title_margin; 4139 $x -= $this->title->margin; 4140 $halign="right"; 4141 } 4142 else { 4143 $x += $this->title_margin; 4144 $x += $this->title->margin; 4145 $halign="left"; 4146 } 4147 // If the user has manually specified an hor. align 4148 // then we override the automatic settings with this 4149 // specifed setting. Since default is 'left' we compare 4150 // with that. (This means a manually set 'left' align 4151 // will have no effect.) 4152 if( $this->title->halign != 'left' ) 4153 $halign = $this->title->halign; 4154 if( $this->title_adjust=="high" ) 4155 $this->title->SetPos($x,$this->img->top_margin,$halign,"top"); 4156 elseif($this->title_adjust=="middle" || $this->title_adjust=="center") 4157 $this->title->SetPos($x,($this->img->height-$this->img->top_margin-$this->img->bottom_margin)/2+$this->img->top_margin,$halign,"center"); 4158 elseif($this->title_adjust=="low") 4159 $this->title->SetPos($x,$this->img->height-$this->img->bottom_margin,$halign,"bottom"); 4160 else 4161 JpGraphError::RaiseL(25061,$this->title_adjust);//('Unknown alignment specified for Y-axis title. ('.$this->title_adjust.')'); 4162 4163 } 4164 $this->scale->ticks->Stroke($this->img,$this->scale,$pos); 4165 if( $aStrokeLabels ) { 4166 if( !$this->hide_labels ) 4167 $this->StrokeLabels($pos); 4168 $this->title->Stroke($this->img); 4169 } 4170 } 4171 4172//--------------- 4173// PRIVATE METHODS 4174 // Draw all the tick labels on major tick marks 4175 function StrokeLabels($aPos,$aMinor=false,$aAbsLabel=false) { 4176 4177 $this->img->SetColor($this->label_color); 4178 $this->img->SetFont($this->font_family,$this->font_style,$this->font_size); 4179 $yoff=$this->img->GetFontHeight()/2; 4180 4181 // Only draw labels at major tick marks 4182 $nbr = count($this->scale->ticks->maj_ticks_label); 4183 4184 // We have the option to not-display the very first mark 4185 // (Usefull when the first label might interfere with another 4186 // axis.) 4187 $i = $this->show_first_label ? 0 : 1 ; 4188 if( !$this->show_last_label ) --$nbr; 4189 // Now run through all labels making sure we don't overshoot the end 4190 // of the scale. 4191 $ncolor=0; 4192 if( isset($this->ticks_label_colors) ) 4193 $ncolor=count($this->ticks_label_colors); 4194 while( $i<$nbr ) { 4195 // $tpos holds the absolute text position for the label 4196 $tpos=$this->scale->ticks->maj_ticklabels_pos[$i]; 4197 4198 // Note. the $limit is only used for the x axis since we 4199 // might otherwise overshoot if the scale has been centered 4200 // This is due to us "loosing" the last tick mark if we center. 4201 if( $this->scale->type=="x" && $tpos > $this->img->width-$this->img->right_margin+1 ) { 4202 return; 4203 } 4204 // we only draw every $label_step label 4205 if( ($i % $this->label_step)==0 ) { 4206 4207 // Set specific label color if specified 4208 if( $ncolor > 0 ) 4209 $this->img->SetColor($this->ticks_label_colors[$i % $ncolor]); 4210 4211 // If the label has been specified use that and in other case 4212 // just label the mark with the actual scale value 4213 $m=$this->scale->ticks->GetMajor(); 4214 4215 // ticks_label has an entry for each data point and is the array 4216 // that holds the labels set by the user. If the user hasn't 4217 // specified any values we use whats in the automatically asigned 4218 // labels in the maj_ticks_label 4219 if( isset($this->ticks_label[$i*$m]) ) 4220 $label=$this->ticks_label[$i*$m]; 4221 else { 4222 if( $aAbsLabel ) 4223 $label=abs($this->scale->ticks->maj_ticks_label[$i]); 4224 else 4225 $label=$this->scale->ticks->maj_ticks_label[$i]; 4226 if( $this->scale->textscale && $this->scale->ticks->label_formfunc == '' ) { 4227 ++$label; 4228 } 4229 } 4230 4231 if( $this->scale->type == "x" ) { 4232 if( $this->labelPos == SIDE_DOWN ) { 4233 if( $this->label_angle==0 || $this->label_angle==90 ) { 4234 if( $this->label_halign=='' && $this->label_valign=='') 4235 $this->img->SetTextAlign('center','top'); 4236 else 4237 $this->img->SetTextAlign($this->label_halign,$this->label_valign); 4238 4239 } 4240 else { 4241 if( $this->label_halign=='' && $this->label_valign=='') 4242 $this->img->SetTextAlign("right","top"); 4243 else 4244 $this->img->SetTextAlign($this->label_halign,$this->label_valign); 4245 } 4246 $this->img->StrokeText($tpos,$aPos+$this->tick_label_margin,$label, 4247 $this->label_angle,$this->label_para_align); 4248 } 4249 else { 4250 if( $this->label_angle==0 || $this->label_angle==90 ) { 4251 if( $this->label_halign=='' && $this->label_valign=='') 4252 $this->img->SetTextAlign("center","bottom"); 4253 else 4254 $this->img->SetTextAlign($this->label_halign,$this->label_valign); 4255 } 4256 else { 4257 if( $this->label_halign=='' && $this->label_valign=='') 4258 $this->img->SetTextAlign("right","bottom"); 4259 else 4260 $this->img->SetTextAlign($this->label_halign,$this->label_valign); 4261 } 4262 $this->img->StrokeText($tpos,$aPos-$this->tick_label_margin,$label, 4263 $this->label_angle,$this->label_para_align); 4264 } 4265 } 4266 else { 4267 // scale->type == "y" 4268 //if( $this->label_angle!=0 ) 4269 //JpGraphError::Raise(" Labels at an angle are not supported on Y-axis"); 4270 if( $this->labelPos == SIDE_LEFT ) { // To the left of y-axis 4271 if( $this->label_halign=='' && $this->label_valign=='') 4272 $this->img->SetTextAlign("right","center"); 4273 else 4274 $this->img->SetTextAlign($this->label_halign,$this->label_valign); 4275 $this->img->StrokeText($aPos-$this->tick_label_margin,$tpos,$label,$this->label_angle,$this->label_para_align); 4276 } 4277 else { // To the right of the y-axis 4278 if( $this->label_halign=='' && $this->label_valign=='') 4279 $this->img->SetTextAlign("left","center"); 4280 else 4281 $this->img->SetTextAlign($this->label_halign,$this->label_valign); 4282 $this->img->StrokeText($aPos+$this->tick_label_margin,$tpos,$label,$this->label_angle,$this->label_para_align); 4283 } 4284 } 4285 } 4286 ++$i; 4287 } 4288 } 4289 4290} 4291 4292 4293//=================================================== 4294// CLASS Ticks 4295// Description: Abstract base class for drawing linear and logarithmic 4296// tick marks on axis 4297//=================================================== 4298class Ticks { 4299 public $label_formatstr=''; // C-style format string to use for labels 4300 public $label_formfunc=''; 4301 public $direction=1; // Should ticks be in(=1) the plot area or outside (=-1) 4302 public $supress_last=false,$supress_tickmarks=false,$supress_minor_tickmarks=false; 4303 4304 protected $minor_abs_size=3, $major_abs_size=5; 4305 protected $scale; 4306 protected $is_set=false; 4307 protected $precision; 4308 protected $supress_zerolabel=false,$supress_first=false; 4309 protected $mincolor="",$majcolor=""; 4310 protected $weight=1; 4311 protected $label_dateformatstr=''; 4312 protected $label_usedateformat=FALSE; 4313 4314//--------------- 4315// CONSTRUCTOR 4316 function Ticks($aScale) { 4317 $this->scale=$aScale; 4318 $this->precision = -1; 4319 } 4320 4321//--------------- 4322// PUBLIC METHODS 4323 // Set format string for automatic labels 4324 function SetLabelFormat($aFormatString,$aDate=FALSE) { 4325 $this->label_formatstr=$aFormatString; 4326 $this->label_usedateformat=$aDate; 4327 } 4328 4329 function SetLabelDateFormat($aFormatString) { 4330 $this->label_dateformatstr=$aFormatString; 4331 } 4332 4333 function SetFormatCallback($aCallbackFuncName) { 4334 $this->label_formfunc = $aCallbackFuncName; 4335 } 4336 4337 // Don't display the first zero label 4338 function SupressZeroLabel($aFlag=true) { 4339 $this->supress_zerolabel=$aFlag; 4340 } 4341 4342 // Don't display minor tick marks 4343 function SupressMinorTickMarks($aHide=true) { 4344 $this->supress_minor_tickmarks=$aHide; 4345 } 4346 4347 // Don't display major tick marks 4348 function SupressTickMarks($aHide=true) { 4349 $this->supress_tickmarks=$aHide; 4350 } 4351 4352 // Hide the first tick mark 4353 function SupressFirst($aHide=true) { 4354 $this->supress_first=$aHide; 4355 } 4356 4357 // Hide the last tick mark 4358 function SupressLast($aHide=true) { 4359 $this->supress_last=$aHide; 4360 } 4361 4362 // Size (in pixels) of minor tick marks 4363 function GetMinTickAbsSize() { 4364 return $this->minor_abs_size; 4365 } 4366 4367 // Size (in pixels) of major tick marks 4368 function GetMajTickAbsSize() { 4369 return $this->major_abs_size; 4370 } 4371 4372 function SetSize($aMajSize,$aMinSize=3) { 4373 $this->major_abs_size = $aMajSize; 4374 $this->minor_abs_size = $aMinSize; 4375 } 4376 4377 // Have the ticks been specified 4378 function IsSpecified() { 4379 return $this->is_set; 4380 } 4381 4382 // Specify number of decimals in automatic labels 4383 // Deprecated from 1.4. Use SetFormatString() instead 4384 function SetPrecision($aPrecision) { 4385 if( ERR_DEPRECATED ) 4386 JpGraphError::RaiseL(25063);//('Ticks::SetPrecision() is deprecated. Use Ticks::SetLabelFormat() (or Ticks::SetFormatCallback()) instead'); 4387 $this->precision=$aPrecision; 4388 } 4389 4390 function SetSide($aSide) { 4391 $this->direction=$aSide; 4392 } 4393 4394 // Which side of the axis should the ticks be on 4395 function SetDirection($aSide=SIDE_RIGHT) { 4396 $this->direction=$aSide; 4397 } 4398 4399 // Set colors for major and minor tick marks 4400 function SetMarkColor($aMajorColor,$aMinorColor="") { 4401 $this->SetColor($aMajorColor,$aMinorColor); 4402 } 4403 4404 function SetColor($aMajorColor,$aMinorColor="") { 4405 $this->majcolor=$aMajorColor; 4406 4407 // If not specified use same as major 4408 if( $aMinorColor=="" ) 4409 $this->mincolor=$aMajorColor; 4410 else 4411 $this->mincolor=$aMinorColor; 4412 } 4413 4414 function SetWeight($aWeight) { 4415 $this->weight=$aWeight; 4416 } 4417 4418} // Class 4419 4420//=================================================== 4421// CLASS LinearTicks 4422// Description: Draw linear ticks on axis 4423//=================================================== 4424class LinearTicks extends Ticks { 4425 public $minor_step=1, $major_step=2; 4426 public $xlabel_offset=0,$xtick_offset=0; 4427 public $maj_ticks_pos = array(), $maj_ticklabels_pos = array(), 4428 $ticks_pos = array(), $maj_ticks_label = array(); 4429 private $label_offset=0; // What offset should the displayed label have 4430 // i.e should we display 0,1,2 or 1,2,3,4 or 2,3,4 etc 4431 private $text_label_start=0; 4432 private $iManualTickPos = NULL, $iManualMinTickPos = NULL, $iManualTickLabels = NULL; 4433 4434//--------------- 4435// CONSTRUCTOR 4436 function LinearTicks() { 4437 $this->precision = -1; 4438 } 4439 4440//--------------- 4441// PUBLIC METHODS 4442 4443 4444 // Return major step size in world coordinates 4445 function GetMajor() { 4446 return $this->major_step; 4447 } 4448 4449 // Return minor step size in world coordinates 4450 function GetMinor() { 4451 return $this->minor_step; 4452 } 4453 4454 // Set Minor and Major ticks (in world coordinates) 4455 function Set($aMajStep,$aMinStep=false) { 4456 if( $aMinStep==false ) 4457 $aMinStep=$aMajStep; 4458 4459 if( $aMajStep <= 0 || $aMinStep <= 0 ) { 4460 JpGraphError::RaiseL(25064); 4461//(" 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."); 4462 } 4463 4464 $this->major_step=$aMajStep; 4465 $this->minor_step=$aMinStep; 4466 $this->is_set = true; 4467 } 4468 4469 function SetMajTickPositions($aMajPos,$aLabels=NULL) { 4470 $this->SetTickPositions($aMajPos,NULL,$aLabels); 4471 } 4472 4473 function SetTickPositions($aMajPos,$aMinPos=NULL,$aLabels=NULL) { 4474 if( !is_array($aMajPos) || ($aMinPos!==NULL && !is_array($aMinPos)) ) { 4475 JpGraphError::RaiseL(25065);//('Tick positions must be specifued as an array()'); 4476 return; 4477 } 4478 $n=count($aMajPos); 4479 if( is_array($aLabels) && (count($aLabels) != $n) ) { 4480 JpGraphError::RaiseL(25066);//('When manually specifying tick positions and labels the number of labels must be the same as the number of specified ticks.'); 4481 return; 4482 } 4483 $this->iManualTickPos = $aMajPos; 4484 $this->iManualMinTickPos = $aMinPos; 4485 $this->iManualTickLabels = $aLabels; 4486 } 4487 4488 // Specify all the tick positions manually and possible also the exact labels 4489 function _doManualTickPos($aScale) { 4490 $n=count($this->iManualTickPos); 4491 $m=count($this->iManualMinTickPos); 4492 $doLbl=count($this->iManualTickLabels) > 0; 4493 $this->use_manualtickpos=true; 4494 4495 $this->maj_ticks_pos = array(); 4496 $this->maj_ticklabels_pos = array(); 4497 $this->ticks_pos = array(); 4498 4499 // Now loop through the supplied positions and translate them to screen coordinates 4500 // and store them in the maj_label_positions 4501 $minScale = $aScale->scale[0]; 4502 $maxScale = $aScale->scale[1]; 4503 $j=0; 4504 for($i=0; $i < $n ; ++$i ) { 4505 // First make sure that the first tick is not lower than the lower scale value 4506 if( !isset($this->iManualTickPos[$i]) || 4507 $this->iManualTickPos[$i] < $minScale || $this->iManualTickPos[$i] > $maxScale) { 4508 continue; 4509 } 4510 4511 4512 $this->maj_ticks_pos[$j] = $aScale->Translate($this->iManualTickPos[$i]); 4513 $this->maj_ticklabels_pos[$j] = $this->maj_ticks_pos[$j]; 4514 4515 // Set the minor tick marks the same as major if not specified 4516 if( $m <= 0 ) { 4517 $this->ticks_pos[$j] = $this->maj_ticks_pos[$j]; 4518 } 4519 4520 if( $doLbl ) { 4521 $this->maj_ticks_label[$j] = $this->iManualTickLabels[$i]; 4522 } 4523 else { 4524 $this->maj_ticks_label[$j]=$this->_doLabelFormat($this->iManualTickPos[$i],$i,$n); 4525 } 4526 ++$j; 4527 } 4528 4529 // Some sanity check 4530 if( count($this->maj_ticks_pos) < 2 ) { 4531 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.'); 4532 } 4533 4534 // Setup the minor tick marks 4535 $j=0; 4536 for($i=0; $i < $m; ++$i ) { 4537 if( empty($this->iManualMinTickPos[$i]) || 4538 $this->iManualMinTickPos[$i] < $minScale || $this->iManualMinTickPos[$i] > $maxScale) 4539 continue; 4540 $this->ticks_pos[$j] = $aScale->Translate($this->iManualMinTickPos[$i]); 4541 ++$j; 4542 } 4543 } 4544 4545 function _doAutoTickPos($aScale) { 4546 $maj_step_abs = $aScale->scale_factor*$this->major_step; 4547 $min_step_abs = $aScale->scale_factor*$this->minor_step; 4548 4549 if( $min_step_abs==0 || $maj_step_abs==0 ) { 4550 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')"); 4551 } 4552 // We need to make this an int since comparing it below 4553 // with the result from round() can give wrong result, such that 4554 // (40 < 40) == TRUE !!! 4555 $limit = (int)$aScale->scale_abs[1]; 4556 4557 if( $aScale->textscale ) { 4558 // This can only be true for a X-scale (horizontal) 4559 // Define ticks for a text scale. This is slightly different from a 4560 // normal linear type of scale since the position might be adjusted 4561 // and the labels start at on 4562 $label = (float)$aScale->GetMinVal()+$this->text_label_start+$this->label_offset; 4563 $start_abs=$aScale->scale_factor*$this->text_label_start; 4564 $nbrmajticks=ceil(($aScale->GetMaxVal()-$aScale->GetMinVal()-$this->text_label_start )/$this->major_step)+1; 4565 4566 $x = $aScale->scale_abs[0]+$start_abs+$this->xlabel_offset*$min_step_abs; 4567 for( $i=0; $label <= $aScale->GetMaxVal()+$this->label_offset; ++$i ) { 4568 // Apply format to label 4569 $this->maj_ticks_label[$i]=$this->_doLabelFormat($label,$i,$nbrmajticks); 4570 $label+=$this->major_step; 4571 4572 // The x-position of the tick marks can be different from the labels. 4573 // Note that we record the tick position (not the label) so that the grid 4574 // happen upon tick marks and not labels. 4575 $xtick=$aScale->scale_abs[0]+$start_abs+$this->xtick_offset*$min_step_abs+$i*$maj_step_abs; 4576 $this->maj_ticks_pos[$i]=$xtick; 4577 $this->maj_ticklabels_pos[$i] = round($x); 4578 $x += $maj_step_abs; 4579 } 4580 } 4581 else { 4582 $label = $aScale->GetMinVal(); 4583 $abs_pos = $aScale->scale_abs[0]; 4584 $j=0; $i=0; 4585 $step = round($maj_step_abs/$min_step_abs); 4586 if( $aScale->type == "x" ) { 4587 // For a normal linear type of scale the major ticks will always be multiples 4588 // of the minor ticks. In order to avoid any rounding issues the major ticks are 4589 // defined as every "step" minor ticks and not calculated separately 4590 $nbrmajticks=ceil(($aScale->GetMaxVal()-$aScale->GetMinVal()-$this->text_label_start )/$this->major_step)+1; 4591 while( round($abs_pos) <= $limit ) { 4592 $this->ticks_pos[] = round($abs_pos); 4593 $this->ticks_label[] = $label; 4594 if( $i % $step == 0 && $j < $nbrmajticks ) { 4595 $this->maj_ticks_pos[$j] = round($abs_pos); 4596 $this->maj_ticklabels_pos[$j] = round($abs_pos); 4597 $this->maj_ticks_label[$j]=$this->_doLabelFormat($label,$j,$nbrmajticks); 4598 ++$j; 4599 } 4600 ++$i; 4601 $abs_pos += $min_step_abs; 4602 $label+=$this->minor_step; 4603 } 4604 } 4605 elseif( $aScale->type == "y" ) { 4606 $nbrmajticks=floor(($aScale->GetMaxVal()-$aScale->GetMinVal())/$this->major_step)+1; 4607 while( round($abs_pos) >= $limit ) { 4608 $this->ticks_pos[$i] = round($abs_pos); 4609 $this->ticks_label[$i]=$label; 4610 if( $i % $step == 0 && $j < $nbrmajticks) { 4611 $this->maj_ticks_pos[$j] = round($abs_pos); 4612 $this->maj_ticklabels_pos[$j] = round($abs_pos); 4613 $this->maj_ticks_label[$j]=$this->_doLabelFormat($label,$j,$nbrmajticks); 4614 ++$j; 4615 } 4616 ++$i; 4617 $abs_pos += $min_step_abs; 4618 $label += $this->minor_step; 4619 } 4620 } 4621 } 4622 } 4623 4624 function _doLabelFormat($aVal,$aIdx,$aNbrTicks) { 4625 4626 // If precision hasn't been specified set it to a sensible value 4627 if( $this->precision==-1 ) { 4628 $t = log10($this->minor_step); 4629 if( $t > 0 ) 4630 $precision = 0; 4631 else 4632 $precision = -floor($t); 4633 } 4634 else 4635 $precision = $this->precision; 4636 4637 if( $this->label_formfunc != '' ) { 4638 $f=$this->label_formfunc; 4639 $l = call_user_func($f,$aVal); 4640 } 4641 elseif( $this->label_formatstr != '' || $this->label_dateformatstr != '' ) { 4642 if( $this->label_usedateformat ) { 4643 $l = date($this->label_formatstr,$aVal); 4644 } 4645 else { 4646 if( $this->label_dateformatstr !== '' ) 4647 $l = date($this->label_dateformatstr,$aVal); 4648 else 4649 $l = sprintf($this->label_formatstr,$aVal); 4650 } 4651 } 4652 else { 4653 $l = sprintf('%01.'.$precision.'f',round($aVal,$precision)); 4654 } 4655 4656 if( ($this->supress_zerolabel && $l==0) || ($this->supress_first && $aIdx==0) || 4657 ($this->supress_last && $aIdx==$aNbrTicks-1) ) { 4658 $l=''; 4659 } 4660 return $l; 4661 } 4662 4663 // Stroke ticks on either X or Y axis 4664 function _StrokeTicks($aImg,$aScale,$aPos) { 4665 $hor = $aScale->type == 'x'; 4666 $aImg->SetLineWeight($this->weight); 4667 4668 // We need to make this an int since comparing it below 4669 // with the result from round() can give wrong result, such that 4670 // (40 < 40) == TRUE !!! 4671 $limit = (int)$aScale->scale_abs[1]; 4672 4673 // A text scale doesn't have any minor ticks 4674 if( !$aScale->textscale ) { 4675 // Stroke minor ticks 4676 $yu = $aPos - $this->direction*$this->GetMinTickAbsSize(); 4677 $xr = $aPos + $this->direction*$this->GetMinTickAbsSize(); 4678 $n = count($this->ticks_pos); 4679 for($i=0; $i < $n; ++$i ) { 4680 if( !$this->supress_tickmarks && !$this->supress_minor_tickmarks) { 4681 if( $this->mincolor!="" ) $aImg->PushColor($this->mincolor); 4682 if( $hor ) { 4683 //if( $this->ticks_pos[$i] <= $limit ) 4684 $aImg->Line($this->ticks_pos[$i],$aPos,$this->ticks_pos[$i],$yu); 4685 } 4686 else { 4687 //if( $this->ticks_pos[$i] >= $limit ) 4688 $aImg->Line($aPos,$this->ticks_pos[$i],$xr,$this->ticks_pos[$i]); 4689 } 4690 if( $this->mincolor!="" ) $aImg->PopColor(); 4691 } 4692 } 4693 } 4694 4695 // Stroke major ticks 4696 $yu = $aPos - $this->direction*$this->GetMajTickAbsSize(); 4697 $xr = $aPos + $this->direction*$this->GetMajTickAbsSize(); 4698 $nbrmajticks=ceil(($aScale->GetMaxVal()-$aScale->GetMinVal()-$this->text_label_start )/$this->major_step)+1; 4699 $n = count($this->maj_ticks_pos); 4700 for($i=0; $i < $n ; ++$i ) { 4701 if(!($this->xtick_offset > 0 && $i==$nbrmajticks-1) && !$this->supress_tickmarks) { 4702 if( $this->majcolor!="" ) $aImg->PushColor($this->majcolor); 4703 if( $hor ) { 4704 //if( $this->maj_ticks_pos[$i] <= $limit ) 4705 $aImg->Line($this->maj_ticks_pos[$i],$aPos,$this->maj_ticks_pos[$i],$yu); 4706 } 4707 else { 4708 //if( $this->maj_ticks_pos[$i] >= $limit ) 4709 $aImg->Line($aPos,$this->maj_ticks_pos[$i],$xr,$this->maj_ticks_pos[$i]); 4710 } 4711 if( $this->majcolor!="" ) $aImg->PopColor(); 4712 } 4713 } 4714 4715 } 4716 4717 // Draw linear ticks 4718 function Stroke($aImg,$aScale,$aPos) { 4719 if( $this->iManualTickPos != NULL ) 4720 $this->_doManualTickPos($aScale); 4721 else 4722 $this->_doAutoTickPos($aScale); 4723 $this->_StrokeTicks($aImg,$aScale,$aPos, $aScale->type == 'x' ); 4724 } 4725 4726//--------------- 4727// PRIVATE METHODS 4728 // Spoecify the offset of the displayed tick mark with the tick "space" 4729 // Legal values for $o is [0,1] used to adjust where the tick marks and label 4730 // should be positioned within the major tick-size 4731 // $lo specifies the label offset and $to specifies the tick offset 4732 // this comes in handy for example in bar graphs where we wont no offset for the 4733 // tick but have the labels displayed halfway under the bars. 4734 function SetXLabelOffset($aLabelOff,$aTickOff=-1) { 4735 $this->xlabel_offset=$aLabelOff; 4736 if( $aTickOff==-1 ) // Same as label offset 4737 $this->xtick_offset=$aLabelOff; 4738 else 4739 $this->xtick_offset=$aTickOff; 4740 if( $aLabelOff>0 ) 4741 $this->SupressLast(); // The last tick wont fit 4742 } 4743 4744 // Which tick label should we start with? 4745 function SetTextLabelStart($aTextLabelOff) { 4746 $this->text_label_start=$aTextLabelOff; 4747 } 4748 4749} // Class 4750 4751//=================================================== 4752// CLASS LinearScale 4753// Description: Handle linear scaling between screen and world 4754//=================================================== 4755class LinearScale { 4756 public $textscale=false; // Just a flag to let the Plot class find out if 4757 // we are a textscale or not. This is a cludge since 4758 // this ionformatyion is availabale in Graph::axtype but 4759 // we don't have access to the graph object in the Plots 4760 // stroke method. So we let graph store the status here 4761 // when the linear scale is created. A real cludge... 4762 public $type; // is this x or y scale ? 4763 public $ticks=null; // Store ticks 4764 public $text_scale_off = 0; 4765 public $scale_abs=array(0,0); 4766 public $scale_factor; // Scale factor between world and screen 4767 public $off; // Offset between image edge and plot area 4768 public $scale=array(0,0); 4769 public $name = 'lin'; 4770 public $auto_ticks=false; // When using manual scale should the ticks be automatically set? 4771 public $world_abs_size; // Plot area size in pixels (Needed public in jpgraph_radar.php) 4772 private $world_size; // Plot area size in world coordinates 4773 private $autoscale_min=false; // Forced minimum value, auto determine max 4774 private $autoscale_max=false; // Forced maximum value, auto determine min 4775 private $gracetop=0,$gracebottom=0; 4776 private $intscale=false; // Restrict autoscale to integers 4777//--------------- 4778// CONSTRUCTOR 4779 function LinearScale($aMin=0,$aMax=0,$aType="y") { 4780 assert($aType=="x" || $aType=="y" ); 4781 assert($aMin<=$aMax); 4782 4783 $this->type=$aType; 4784 $this->scale=array($aMin,$aMax); 4785 $this->world_size=$aMax-$aMin; 4786 $this->ticks = new LinearTicks(); 4787 } 4788 4789//--------------- 4790// PUBLIC METHODS 4791 // Check if scale is set or if we should autoscale 4792 // We should do this is either scale or ticks has not been set 4793 function IsSpecified() { 4794 if( $this->GetMinVal()==$this->GetMaxVal() ) { // Scale not set 4795 return false; 4796 } 4797 return true; 4798 } 4799 4800 // Set the minimum data value when the autoscaling is used. 4801 // Usefull if you want a fix minimum (like 0) but have an 4802 // automatic maximum 4803 function SetAutoMin($aMin) { 4804 $this->autoscale_min=$aMin; 4805 } 4806 4807 // Set the minimum data value when the autoscaling is used. 4808 // Usefull if you want a fix minimum (like 0) but have an 4809 // automatic maximum 4810 function SetAutoMax($aMax) { 4811 $this->autoscale_max=$aMax; 4812 } 4813 4814 // If the user manually specifies a scale should the ticks 4815 // still be set automatically? 4816 function SetAutoTicks($aFlag=true) { 4817 $this->auto_ticks = $aFlag; 4818 } 4819 4820 // Specify scale "grace" value (top and bottom) 4821 function SetGrace($aGraceTop,$aGraceBottom=0) { 4822 if( $aGraceTop<0 || $aGraceBottom < 0 ) 4823 JpGraphError::RaiseL(25069);//(" Grace must be larger then 0"); 4824 $this->gracetop=$aGraceTop; 4825 $this->gracebottom=$aGraceBottom; 4826 } 4827 4828 // Get the minimum value in the scale 4829 function GetMinVal() { 4830 return $this->scale[0]; 4831 } 4832 4833 // get maximum value for scale 4834 function GetMaxVal() { 4835 return $this->scale[1]; 4836 } 4837 4838 // Specify a new min/max value for sclae 4839 function Update($aImg,$aMin,$aMax) { 4840 $this->scale=array($aMin,$aMax); 4841 $this->world_size=$aMax-$aMin; 4842 $this->InitConstants($aImg); 4843 } 4844 4845 // Translate between world and screen 4846 function Translate($aCoord) { 4847 if( !is_numeric($aCoord) ) { 4848 if( $aCoord != '' && $aCoord != '-' && $aCoord != 'x' ) 4849 JpGraphError::RaiseL(25070);//('Your data contains non-numeric values.'); 4850 return 0; 4851 } 4852 else { 4853 return $this->off+($aCoord - $this->scale[0]) * $this->scale_factor; 4854 } 4855 } 4856 4857 // Relative translate (don't include offset) usefull when we just want 4858 // to know the relative position (in pixels) on the axis 4859 function RelTranslate($aCoord) { 4860 if( !is_numeric($aCoord) ) { 4861 if( $aCoord != '' && $aCoord != '-' && $aCoord != 'x' ) 4862 JpGraphError::RaiseL(25070);//('Your data contains non-numeric values.'); 4863 return 0; 4864 } 4865 else { 4866 return ($aCoord - $this->scale[0]) * $this->scale_factor; 4867 } 4868 } 4869 4870 // Restrict autoscaling to only use integers 4871 function SetIntScale($aIntScale=true) { 4872 $this->intscale=$aIntScale; 4873 } 4874 4875 // Calculate an integer autoscale 4876 function IntAutoScale($img,$min,$max,$maxsteps,$majend=true) { 4877 // Make sure limits are integers 4878 $min=floor($min); 4879 $max=ceil($max); 4880 if( abs($min-$max)==0 ) { 4881 --$min; ++$max; 4882 } 4883 $maxsteps = floor($maxsteps); 4884 4885 $gracetop=round(($this->gracetop/100.0)*abs($max-$min)); 4886 $gracebottom=round(($this->gracebottom/100.0)*abs($max-$min)); 4887 if( is_numeric($this->autoscale_min) ) { 4888 $min = ceil($this->autoscale_min); 4889 if( $min >= $max ) { 4890 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.'); 4891 } 4892 } 4893 4894 if( is_numeric($this->autoscale_max) ) { 4895 $max = ceil($this->autoscale_max); 4896 if( $min >= $max ) { 4897 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.'); 4898 } 4899 } 4900 4901 if( abs($min-$max ) == 0 ) { 4902 ++$max; 4903 --$min; 4904 } 4905 4906 $min -= $gracebottom; 4907 $max += $gracetop; 4908 4909 // First get tickmarks as multiples of 1, 10, ... 4910 if( $majend ) { 4911 list($num1steps,$adj1min,$adj1max,$maj1step) = 4912 $this->IntCalcTicks($maxsteps,$min,$max,1); 4913 } 4914 else { 4915 $adj1min = $min; 4916 $adj1max = $max; 4917 list($num1steps,$maj1step) = 4918 $this->IntCalcTicksFreeze($maxsteps,$min,$max,1); 4919 } 4920 4921 if( abs($min-$max) > 2 ) { 4922 // Then get tick marks as 2:s 2, 20, ... 4923 if( $majend ) { 4924 list($num2steps,$adj2min,$adj2max,$maj2step) = 4925 $this->IntCalcTicks($maxsteps,$min,$max,5); 4926 } 4927 else { 4928 $adj2min = $min; 4929 $adj2max = $max; 4930 list($num2steps,$maj2step) = 4931 $this->IntCalcTicksFreeze($maxsteps,$min,$max,5); 4932 } 4933 } 4934 else { 4935 $num2steps = 10000; // Dummy high value so we don't choose this 4936 } 4937 4938 if( abs($min-$max) > 5 ) { 4939 // Then get tickmarks as 5:s 5, 50, 500, ... 4940 if( $majend ) { 4941 list($num5steps,$adj5min,$adj5max,$maj5step) = 4942 $this->IntCalcTicks($maxsteps,$min,$max,2); 4943 } 4944 else { 4945 $adj5min = $min; 4946 $adj5max = $max; 4947 list($num5steps,$maj5step) = 4948 $this->IntCalcTicksFreeze($maxsteps,$min,$max,2); 4949 } 4950 } 4951 else { 4952 $num5steps = 10000; // Dummy high value so we don't choose this 4953 } 4954 4955 // Check to see whichof 1:s, 2:s or 5:s fit better with 4956 // the requested number of major ticks 4957 $match1=abs($num1steps-$maxsteps); 4958 $match2=abs($num2steps-$maxsteps); 4959 if( !empty($maj5step) && $maj5step > 1 ) 4960 $match5=abs($num5steps-$maxsteps); 4961 else 4962 $match5=10000; // Dummy high value 4963 4964 // Compare these three values and see which is the closest match 4965 // We use a 0.6 weight to gravitate towards multiple of 5:s 4966 if( $match1 < $match2 ) { 4967 if( $match1 < $match5 ) 4968 $r=1; 4969 else 4970 $r=3; 4971 } 4972 else { 4973 if( $match2 < $match5 ) 4974 $r=2; 4975 else 4976 $r=3; 4977 } 4978 // Minsteps are always the same as maxsteps for integer scale 4979 switch( $r ) { 4980 case 1: 4981 $this->ticks->Set($maj1step,$maj1step); 4982 $this->Update($img,$adj1min,$adj1max); 4983 break; 4984 case 2: 4985 $this->ticks->Set($maj2step,$maj2step); 4986 $this->Update($img,$adj2min,$adj2max); 4987 break; 4988 case 3: 4989 $this->ticks->Set($maj5step,$maj5step); 4990 $this->Update($img,$adj5min,$adj5max); 4991 break; 4992 default: 4993 JpGraphError::RaiseL(25073,$r);//('Internal error. Integer scale algorithm comparison out of bound (r=$r)'); 4994 } 4995 } 4996 4997 4998 // Calculate autoscale. Used if user hasn't given a scale and ticks 4999 // $maxsteps is the maximum number of major tickmarks allowed. 5000 function AutoScale($img,$min,$max,$maxsteps,$majend=true) { 5001 if( $this->intscale ) { 5002 $this->IntAutoScale($img,$min,$max,$maxsteps,$majend); 5003 return; 5004 } 5005 if( abs($min-$max) < 0.00001 ) { 5006 // We need some difference to be able to autoscale 5007 // make it 5% above and 5% below value 5008 if( $min==0 && $max==0 ) { // Special case 5009 $min=-1; $max=1; 5010 } 5011 else { 5012 $delta = (abs($max)+abs($min))*0.005; 5013 $min -= $delta; 5014 $max += $delta; 5015 } 5016 } 5017 5018 $gracetop=($this->gracetop/100.0)*abs($max-$min); 5019 $gracebottom=($this->gracebottom/100.0)*abs($max-$min); 5020 if( is_numeric($this->autoscale_min) ) { 5021 $min = $this->autoscale_min; 5022 if( $min >= $max ) { 5023 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.'); 5024 } 5025 if( abs($min-$max ) < 0.00001 ) 5026 $max *= 1.2; 5027 } 5028 5029 if( is_numeric($this->autoscale_max) ) { 5030 $max = $this->autoscale_max; 5031 if( $min >= $max ) { 5032 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.'); 5033 } 5034 if( abs($min-$max ) < 0.00001 ) 5035 $min *= 0.8; 5036 } 5037 5038 5039 $min -= $gracebottom; 5040 $max += $gracetop; 5041 5042 // First get tickmarks as multiples of 0.1, 1, 10, ... 5043 if( $majend ) { 5044 list($num1steps,$adj1min,$adj1max,$min1step,$maj1step) = 5045 $this->CalcTicks($maxsteps,$min,$max,1,2); 5046 } 5047 else { 5048 $adj1min=$min; 5049 $adj1max=$max; 5050 list($num1steps,$min1step,$maj1step) = 5051 $this->CalcTicksFreeze($maxsteps,$min,$max,1,2,false); 5052 } 5053 5054 // Then get tick marks as 2:s 0.2, 2, 20, ... 5055 if( $majend ) { 5056 list($num2steps,$adj2min,$adj2max,$min2step,$maj2step) = 5057 $this->CalcTicks($maxsteps,$min,$max,5,2); 5058 } 5059 else { 5060 $adj2min=$min; 5061 $adj2max=$max; 5062 list($num2steps,$min2step,$maj2step) = 5063 $this->CalcTicksFreeze($maxsteps,$min,$max,5,2,false); 5064 } 5065 5066 // Then get tickmarks as 5:s 0.05, 0.5, 5, 50, ... 5067 if( $majend ) { 5068 list($num5steps,$adj5min,$adj5max,$min5step,$maj5step) = 5069 $this->CalcTicks($maxsteps,$min,$max,2,5); 5070 } 5071 else { 5072 $adj5min=$min; 5073 $adj5max=$max; 5074 list($num5steps,$min5step,$maj5step) = 5075 $this->CalcTicksFreeze($maxsteps,$min,$max,2,5,false); 5076 } 5077 5078 // Check to see whichof 1:s, 2:s or 5:s fit better with 5079 // the requested number of major ticks 5080 $match1=abs($num1steps-$maxsteps); 5081 $match2=abs($num2steps-$maxsteps); 5082 $match5=abs($num5steps-$maxsteps); 5083 // Compare these three values and see which is the closest match 5084 // We use a 0.8 weight to gravitate towards multiple of 5:s 5085 $r=$this->MatchMin3($match1,$match2,$match5,0.8); 5086 switch( $r ) { 5087 case 1: 5088 $this->Update($img,$adj1min,$adj1max); 5089 $this->ticks->Set($maj1step,$min1step); 5090 break; 5091 case 2: 5092 $this->Update($img,$adj2min,$adj2max); 5093 $this->ticks->Set($maj2step,$min2step); 5094 break; 5095 case 3: 5096 $this->Update($img,$adj5min,$adj5max); 5097 $this->ticks->Set($maj5step,$min5step); 5098 break; 5099 } 5100 } 5101 5102//--------------- 5103// PRIVATE METHODS 5104 5105 // This method recalculates all constants that are depending on the 5106 // margins in the image. If the margins in the image are changed 5107 // this method should be called for every scale that is registred with 5108 // that image. Should really be installed as an observer of that image. 5109 function InitConstants($img) { 5110 if( $this->type=="x" ) { 5111 $this->world_abs_size=$img->width - $img->left_margin - $img->right_margin; 5112 $this->off=$img->left_margin; 5113 $this->scale_factor = 0; 5114 if( $this->world_size > 0 ) 5115 $this->scale_factor=$this->world_abs_size/($this->world_size*1.0); 5116 } 5117 else { // y scale 5118 $this->world_abs_size=$img->height - $img->top_margin - $img->bottom_margin; 5119 $this->off=$img->top_margin+$this->world_abs_size; 5120 $this->scale_factor = 0; 5121 if( $this->world_size > 0 ) 5122 $this->scale_factor=-$this->world_abs_size/($this->world_size*1.0); 5123 } 5124 $size = $this->world_size * $this->scale_factor; 5125 $this->scale_abs=array($this->off,$this->off + $size); 5126 } 5127 5128 // Initialize the conversion constants for this scale 5129 // This tries to pre-calculate as much as possible to speed up the 5130 // actual conversion (with Translate()) later on 5131 // $start =scale start in absolute pixels (for x-scale this is an y-position 5132 // and for an y-scale this is an x-position 5133 // $len =absolute length in pixels of scale 5134 function SetConstants($aStart,$aLen) { 5135 $this->world_abs_size=$aLen; 5136 $this->off=$aStart; 5137 5138 if( $this->world_size<=0 ) { 5139 // This should never ever happen !! 5140 JpGraphError::RaiseL(25074); 5141//("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."); 5142 5143 } 5144 5145 // scale_factor = number of pixels per world unit 5146 $this->scale_factor=$this->world_abs_size/($this->world_size*1.0); 5147 5148 // scale_abs = start and end points of scale in absolute pixels 5149 $this->scale_abs=array($this->off,$this->off+$this->world_size*$this->scale_factor); 5150 } 5151 5152 5153 // Calculate number of ticks steps with a specific division 5154 // $a is the divisor of 10**x to generate the first maj tick intervall 5155 // $a=1, $b=2 give major ticks with multiple of 10, ...,0.1,1,10,... 5156 // $a=5, $b=2 give major ticks with multiple of 2:s ...,0.2,2,20,... 5157 // $a=2, $b=5 give major ticks with multiple of 5:s ...,0.5,5,50,... 5158 // We return a vector of 5159 // [$numsteps,$adjmin,$adjmax,$minstep,$majstep] 5160 // If $majend==true then the first and last marks on the axis will be major 5161 // labeled tick marks otherwise it will be adjusted to the closest min tick mark 5162 function CalcTicks($maxsteps,$min,$max,$a,$b,$majend=true) { 5163 $diff=$max-$min; 5164 if( $diff==0 ) 5165 $ld=0; 5166 else 5167 $ld=floor(log10($diff)); 5168 5169 // Gravitate min towards zero if we are close 5170 if( $min>0 && $min < pow(10,$ld) ) $min=0; 5171 5172 //$majstep=pow(10,$ld-1)/$a; 5173 $majstep=pow(10,$ld)/$a; 5174 $minstep=$majstep/$b; 5175 5176 $adjmax=ceil($max/$minstep)*$minstep; 5177 $adjmin=floor($min/$minstep)*$minstep; 5178 $adjdiff = $adjmax-$adjmin; 5179 $numsteps=$adjdiff/$majstep; 5180 5181 while( $numsteps>$maxsteps ) { 5182 $majstep=pow(10,$ld)/$a; 5183 $numsteps=$adjdiff/$majstep; 5184 ++$ld; 5185 } 5186 5187 $minstep=$majstep/$b; 5188 $adjmin=floor($min/$minstep)*$minstep; 5189 $adjdiff = $adjmax-$adjmin; 5190 if( $majend ) { 5191 $adjmin = floor($min/$majstep)*$majstep; 5192 $adjdiff = $adjmax-$adjmin; 5193 $adjmax = ceil($adjdiff/$majstep)*$majstep+$adjmin; 5194 } 5195 else 5196 $adjmax=ceil($max/$minstep)*$minstep; 5197 5198 return array($numsteps,$adjmin,$adjmax,$minstep,$majstep); 5199 } 5200 5201 function CalcTicksFreeze($maxsteps,$min,$max,$a,$b) { 5202 // Same as CalcTicks but don't adjust min/max values 5203 $diff=$max-$min; 5204 if( $diff==0 ) 5205 $ld=0; 5206 else 5207 $ld=floor(log10($diff)); 5208 5209 //$majstep=pow(10,$ld-1)/$a; 5210 $majstep=pow(10,$ld)/$a; 5211 $minstep=$majstep/$b; 5212 $numsteps=floor($diff/$majstep); 5213 5214 while( $numsteps > $maxsteps ) { 5215 $majstep=pow(10,$ld)/$a; 5216 $numsteps=floor($diff/$majstep); 5217 ++$ld; 5218 } 5219 $minstep=$majstep/$b; 5220 return array($numsteps,$minstep,$majstep); 5221 } 5222 5223 5224 function IntCalcTicks($maxsteps,$min,$max,$a,$majend=true) { 5225 $diff=$max-$min; 5226 if( $diff==0 ) 5227 JpGraphError::RaiseL(25075);//('Can\'t automatically determine ticks since min==max.'); 5228 else 5229 $ld=floor(log10($diff)); 5230 5231 // Gravitate min towards zero if we are close 5232 if( $min>0 && $min < pow(10,$ld) ) $min=0; 5233 5234 if( $ld == 0 ) $ld=1; 5235 5236 if( $a == 1 ) 5237 $majstep = 1; 5238 else 5239 $majstep=pow(10,$ld)/$a; 5240 $adjmax=ceil($max/$majstep)*$majstep; 5241 5242 $adjmin=floor($min/$majstep)*$majstep; 5243 $adjdiff = $adjmax-$adjmin; 5244 $numsteps=$adjdiff/$majstep; 5245 while( $numsteps>$maxsteps ) { 5246 $majstep=pow(10,$ld)/$a; 5247 $numsteps=$adjdiff/$majstep; 5248 ++$ld; 5249 } 5250 5251 $adjmin=floor($min/$majstep)*$majstep; 5252 $adjdiff = $adjmax-$adjmin; 5253 if( $majend ) { 5254 $adjmin = floor($min/$majstep)*$majstep; 5255 $adjdiff = $adjmax-$adjmin; 5256 $adjmax = ceil($adjdiff/$majstep)*$majstep+$adjmin; 5257 } 5258 else 5259 $adjmax=ceil($max/$majstep)*$majstep; 5260 5261 return array($numsteps,$adjmin,$adjmax,$majstep); 5262 } 5263 5264 5265 function IntCalcTicksFreeze($maxsteps,$min,$max,$a) { 5266 // Same as IntCalcTick but don't change min/max values 5267 $diff=$max-$min; 5268 if( $diff==0 ) 5269 JpGraphError::RaiseL(25075);//('Can\'t automatically determine ticks since min==max.'); 5270 else 5271 $ld=floor(log10($diff)); 5272 5273 if( $ld == 0 ) $ld=1; 5274 5275 if( $a == 1 ) 5276 $majstep = 1; 5277 else 5278 $majstep=pow(10,$ld)/$a; 5279 5280 $numsteps=floor($diff/$majstep); 5281 while( $numsteps > $maxsteps ) { 5282 $majstep=pow(10,$ld)/$a; 5283 $numsteps=floor($diff/$majstep); 5284 ++$ld; 5285 } 5286 5287 return array($numsteps,$majstep); 5288 } 5289 5290 5291 5292 // Determine the minimum of three values witha weight for last value 5293 function MatchMin3($a,$b,$c,$weight) { 5294 if( $a < $b ) { 5295 if( $a < ($c*$weight) ) 5296 return 1; // $a smallest 5297 else 5298 return 3; // $c smallest 5299 } 5300 elseif( $b < ($c*$weight) ) 5301 return 2; // $b smallest 5302 return 3; // $c smallest 5303 } 5304} // Class 5305 5306//=================================================== 5307// CLASS RGB 5308// Description: Color definitions as RGB triples 5309//=================================================== 5310class RGB { 5311 public $rgb_table; 5312 public $img; 5313 5314 function RGB($aImg=null) { 5315 $this->img = $aImg; 5316 5317 // Conversion array between color names and RGB 5318 $this->rgb_table = array( 5319 "aqua"=> array(0,255,255), 5320 "lime"=> array(0,255,0), 5321 "teal"=> array(0,128,128), 5322 "whitesmoke"=>array(245,245,245), 5323 "gainsboro"=>array(220,220,220), 5324 "oldlace"=>array(253,245,230), 5325 "linen"=>array(250,240,230), 5326 "antiquewhite"=>array(250,235,215), 5327 "papayawhip"=>array(255,239,213), 5328 "blanchedalmond"=>array(255,235,205), 5329 "bisque"=>array(255,228,196), 5330 "peachpuff"=>array(255,218,185), 5331 "navajowhite"=>array(255,222,173), 5332 "moccasin"=>array(255,228,181), 5333 "cornsilk"=>array(255,248,220), 5334 "ivory"=>array(255,255,240), 5335 "lemonchiffon"=>array(255,250,205), 5336 "seashell"=>array(255,245,238), 5337 "mintcream"=>array(245,255,250), 5338 "azure"=>array(240,255,255), 5339 "aliceblue"=>array(240,248,255), 5340 "lavender"=>array(230,230,250), 5341 "lavenderblush"=>array(255,240,245), 5342 "mistyrose"=>array(255,228,225), 5343 "white"=>array(255,255,255), 5344 "black"=>array(0,0,0), 5345 "darkslategray"=>array(47,79,79), 5346 "dimgray"=>array(105,105,105), 5347 "slategray"=>array(112,128,144), 5348 "lightslategray"=>array(119,136,153), 5349 "gray"=>array(190,190,190), 5350 "lightgray"=>array(211,211,211), 5351 "midnightblue"=>array(25,25,112), 5352 "navy"=>array(0,0,128), 5353 "cornflowerblue"=>array(100,149,237), 5354 "darkslateblue"=>array(72,61,139), 5355 "slateblue"=>array(106,90,205), 5356 "mediumslateblue"=>array(123,104,238), 5357 "lightslateblue"=>array(132,112,255), 5358 "mediumblue"=>array(0,0,205), 5359 "royalblue"=>array(65,105,225), 5360 "blue"=>array(0,0,255), 5361 "dodgerblue"=>array(30,144,255), 5362 "deepskyblue"=>array(0,191,255), 5363 "skyblue"=>array(135,206,235), 5364 "lightskyblue"=>array(135,206,250), 5365 "steelblue"=>array(70,130,180), 5366 "lightred"=>array(211,167,168), 5367 "lightsteelblue"=>array(176,196,222), 5368 "lightblue"=>array(173,216,230), 5369 "powderblue"=>array(176,224,230), 5370 "paleturquoise"=>array(175,238,238), 5371 "darkturquoise"=>array(0,206,209), 5372 "mediumturquoise"=>array(72,209,204), 5373 "turquoise"=>array(64,224,208), 5374 "cyan"=>array(0,255,255), 5375 "lightcyan"=>array(224,255,255), 5376 "cadetblue"=>array(95,158,160), 5377 "mediumaquamarine"=>array(102,205,170), 5378 "aquamarine"=>array(127,255,212), 5379 "darkgreen"=>array(0,100,0), 5380 "darkolivegreen"=>array(85,107,47), 5381 "darkseagreen"=>array(143,188,143), 5382 "seagreen"=>array(46,139,87), 5383 "mediumseagreen"=>array(60,179,113), 5384 "lightseagreen"=>array(32,178,170), 5385 "palegreen"=>array(152,251,152), 5386 "springgreen"=>array(0,255,127), 5387 "lawngreen"=>array(124,252,0), 5388 "green"=>array(0,255,0), 5389 "chartreuse"=>array(127,255,0), 5390 "mediumspringgreen"=>array(0,250,154), 5391 "greenyellow"=>array(173,255,47), 5392 "limegreen"=>array(50,205,50), 5393 "yellowgreen"=>array(154,205,50), 5394 "forestgreen"=>array(34,139,34), 5395 "olivedrab"=>array(107,142,35), 5396 "darkkhaki"=>array(189,183,107), 5397 "khaki"=>array(240,230,140), 5398 "palegoldenrod"=>array(238,232,170), 5399 "lightgoldenrodyellow"=>array(250,250,210), 5400 "lightyellow"=>array(255,255,200), 5401 "yellow"=>array(255,255,0), 5402 "gold"=>array(255,215,0), 5403 "lightgoldenrod"=>array(238,221,130), 5404 "goldenrod"=>array(218,165,32), 5405 "darkgoldenrod"=>array(184,134,11), 5406 "rosybrown"=>array(188,143,143), 5407 "indianred"=>array(205,92,92), 5408 "saddlebrown"=>array(139,69,19), 5409 "sienna"=>array(160,82,45), 5410 "peru"=>array(205,133,63), 5411 "burlywood"=>array(222,184,135), 5412 "beige"=>array(245,245,220), 5413 "wheat"=>array(245,222,179), 5414 "sandybrown"=>array(244,164,96), 5415 "tan"=>array(210,180,140), 5416 "chocolate"=>array(210,105,30), 5417 "firebrick"=>array(178,34,34), 5418 "brown"=>array(165,42,42), 5419 "darksalmon"=>array(233,150,122), 5420 "salmon"=>array(250,128,114), 5421 "lightsalmon"=>array(255,160,122), 5422 "orange"=>array(255,165,0), 5423 "darkorange"=>array(255,140,0), 5424 "coral"=>array(255,127,80), 5425 "lightcoral"=>array(240,128,128), 5426 "tomato"=>array(255,99,71), 5427 "orangered"=>array(255,69,0), 5428 "red"=>array(255,0,0), 5429 "hotpink"=>array(255,105,180), 5430 "deeppink"=>array(255,20,147), 5431 "pink"=>array(255,192,203), 5432 "lightpink"=>array(255,182,193), 5433 "palevioletred"=>array(219,112,147), 5434 "maroon"=>array(176,48,96), 5435 "mediumvioletred"=>array(199,21,133), 5436 "violetred"=>array(208,32,144), 5437 "magenta"=>array(255,0,255), 5438 "violet"=>array(238,130,238), 5439 "plum"=>array(221,160,221), 5440 "orchid"=>array(218,112,214), 5441 "mediumorchid"=>array(186,85,211), 5442 "darkorchid"=>array(153,50,204), 5443 "darkviolet"=>array(148,0,211), 5444 "blueviolet"=>array(138,43,226), 5445 "purple"=>array(160,32,240), 5446 "mediumpurple"=>array(147,112,219), 5447 "thistle"=>array(216,191,216), 5448 "snow1"=>array(255,250,250), 5449 "snow2"=>array(238,233,233), 5450 "snow3"=>array(205,201,201), 5451 "snow4"=>array(139,137,137), 5452 "seashell1"=>array(255,245,238), 5453 "seashell2"=>array(238,229,222), 5454 "seashell3"=>array(205,197,191), 5455 "seashell4"=>array(139,134,130), 5456 "AntiqueWhite1"=>array(255,239,219), 5457 "AntiqueWhite2"=>array(238,223,204), 5458 "AntiqueWhite3"=>array(205,192,176), 5459 "AntiqueWhite4"=>array(139,131,120), 5460 "bisque1"=>array(255,228,196), 5461 "bisque2"=>array(238,213,183), 5462 "bisque3"=>array(205,183,158), 5463 "bisque4"=>array(139,125,107), 5464 "peachPuff1"=>array(255,218,185), 5465 "peachpuff2"=>array(238,203,173), 5466 "peachpuff3"=>array(205,175,149), 5467 "peachpuff4"=>array(139,119,101), 5468 "navajowhite1"=>array(255,222,173), 5469 "navajowhite2"=>array(238,207,161), 5470 "navajowhite3"=>array(205,179,139), 5471 "navajowhite4"=>array(139,121,94), 5472 "lemonchiffon1"=>array(255,250,205), 5473 "lemonchiffon2"=>array(238,233,191), 5474 "lemonchiffon3"=>array(205,201,165), 5475 "lemonchiffon4"=>array(139,137,112), 5476 "ivory1"=>array(255,255,240), 5477 "ivory2"=>array(238,238,224), 5478 "ivory3"=>array(205,205,193), 5479 "ivory4"=>array(139,139,131), 5480 "honeydew"=>array(193,205,193), 5481 "lavenderblush1"=>array(255,240,245), 5482 "lavenderblush2"=>array(238,224,229), 5483 "lavenderblush3"=>array(205,193,197), 5484 "lavenderblush4"=>array(139,131,134), 5485 "mistyrose1"=>array(255,228,225), 5486 "mistyrose2"=>array(238,213,210), 5487 "mistyrose3"=>array(205,183,181), 5488 "mistyrose4"=>array(139,125,123), 5489 "azure1"=>array(240,255,255), 5490 "azure2"=>array(224,238,238), 5491 "azure3"=>array(193,205,205), 5492 "azure4"=>array(131,139,139), 5493 "slateblue1"=>array(131,111,255), 5494 "slateblue2"=>array(122,103,238), 5495 "slateblue3"=>array(105,89,205), 5496 "slateblue4"=>array(71,60,139), 5497 "royalblue1"=>array(72,118,255), 5498 "royalblue2"=>array(67,110,238), 5499 "royalblue3"=>array(58,95,205), 5500 "royalblue4"=>array(39,64,139), 5501 "dodgerblue1"=>array(30,144,255), 5502 "dodgerblue2"=>array(28,134,238), 5503 "dodgerblue3"=>array(24,116,205), 5504 "dodgerblue4"=>array(16,78,139), 5505 "steelblue1"=>array(99,184,255), 5506 "steelblue2"=>array(92,172,238), 5507 "steelblue3"=>array(79,148,205), 5508 "steelblue4"=>array(54,100,139), 5509 "deepskyblue1"=>array(0,191,255), 5510 "deepskyblue2"=>array(0,178,238), 5511 "deepskyblue3"=>array(0,154,205), 5512 "deepskyblue4"=>array(0,104,139), 5513 "skyblue1"=>array(135,206,255), 5514 "skyblue2"=>array(126,192,238), 5515 "skyblue3"=>array(108,166,205), 5516 "skyblue4"=>array(74,112,139), 5517 "lightskyblue1"=>array(176,226,255), 5518 "lightskyblue2"=>array(164,211,238), 5519 "lightskyblue3"=>array(141,182,205), 5520 "lightskyblue4"=>array(96,123,139), 5521 "slategray1"=>array(198,226,255), 5522 "slategray2"=>array(185,211,238), 5523 "slategray3"=>array(159,182,205), 5524 "slategray4"=>array(108,123,139), 5525 "lightsteelblue1"=>array(202,225,255), 5526 "lightsteelblue2"=>array(188,210,238), 5527 "lightsteelblue3"=>array(162,181,205), 5528 "lightsteelblue4"=>array(110,123,139), 5529 "lightblue1"=>array(191,239,255), 5530 "lightblue2"=>array(178,223,238), 5531 "lightblue3"=>array(154,192,205), 5532 "lightblue4"=>array(104,131,139), 5533 "lightcyan1"=>array(224,255,255), 5534 "lightcyan2"=>array(209,238,238), 5535 "lightcyan3"=>array(180,205,205), 5536 "lightcyan4"=>array(122,139,139), 5537 "paleturquoise1"=>array(187,255,255), 5538 "paleturquoise2"=>array(174,238,238), 5539 "paleturquoise3"=>array(150,205,205), 5540 "paleturquoise4"=>array(102,139,139), 5541 "cadetblue1"=>array(152,245,255), 5542 "cadetblue2"=>array(142,229,238), 5543 "cadetblue3"=>array(122,197,205), 5544 "cadetblue4"=>array(83,134,139), 5545 "turquoise1"=>array(0,245,255), 5546 "turquoise2"=>array(0,229,238), 5547 "turquoise3"=>array(0,197,205), 5548 "turquoise4"=>array(0,134,139), 5549 "cyan1"=>array(0,255,255), 5550 "cyan2"=>array(0,238,238), 5551 "cyan3"=>array(0,205,205), 5552 "cyan4"=>array(0,139,139), 5553 "darkslategray1"=>array(151,255,255), 5554 "darkslategray2"=>array(141,238,238), 5555 "darkslategray3"=>array(121,205,205), 5556 "darkslategray4"=>array(82,139,139), 5557 "aquamarine1"=>array(127,255,212), 5558 "aquamarine2"=>array(118,238,198), 5559 "aquamarine3"=>array(102,205,170), 5560 "aquamarine4"=>array(69,139,116), 5561 "darkseagreen1"=>array(193,255,193), 5562 "darkseagreen2"=>array(180,238,180), 5563 "darkseagreen3"=>array(155,205,155), 5564 "darkseagreen4"=>array(105,139,105), 5565 "seagreen1"=>array(84,255,159), 5566 "seagreen2"=>array(78,238,148), 5567 "seagreen3"=>array(67,205,128), 5568 "seagreen4"=>array(46,139,87), 5569 "palegreen1"=>array(154,255,154), 5570 "palegreen2"=>array(144,238,144), 5571 "palegreen3"=>array(124,205,124), 5572 "palegreen4"=>array(84,139,84), 5573 "springgreen1"=>array(0,255,127), 5574 "springgreen2"=>array(0,238,118), 5575 "springgreen3"=>array(0,205,102), 5576 "springgreen4"=>array(0,139,69), 5577 "chartreuse1"=>array(127,255,0), 5578 "chartreuse2"=>array(118,238,0), 5579 "chartreuse3"=>array(102,205,0), 5580 "chartreuse4"=>array(69,139,0), 5581 "olivedrab1"=>array(192,255,62), 5582 "olivedrab2"=>array(179,238,58), 5583 "olivedrab3"=>array(154,205,50), 5584 "olivedrab4"=>array(105,139,34), 5585 "darkolivegreen1"=>array(202,255,112), 5586 "darkolivegreen2"=>array(188,238,104), 5587 "darkolivegreen3"=>array(162,205,90), 5588 "darkolivegreen4"=>array(110,139,61), 5589 "khaki1"=>array(255,246,143), 5590 "khaki2"=>array(238,230,133), 5591 "khaki3"=>array(205,198,115), 5592 "khaki4"=>array(139,134,78), 5593 "lightgoldenrod1"=>array(255,236,139), 5594 "lightgoldenrod2"=>array(238,220,130), 5595 "lightgoldenrod3"=>array(205,190,112), 5596 "lightgoldenrod4"=>array(139,129,76), 5597 "yellow1"=>array(255,255,0), 5598 "yellow2"=>array(238,238,0), 5599 "yellow3"=>array(205,205,0), 5600 "yellow4"=>array(139,139,0), 5601 "gold1"=>array(255,215,0), 5602 "gold2"=>array(238,201,0), 5603 "gold3"=>array(205,173,0), 5604 "gold4"=>array(139,117,0), 5605 "goldenrod1"=>array(255,193,37), 5606 "goldenrod2"=>array(238,180,34), 5607 "goldenrod3"=>array(205,155,29), 5608 "goldenrod4"=>array(139,105,20), 5609 "darkgoldenrod1"=>array(255,185,15), 5610 "darkgoldenrod2"=>array(238,173,14), 5611 "darkgoldenrod3"=>array(205,149,12), 5612 "darkgoldenrod4"=>array(139,101,8), 5613 "rosybrown1"=>array(255,193,193), 5614 "rosybrown2"=>array(238,180,180), 5615 "rosybrown3"=>array(205,155,155), 5616 "rosybrown4"=>array(139,105,105), 5617 "indianred1"=>array(255,106,106), 5618 "indianred2"=>array(238,99,99), 5619 "indianred3"=>array(205,85,85), 5620 "indianred4"=>array(139,58,58), 5621 "sienna1"=>array(255,130,71), 5622 "sienna2"=>array(238,121,66), 5623 "sienna3"=>array(205,104,57), 5624 "sienna4"=>array(139,71,38), 5625 "burlywood1"=>array(255,211,155), 5626 "burlywood2"=>array(238,197,145), 5627 "burlywood3"=>array(205,170,125), 5628 "burlywood4"=>array(139,115,85), 5629 "wheat1"=>array(255,231,186), 5630 "wheat2"=>array(238,216,174), 5631 "wheat3"=>array(205,186,150), 5632 "wheat4"=>array(139,126,102), 5633 "tan1"=>array(255,165,79), 5634 "tan2"=>array(238,154,73), 5635 "tan3"=>array(205,133,63), 5636 "tan4"=>array(139,90,43), 5637 "chocolate1"=>array(255,127,36), 5638 "chocolate2"=>array(238,118,33), 5639 "chocolate3"=>array(205,102,29), 5640 "chocolate4"=>array(139,69,19), 5641 "firebrick1"=>array(255,48,48), 5642 "firebrick2"=>array(238,44,44), 5643 "firebrick3"=>array(205,38,38), 5644 "firebrick4"=>array(139,26,26), 5645 "brown1"=>array(255,64,64), 5646 "brown2"=>array(238,59,59), 5647 "brown3"=>array(205,51,51), 5648 "brown4"=>array(139,35,35), 5649 "salmon1"=>array(255,140,105), 5650 "salmon2"=>array(238,130,98), 5651 "salmon3"=>array(205,112,84), 5652 "salmon4"=>array(139,76,57), 5653 "lightsalmon1"=>array(255,160,122), 5654 "lightsalmon2"=>array(238,149,114), 5655 "lightsalmon3"=>array(205,129,98), 5656 "lightsalmon4"=>array(139,87,66), 5657 "orange1"=>array(255,165,0), 5658 "orange2"=>array(238,154,0), 5659 "orange3"=>array(205,133,0), 5660 "orange4"=>array(139,90,0), 5661 "darkorange1"=>array(255,127,0), 5662 "darkorange2"=>array(238,118,0), 5663 "darkorange3"=>array(205,102,0), 5664 "darkorange4"=>array(139,69,0), 5665 "coral1"=>array(255,114,86), 5666 "coral2"=>array(238,106,80), 5667 "coral3"=>array(205,91,69), 5668 "coral4"=>array(139,62,47), 5669 "tomato1"=>array(255,99,71), 5670 "tomato2"=>array(238,92,66), 5671 "tomato3"=>array(205,79,57), 5672 "tomato4"=>array(139,54,38), 5673 "orangered1"=>array(255,69,0), 5674 "orangered2"=>array(238,64,0), 5675 "orangered3"=>array(205,55,0), 5676 "orangered4"=>array(139,37,0), 5677 "deeppink1"=>array(255,20,147), 5678 "deeppink2"=>array(238,18,137), 5679 "deeppink3"=>array(205,16,118), 5680 "deeppink4"=>array(139,10,80), 5681 "hotpink1"=>array(255,110,180), 5682 "hotpink2"=>array(238,106,167), 5683 "hotpink3"=>array(205,96,144), 5684 "hotpink4"=>array(139,58,98), 5685 "pink1"=>array(255,181,197), 5686 "pink2"=>array(238,169,184), 5687 "pink3"=>array(205,145,158), 5688 "pink4"=>array(139,99,108), 5689 "lightpink1"=>array(255,174,185), 5690 "lightpink2"=>array(238,162,173), 5691 "lightpink3"=>array(205,140,149), 5692 "lightpink4"=>array(139,95,101), 5693 "palevioletred1"=>array(255,130,171), 5694 "palevioletred2"=>array(238,121,159), 5695 "palevioletred3"=>array(205,104,137), 5696 "palevioletred4"=>array(139,71,93), 5697 "maroon1"=>array(255,52,179), 5698 "maroon2"=>array(238,48,167), 5699 "maroon3"=>array(205,41,144), 5700 "maroon4"=>array(139,28,98), 5701 "violetred1"=>array(255,62,150), 5702 "violetred2"=>array(238,58,140), 5703 "violetred3"=>array(205,50,120), 5704 "violetred4"=>array(139,34,82), 5705 "magenta1"=>array(255,0,255), 5706 "magenta2"=>array(238,0,238), 5707 "magenta3"=>array(205,0,205), 5708 "magenta4"=>array(139,0,139), 5709 "mediumred"=>array(140,34,34), 5710 "orchid1"=>array(255,131,250), 5711 "orchid2"=>array(238,122,233), 5712 "orchid3"=>array(205,105,201), 5713 "orchid4"=>array(139,71,137), 5714 "plum1"=>array(255,187,255), 5715 "plum2"=>array(238,174,238), 5716 "plum3"=>array(205,150,205), 5717 "plum4"=>array(139,102,139), 5718 "mediumorchid1"=>array(224,102,255), 5719 "mediumorchid2"=>array(209,95,238), 5720 "mediumorchid3"=>array(180,82,205), 5721 "mediumorchid4"=>array(122,55,139), 5722 "darkorchid1"=>array(191,62,255), 5723 "darkorchid2"=>array(178,58,238), 5724 "darkorchid3"=>array(154,50,205), 5725 "darkorchid4"=>array(104,34,139), 5726 "purple1"=>array(155,48,255), 5727 "purple2"=>array(145,44,238), 5728 "purple3"=>array(125,38,205), 5729 "purple4"=>array(85,26,139), 5730 "mediumpurple1"=>array(171,130,255), 5731 "mediumpurple2"=>array(159,121,238), 5732 "mediumpurple3"=>array(137,104,205), 5733 "mediumpurple4"=>array(93,71,139), 5734 "thistle1"=>array(255,225,255), 5735 "thistle2"=>array(238,210,238), 5736 "thistle3"=>array(205,181,205), 5737 "thistle4"=>array(139,123,139), 5738 "gray1"=>array(10,10,10), 5739 "gray2"=>array(40,40,30), 5740 "gray3"=>array(70,70,70), 5741 "gray4"=>array(100,100,100), 5742 "gray5"=>array(130,130,130), 5743 "gray6"=>array(160,160,160), 5744 "gray7"=>array(190,190,190), 5745 "gray8"=>array(210,210,210), 5746 "gray9"=>array(240,240,240), 5747 "darkgray"=>array(100,100,100), 5748 "darkblue"=>array(0,0,139), 5749 "darkcyan"=>array(0,139,139), 5750 "darkmagenta"=>array(139,0,139), 5751 "darkred"=>array(139,0,0), 5752 "silver"=>array(192, 192, 192), 5753 "eggplant"=>array(144,176,168), 5754 "lightgreen"=>array(144,238,144)); 5755 } 5756//---------------- 5757// PUBLIC METHODS 5758 // Colors can be specified as either 5759 // 1. #xxxxxx HTML style 5760 // 2. "colorname" as a named color 5761 // 3. array(r,g,b) RGB triple 5762 // This function translates this to a native RGB format and returns an 5763 // RGB triple. 5764 function Color($aColor) { 5765 if (is_string($aColor)) { 5766 // Strip of any alpha factor 5767 $pos = strpos($aColor,'@'); 5768 if( $pos === false ) { 5769 $alpha = 0; 5770 } 5771 else { 5772 $pos2 = strpos($aColor,':'); 5773 if( $pos2===false ) 5774 $pos2 = $pos-1; // Sentinel 5775 if( $pos > $pos2 ) { 5776 $alpha = substr($aColor,$pos+1); 5777 $aColor = substr($aColor,0,$pos); 5778 } 5779 else { 5780 $alpha = substr($aColor,$pos+1,$pos2-$pos-1); 5781 $aColor = substr($aColor,0,$pos).substr($aColor,$pos2); 5782 } 5783 } 5784 5785 // Extract potential adjustment figure at end of color 5786 // specification 5787 $pos = strpos($aColor,":"); 5788 if( $pos === false ) { 5789 $adj = 1.0; 5790 } 5791 else { 5792 $adj = 0.0 + substr($aColor,$pos+1); 5793 $aColor = substr($aColor,0,$pos); 5794 } 5795 if( $adj < 0 ) 5796 JpGraphError::RaiseL(25077);//('Adjustment factor for color must be > 0'); 5797 5798 if (substr($aColor, 0, 1) == "#") { 5799 $r = hexdec(substr($aColor, 1, 2)); 5800 $g = hexdec(substr($aColor, 3, 2)); 5801 $b = hexdec(substr($aColor, 5, 2)); 5802 } else { 5803 if(!isset($this->rgb_table[$aColor]) ) 5804 JpGraphError::RaiseL(25078,$aColor);//(" Unknown color: $aColor"); 5805 $tmp=$this->rgb_table[$aColor]; 5806 $r = $tmp[0]; 5807 $g = $tmp[1]; 5808 $b = $tmp[2]; 5809 } 5810 // Scale adj so that an adj=2 always 5811 // makes the color 100% white (i.e. 255,255,255. 5812 // and adj=1 neutral and adj=0 black. 5813 if( $adj > 1 ) { 5814 $m = ($adj-1.0)*(255-min(255,min($r,min($g,$b)))); 5815 return array(min(255,$r+$m), min(255,$g+$m), min(255,$b+$m),$alpha); 5816 } 5817 elseif( $adj < 1 ) { 5818 $m = ($adj-1.0)*max(255,max($r,max($g,$b))); 5819 return array(max(0,$r+$m), max(0,$g+$m), max(0,$b+$m),$alpha); 5820 } 5821 else { 5822 return array($r,$g,$b,$alpha); 5823 } 5824 5825 } elseif( is_array($aColor) ) { 5826 if( count($aColor)==3 ) { 5827 $aColor[3]=0; 5828 return $aColor; 5829 } 5830 else 5831 return $aColor; 5832 } 5833 else 5834 JpGraphError::RaiseL(25079,$aColor,count($aColor));//(" Unknown color specification: $aColor , size=".count($aColor)); 5835 } 5836 5837 // Compare two colors 5838 // return true if equal 5839 function Equal($aCol1,$aCol2) { 5840 $c1 = $this->Color($aCol1); 5841 $c2 = $this->Color($aCol2); 5842 if( $c1[0]==$c2[0] && $c1[1]==$c2[1] && $c1[2]==$c2[2] ) 5843 return true; 5844 else 5845 return false; 5846 } 5847 5848 // Allocate a new color in the current image 5849 // Return new color index, -1 if no more colors could be allocated 5850 function Allocate($aColor,$aAlpha=0.0) { 5851 list ($r, $g, $b, $a) = $this->color($aColor); 5852 // If alpha is specified in the color string then this 5853 // takes precedence over the second argument 5854 if( $a > 0 ) 5855 $aAlpha = $a; 5856 if( $aAlpha < 0 || $aAlpha > 1 ) { 5857 JpGraphError::RaiseL(25080);//('Alpha parameter for color must be between 0.0 and 1.0'); 5858 } 5859 return imagecolorresolvealpha($this->img, $r, $g, $b, round($aAlpha * 127)); 5860 } 5861} // Class 5862 5863 5864//=================================================== 5865// CLASS Image 5866// Description: Wrapper class with some goodies to form the 5867// Interface to low level image drawing routines. 5868//=================================================== 5869class Image { 5870 public $left_margin=30,$right_margin=30,$top_margin=20,$bottom_margin=30; 5871 public $img=null; 5872 public $plotwidth=0,$plotheight=0; 5873 public $width=0, $height=0; 5874 public $rgb=null; 5875 public $current_color,$current_color_name; 5876 public $line_weight=1, $line_style=1; // Default line style is solid 5877 public $img_format; 5878 protected $expired=true; 5879 protected $lastx=0, $lasty=0; 5880 protected $obs_list=array(); 5881 protected $font_size=12,$font_family=FF_FONT1, $font_style=FS_NORMAL; 5882 protected $font_file=''; 5883 protected $text_halign="left",$text_valign="bottom"; 5884 protected $ttf=null; 5885 protected $use_anti_aliasing=false; 5886 protected $quality=null; 5887 protected $colorstack=array(),$colorstackidx=0; 5888 protected $canvascolor = 'white' ; 5889 protected $langconv = null ; 5890 protected $iInterlace=false; 5891 //--------------- 5892 // CONSTRUCTOR 5893 function Image($aWidth,$aHeight,$aFormat=DEFAULT_GFORMAT) { 5894 $this->CreateImgCanvas($aWidth,$aHeight); 5895 $this->SetAutoMargin(); 5896 5897 if( !$this->SetImgFormat($aFormat) ) { 5898 JpGraphError::RaiseL(25081,$aFormat);//("JpGraph: Selected graphic format is either not supported or unknown [$aFormat]"); 5899 } 5900 $this->ttf = new TTF(); 5901 $this->langconv = new LanguageConv(); 5902 } 5903 5904 // Enable interlacing in images 5905 function SetInterlace($aFlg=true) { 5906 $this->iInterlace=$aFlg; 5907 } 5908 5909 // Should we use anti-aliasing. Note: This really slows down graphics! 5910 function SetAntiAliasing($aFlg=true) { 5911 $this->use_anti_aliasing = $aFlg; 5912 imageantialias($this->img,$aFlg); 5913 } 5914 5915 function CreateRawCanvas($aWidth=0,$aHeight=0) { 5916 if( $aWidth <= 1 || $aHeight <= 1 ) { 5917 JpGraphError::RaiseL(25082,$aWidth,$aHeight);//("Illegal sizes specified for width or height when creating an image, (width=$aWidth, height=$aHeight)"); 5918 } 5919 5920 if( USE_TRUECOLOR ) { 5921 $this->img = @imagecreatetruecolor($aWidth, $aHeight); 5922 if( $this->img < 1 ) { 5923 JpGraphError::RaiseL(25126); 5924 //die("Can't create truecolor image. Check that you really have GD2 library installed."); 5925 } 5926 $this->SetAlphaBlending(); 5927 } else { 5928 $this->img = @imagecreate($aWidth, $aHeight); 5929 if( $this->img < 1 ) { 5930 JpGraphError::RaiseL(25126); 5931 //die("<b>JpGraph Error:</b> Can't create image. Check that you really have the GD library installed."); 5932 } 5933 } 5934 5935 if( $this->iInterlace ) { 5936 imageinterlace($this->img,1); 5937 } 5938 if( $this->rgb != null ) 5939 $this->rgb->img = $this->img ; 5940 else 5941 $this->rgb = new RGB($this->img); 5942 } 5943 5944 function CloneCanvasH() { 5945 $oldimage = $this->img; 5946 $this->CreateRawCanvas($this->width,$this->height); 5947 imagecopy($this->img,$oldimage,0,0,0,0,$this->width,$this->height); 5948 return $oldimage; 5949 } 5950 5951 function CreateImgCanvas($aWidth=0,$aHeight=0) { 5952 5953 $old = array($this->img,$this->width,$this->height); 5954 5955 $aWidth = round($aWidth); 5956 $aHeight = round($aHeight); 5957 5958 $this->width=$aWidth; 5959 $this->height=$aHeight; 5960 5961 5962 if( $aWidth==0 || $aHeight==0 ) { 5963 // We will set the final size later. 5964 // Note: The size must be specified before any other 5965 // img routines that stroke anything are called. 5966 $this->img = null; 5967 $this->rgb = null; 5968 return $old; 5969 } 5970 5971 $this->CreateRawCanvas($aWidth,$aHeight); 5972 // Set canvas color (will also be the background color for a 5973 // a pallett image 5974 $this->SetColor($this->canvascolor); 5975 $this->FilledRectangle(0,0,$aWidth,$aHeight); 5976 5977 return $old ; 5978 } 5979 5980 function CopyCanvasH($aToHdl,$aFromHdl,$aToX,$aToY,$aFromX,$aFromY,$aWidth,$aHeight,$aw=-1,$ah=-1) { 5981 if( $aw === -1 ) { 5982 $aw = $aWidth; 5983 $ah = $aHeight; 5984 $f = 'imagecopyresized'; 5985 } 5986 else { 5987 $f = 'imagecopyresampled'; 5988 } 5989 $f($aToHdl,$aFromHdl,$aToX,$aToY,$aFromX,$aFromY, $aWidth,$aHeight,$aw,$ah); 5990 } 5991 5992 function Copy($fromImg,$toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$fromWidth=-1,$fromHeight=-1) { 5993 $this->CopyCanvasH($this->img,$fromImg,$toX,$toY,$fromX,$fromY, 5994 $toWidth,$toHeight,$fromWidth,$fromHeight); 5995 } 5996 5997 function CopyMerge($fromImg,$toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$fromWidth=-1,$fromHeight=-1,$aMix=100) { 5998 if( $aMix == 100 ) { 5999 $this->CopyCanvasH($this->img,$fromImg, 6000 $toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$fromWidth,$fromHeight); 6001 } 6002 else { 6003 if( ($fromWidth != -1 && ($fromWidth != $toWidth)) || 6004 ($fromHeight != -1 && ($fromHeight != $fromHeight)) ) { 6005 // Create a new canvas that will hold the re-scaled original from image 6006 if( $toWidth <= 1 || $toHeight <= 1 ) { 6007 JpGraphError::RaiseL(25083);//('Illegal image size when copying image. Size for copied to image is 1 pixel or less.'); 6008 } 6009 if( USE_TRUECOLOR ) { 6010 $tmpimg = @imagecreatetruecolor($toWidth, $toHeight); 6011 } else { 6012 $tmpimg = @imagecreate($toWidth, $toHeight); 6013 } 6014 if( $tmpimg < 1 ) { 6015 JpGraphError::RaiseL(25084);//('Failed to create temporary GD canvas. Out of memory ?'); 6016 } 6017 $this->CopyCanvasH($tmpimg,$fromImg,0,0,0,0, 6018 $toWidth,$toHeight,$fromWidth,$fromHeight); 6019 $fromImg = $tmpimg; 6020 } 6021 imagecopymerge($this->img,$fromImg,$toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$aMix); 6022 } 6023 } 6024 6025 function GetWidth($aImg=null) { 6026 if( $aImg === null ) 6027 $aImg = $this->img; 6028 return imagesx($aImg); 6029 } 6030 6031 function GetHeight($aImg=null) { 6032 if( $aImg === null ) 6033 $aImg = $this->img; 6034 return imagesy($aImg); 6035 } 6036 6037 function CreateFromString($aStr) { 6038 $img = imagecreatefromstring($aStr); 6039 if( $img === false ) { 6040 JpGraphError::RaiseL(25085);//('An image can not be created from the supplied string. It is either in a format not supported or the string is representing an corrupt image.'); 6041 } 6042 return $img; 6043 } 6044 6045 function SetCanvasH($aHdl) { 6046 $this->img = $aHdl; 6047 $this->rgb->img = $aHdl; 6048 } 6049 6050 function SetCanvasColor($aColor) { 6051 $this->canvascolor = $aColor ; 6052 } 6053 6054 function SetAlphaBlending($aFlg=true) { 6055 ImageAlphaBlending($this->img,$aFlg); 6056 } 6057 6058 6059 function SetAutoMargin() { 6060 GLOBAL $gJpgBrandTiming; 6061 $min_bm=10; 6062 /* 6063 if( $gJpgBrandTiming ) 6064 $min_bm=15; 6065 */ 6066 $lm = min(40,$this->width/7); 6067 $rm = min(20,$this->width/10); 6068 $tm = max(20,$this->height/7); 6069 $bm = max($min_bm,$this->height/7); 6070 $this->SetMargin($lm,$rm,$tm,$bm); 6071 } 6072 6073 6074 //--------------- 6075 // PUBLIC METHODS 6076 6077 function SetFont($family,$style=FS_NORMAL,$size=10) { 6078 $this->font_family=$family; 6079 $this->font_style=$style; 6080 $this->font_size=$size; 6081 $this->font_file=''; 6082 if( ($this->font_family==FF_FONT1 || $this->font_family==FF_FONT2) && $this->font_style==FS_BOLD ){ 6083 ++$this->font_family; 6084 } 6085 if( $this->font_family > FF_FONT2+1 ) { // A TTF font so get the font file 6086 6087 // Check that this PHP has support for TTF fonts 6088 if( !function_exists('imagettfbbox') ) { 6089 JpGraphError::RaiseL(25087);//('This PHP build has not been configured with TTF support. You need to recompile your PHP installation with FreeType support.'); 6090 exit(); 6091 } 6092 $this->font_file = $this->ttf->File($this->font_family,$this->font_style); 6093 } 6094 } 6095 6096 // Get the specific height for a text string 6097 function GetTextHeight($txt="",$angle=0) { 6098 $tmp = split("\n",$txt); 6099 $n = count($tmp); 6100 $m=0; 6101 for($i=0; $i< $n; ++$i) 6102 $m = max($m,strlen($tmp[$i])); 6103 6104 if( $this->font_family <= FF_FONT2+1 ) { 6105 if( $angle==0 ) { 6106 $h = imagefontheight($this->font_family); 6107 if( $h === false ) { 6108 JpGraphError::RaiseL(25088);//('You have a misconfigured GD font support. The call to imagefontwidth() fails.'); 6109 } 6110 6111 return $n*$h; 6112 } 6113 else { 6114 $w = @imagefontwidth($this->font_family); 6115 if( $w === false ) { 6116 JpGraphError::RaiseL(25088);//('You have a misconfigured GD font support. The call to imagefontwidth() fails.'); 6117 } 6118 6119 return $m*$w; 6120 } 6121 } 6122 else { 6123 $bbox = $this->GetTTFBBox($txt,$angle); 6124 return $bbox[1]-$bbox[5]; 6125 } 6126 } 6127 6128 // Estimate font height 6129 function GetFontHeight($angle=0) { 6130 $txt = "XOMg"; 6131 return $this->GetTextHeight($txt,$angle); 6132 } 6133 6134 // Approximate font width with width of letter "O" 6135 function GetFontWidth($angle=0) { 6136 $txt = 'O'; 6137 return $this->GetTextWidth($txt,$angle); 6138 } 6139 6140 // Get actual width of text in absolute pixels 6141 function GetTextWidth($txt,$angle=0) { 6142 6143 $tmp = split("\n",$txt); 6144 $n = count($tmp); 6145 if( $this->font_family <= FF_FONT2+1 ) { 6146 6147 $m=0; 6148 for($i=0; $i < $n; ++$i) { 6149 $l=strlen($tmp[$i]); 6150 if( $l > $m ) { 6151 $m = $l; 6152 } 6153 } 6154 6155 if( $angle==0 ) { 6156 $w = @imagefontwidth($this->font_family); 6157 if( $w === false ) { 6158 JpGraphError::RaiseL(25088);//('You have a misconfigured GD font support. The call to imagefontwidth() fails.'); 6159 } 6160 return $m*$w; 6161 } 6162 else { 6163 // 90 degrees internal so height becomes width 6164 $h = @imagefontheight($this->font_family); 6165 if( $h === false ) { 6166 JpGraphError::RaiseL(25089);//('You have a misconfigured GD font support. The call to imagefontheight() fails.'); 6167 } 6168 return $n*$h; 6169 } 6170 } 6171 else { 6172 // For TTF fonts we must walk through a lines and find the 6173 // widest one which we use as the width of the multi-line 6174 // paragraph 6175 $m=0; 6176 for( $i=0; $i < $n; ++$i ) { 6177 $bbox = $this->GetTTFBBox($tmp[$i],$angle); 6178 $mm = $bbox[2] - $bbox[0]; 6179 if( $mm > $m ) 6180 $m = $mm; 6181 } 6182 return $m; 6183 } 6184 } 6185 6186 // Draw text with a box around it 6187 function StrokeBoxedText($x,$y,$txt,$dir=0,$fcolor="white",$bcolor="black", 6188 $shadowcolor=false,$paragraph_align="left", 6189 $xmarg=6,$ymarg=4,$cornerradius=0,$dropwidth=3) { 6190 6191 if( !is_numeric($dir) ) { 6192 if( $dir=="h" ) $dir=0; 6193 elseif( $dir=="v" ) $dir=90; 6194 else JpGraphError::RaiseL(25090,$dir);//(" Unknown direction specified in call to StrokeBoxedText() [$dir]"); 6195 } 6196 6197 if( $this->font_family >= FF_FONT0 && $this->font_family <= FF_FONT2+1) { 6198 $width=$this->GetTextWidth($txt,$dir) ; 6199 $height=$this->GetTextHeight($txt,$dir) ; 6200 } 6201 else { 6202 $width=$this->GetBBoxWidth($txt,$dir) ; 6203 $height=$this->GetBBoxHeight($txt,$dir) ; 6204 } 6205 6206 $height += 2*$ymarg; 6207 $width += 2*$xmarg; 6208 6209 if( $this->text_halign=="right" ) $x -= $width; 6210 elseif( $this->text_halign=="center" ) $x -= $width/2; 6211 if( $this->text_valign=="bottom" ) $y -= $height; 6212 elseif( $this->text_valign=="center" ) $y -= $height/2; 6213 6214 if( $shadowcolor ) { 6215 $this->PushColor($shadowcolor); 6216 $this->FilledRoundedRectangle($x-$xmarg+$dropwidth,$y-$ymarg+$dropwidth, 6217 $x+$width+$dropwidth,$y+$height-$ymarg+$dropwidth, 6218 $cornerradius); 6219 $this->PopColor(); 6220 $this->PushColor($fcolor); 6221 $this->FilledRoundedRectangle($x-$xmarg,$y-$ymarg, 6222 $x+$width,$y+$height-$ymarg, 6223 $cornerradius); 6224 $this->PopColor(); 6225 $this->PushColor($bcolor); 6226 $this->RoundedRectangle($x-$xmarg,$y-$ymarg, 6227 $x+$width,$y+$height-$ymarg,$cornerradius); 6228 $this->PopColor(); 6229 } 6230 else { 6231 if( $fcolor ) { 6232 $oc=$this->current_color; 6233 $this->SetColor($fcolor); 6234 $this->FilledRoundedRectangle($x-$xmarg,$y-$ymarg,$x+$width,$y+$height-$ymarg,$cornerradius); 6235 $this->current_color=$oc; 6236 } 6237 if( $bcolor ) { 6238 $oc=$this->current_color; 6239 $this->SetColor($bcolor); 6240 $this->RoundedRectangle($x-$xmarg,$y-$ymarg,$x+$width,$y+$height-$ymarg,$cornerradius); 6241 $this->current_color=$oc; 6242 } 6243 } 6244 6245 $h=$this->text_halign; 6246 $v=$this->text_valign; 6247 $this->SetTextAlign("left","top"); 6248 $this->StrokeText($x, $y, $txt, $dir, $paragraph_align); 6249 $bb = array($x-$xmarg,$y+$height-$ymarg,$x+$width,$y+$height-$ymarg, 6250 $x+$width,$y-$ymarg,$x-$xmarg,$y-$ymarg); 6251 $this->SetTextAlign($h,$v); 6252 return $bb; 6253 } 6254 6255 // Set text alignment 6256 function SetTextAlign($halign,$valign="bottom") { 6257 $this->text_halign=$halign; 6258 $this->text_valign=$valign; 6259 } 6260 6261 6262 function _StrokeBuiltinFont($x,$y,$txt,$dir=0,$paragraph_align="left",&$aBoundingBox,$aDebug=false) { 6263 6264 if( is_numeric($dir) && $dir!=90 && $dir!=0) 6265 JpGraphError::RaiseL(25091);//(" Internal font does not support drawing text at arbitrary angle. Use TTF fonts instead."); 6266 6267 $h=$this->GetTextHeight($txt); 6268 $fh=$this->GetFontHeight(); 6269 $w=$this->GetTextWidth($txt); 6270 6271 if( $this->text_halign=="right") 6272 $x -= $dir==0 ? $w : $h; 6273 elseif( $this->text_halign=="center" ) { 6274 // For center we subtract 1 pixel since this makes the middle 6275 // be prefectly in the middle 6276 $x -= $dir==0 ? $w/2-1 : $h/2; 6277 } 6278 if( $this->text_valign=="top" ) 6279 $y += $dir==0 ? $h : $w; 6280 elseif( $this->text_valign=="center" ) 6281 $y += $dir==0 ? $h/2 : $w/2; 6282 6283 if( $dir==90 ) { 6284 imagestringup($this->img,$this->font_family,$x,$y,$txt,$this->current_color); 6285 $aBoundingBox = array(round($x),round($y),round($x),round($y-$w),round($x+$h),round($y-$w),round($x+$h),round($y)); 6286 if( $aDebug ) { 6287 // Draw bounding box 6288 $this->PushColor('green'); 6289 $this->Polygon($aBoundingBox,true); 6290 $this->PopColor(); 6291 } 6292 } 6293 else { 6294 if( ereg("\n",$txt) ) { 6295 $tmp = split("\n",$txt); 6296 for($i=0; $i < count($tmp); ++$i) { 6297 $w1 = $this->GetTextWidth($tmp[$i]); 6298 if( $paragraph_align=="left" ) { 6299 imagestring($this->img,$this->font_family,$x,$y-$h+1+$i*$fh,$tmp[$i],$this->current_color); 6300 } 6301 elseif( $paragraph_align=="right" ) { 6302 imagestring($this->img,$this->font_family,$x+($w-$w1), 6303 $y-$h+1+$i*$fh,$tmp[$i],$this->current_color); 6304 } 6305 else { 6306 imagestring($this->img,$this->font_family,$x+$w/2-$w1/2, 6307 $y-$h+1+$i*$fh,$tmp[$i],$this->current_color); 6308 } 6309 } 6310 } 6311 else { 6312 //Put the text 6313 imagestring($this->img,$this->font_family,$x,$y-$h+1,$txt,$this->current_color); 6314 } 6315 if( $aDebug ) { 6316 // Draw the bounding rectangle and the bounding box 6317 $p1 = array(round($x),round($y),round($x),round($y-$h),round($x+$w),round($y-$h),round($x+$w),round($y)); 6318 6319 // Draw bounding box 6320 $this->PushColor('green'); 6321 $this->Polygon($p1,true); 6322 $this->PopColor(); 6323 6324 } 6325 $aBoundingBox=array(round($x),round($y),round($x),round($y-$h),round($x+$w),round($y-$h),round($x+$w),round($y)); 6326 } 6327 } 6328 6329 function AddTxtCR($aTxt) { 6330 // If the user has just specified a '\n' 6331 // instead of '\n\t' we have to add '\r' since 6332 // the width will be too muchy otherwise since when 6333 // we print we stroke the individually lines by hand. 6334 $e = explode("\n",$aTxt); 6335 $n = count($e); 6336 for($i=0; $i<$n; ++$i) { 6337 $e[$i]=str_replace("\r","",$e[$i]); 6338 } 6339 return implode("\n\r",$e); 6340 } 6341 6342 function GetTTFBBox($aTxt,$aAngle=0) { 6343 $bbox = @ImageTTFBBox($this->font_size,$aAngle,$this->font_file,$aTxt); 6344 if( $bbox === false ) { 6345 JpGraphError::RaiseL(25092,$this->font_file); 6346//("There is either a configuration problem with TrueType or a problem reading font file (".$this->font_file."). Make sure file exists and is in a readable place for the HTTP process. (If 'basedir' restriction is enabled in PHP then the font file must be located in the document root.). It might also be a wrongly installed FreeType library. Try uppgrading to at least FreeType 2.1.13 and recompile GD with the correct setup so it can find the new FT library."); 6347 } 6348 return $bbox; 6349 } 6350 6351 function GetBBoxTTF($aTxt,$aAngle=0) { 6352 // Normalize the bounding box to become a minimum 6353 // enscribing rectangle 6354 6355 $aTxt = $this->AddTxtCR($aTxt); 6356 6357 if( !is_readable($this->font_file) ) { 6358 JpGraphError::RaiseL(25093,$this->font_file); 6359//('Can not read font file ('.$this->font_file.') in call to Image::GetBBoxTTF. Please make sure that you have set a font before calling this method and that the font is installed in the TTF directory.'); 6360 } 6361 $bbox = $this->GetTTFBBox($aTxt,$aAngle); 6362 6363 if( $aAngle==0 ) 6364 return $bbox; 6365 if( $aAngle >= 0 ) { 6366 if( $aAngle <= 90 ) { //<=0 6367 $bbox = array($bbox[6],$bbox[1],$bbox[2],$bbox[1], 6368 $bbox[2],$bbox[5],$bbox[6],$bbox[5]); 6369 } 6370 elseif( $aAngle <= 180 ) { //<= 2 6371 $bbox = array($bbox[4],$bbox[7],$bbox[0],$bbox[7], 6372 $bbox[0],$bbox[3],$bbox[4],$bbox[3]); 6373 } 6374 elseif( $aAngle <= 270 ) { //<= 3 6375 $bbox = array($bbox[2],$bbox[5],$bbox[6],$bbox[5], 6376 $bbox[6],$bbox[1],$bbox[2],$bbox[1]); 6377 } 6378 else { 6379 $bbox = array($bbox[0],$bbox[3],$bbox[4],$bbox[3], 6380 $bbox[4],$bbox[7],$bbox[0],$bbox[7]); 6381 } 6382 } 6383 elseif( $aAngle < 0 ) { 6384 if( $aAngle <= -270 ) { // <= -3 6385 $bbox = array($bbox[6],$bbox[1],$bbox[2],$bbox[1], 6386 $bbox[2],$bbox[5],$bbox[6],$bbox[5]); 6387 } 6388 elseif( $aAngle <= -180 ) { // <= -2 6389 $bbox = array($bbox[0],$bbox[3],$bbox[4],$bbox[3], 6390 $bbox[4],$bbox[7],$bbox[0],$bbox[7]); 6391 } 6392 elseif( $aAngle <= -90 ) { // <= -1 6393 $bbox = array($bbox[2],$bbox[5],$bbox[6],$bbox[5], 6394 $bbox[6],$bbox[1],$bbox[2],$bbox[1]); 6395 } 6396 else { 6397 $bbox = array($bbox[0],$bbox[3],$bbox[4],$bbox[3], 6398 $bbox[4],$bbox[7],$bbox[0],$bbox[7]); 6399 } 6400 } 6401 return $bbox; 6402 } 6403 6404 function GetBBoxHeight($aTxt,$aAngle=0) { 6405 $box = $this->GetBBoxTTF($aTxt,$aAngle); 6406 return $box[1]-$box[7]+1; 6407 } 6408 6409 function GetBBoxWidth($aTxt,$aAngle=0) { 6410 $box = $this->GetBBoxTTF($aTxt,$aAngle); 6411 return $box[2]-$box[0]+1; 6412 } 6413 6414 function _StrokeTTF($x,$y,$txt,$dir=0,$paragraph_align="left",&$aBoundingBox,$debug=false) { 6415 6416 // Setupo default inter line margin for paragraphs to 6417 // 25% of the font height. 6418 $ConstLineSpacing = 0.25 ; 6419 6420 // Remember the anchor point before adjustment 6421 if( $debug ) { 6422 $ox=$x; 6423 $oy=$y; 6424 } 6425 6426 if( !ereg("\n",$txt) || ($dir>0 && ereg("\n",$txt)) ) { 6427 // Format a single line 6428 6429 $txt = $this->AddTxtCR($txt); 6430 6431 $bbox=$this->GetBBoxTTF($txt,$dir); 6432 6433 // Align x,y ot lower left corner of bbox 6434 $x -= $bbox[0]; 6435 $y -= $bbox[1]; 6436 6437 // Note to self: "topanchor" is deprecated after we changed the 6438 // bopunding box stuff. 6439 if( $this->text_halign=="right" || $this->text_halign=="topanchor" ) 6440 $x -= $bbox[2]-$bbox[0]; 6441 elseif( $this->text_halign=="center" ) $x -= ($bbox[2]-$bbox[0])/2; 6442 6443 if( $this->text_valign=="top" ) $y += abs($bbox[5])+$bbox[1]; 6444 elseif( $this->text_valign=="center" ) $y -= ($bbox[5]-$bbox[1])/2; 6445 6446 ImageTTFText ($this->img, $this->font_size, $dir, $x, $y, 6447 $this->current_color,$this->font_file,$txt); 6448 6449 // Calculate and return the co-ordinates for the bounding box 6450 $box=@ImageTTFBBox($this->font_size,$dir,$this->font_file,$txt); 6451 $p1 = array(); 6452 6453 6454 for($i=0; $i < 4; ++$i) { 6455 $p1[] = round($box[$i*2]+$x); 6456 $p1[] = round($box[$i*2+1]+$y); 6457 } 6458 $aBoundingBox = $p1; 6459 6460 // Debugging code to highlight the bonding box and bounding rectangle 6461 // For text at 0 degrees the bounding box and bounding rectangle are the 6462 // same 6463 if( $debug ) { 6464 // Draw the bounding rectangle and the bounding box 6465 $box=@ImageTTFBBox($this->font_size,$dir,$this->font_file,$txt); 6466 $p = array(); 6467 $p1 = array(); 6468 for($i=0; $i < 4; ++$i) { 6469 $p[] = $bbox[$i*2]+$x; 6470 $p[] = $bbox[$i*2+1]+$y; 6471 $p1[] = $box[$i*2]+$x; 6472 $p1[] = $box[$i*2+1]+$y; 6473 } 6474 6475 // Draw bounding box 6476 $this->PushColor('green'); 6477 $this->Polygon($p1,true); 6478 $this->PopColor(); 6479 6480 // Draw bounding rectangle 6481 $this->PushColor('darkgreen'); 6482 $this->Polygon($p,true); 6483 $this->PopColor(); 6484 6485 // Draw a cross at the anchor point 6486 $this->PushColor('red'); 6487 $this->Line($ox-15,$oy,$ox+15,$oy); 6488 $this->Line($ox,$oy-15,$ox,$oy+15); 6489 $this->PopColor(); 6490 } 6491 } 6492 else { 6493 // Format a text paragraph 6494 $fh=$this->GetFontHeight(); 6495 6496 // Line margin is 25% of font height 6497 $linemargin=round($fh*$ConstLineSpacing); 6498 $fh += $linemargin; 6499 $w=$this->GetTextWidth($txt); 6500 6501 $y -= $linemargin/2; 6502 $tmp = split("\n",$txt); 6503 $nl = count($tmp); 6504 $h = $nl * $fh; 6505 6506 if( $this->text_halign=="right") 6507 $x -= $dir==0 ? $w : $h; 6508 elseif( $this->text_halign=="center" ) { 6509 $x -= $dir==0 ? $w/2 : $h/2; 6510 } 6511 6512 if( $this->text_valign=="top" ) 6513 $y += $dir==0 ? $h : $w; 6514 elseif( $this->text_valign=="center" ) 6515 $y += $dir==0 ? $h/2 : $w/2; 6516 6517 // Here comes a tricky bit. 6518 // Since we have to give the position for the string at the 6519 // baseline this means thaht text will move slightly up 6520 // and down depending on any of it's character descend below 6521 // the baseline, for example a 'g'. To adjust the Y-position 6522 // we therefore adjust the text with the baseline Y-offset 6523 // as used for the current font and size. This will keep the 6524 // baseline at a fixed positoned disregarding the actual 6525 // characters in the string. 6526 $standardbox = $this->GetTTFBBox('Gg',$dir); 6527 $yadj = $standardbox[1]; 6528 $xadj = $standardbox[0]; 6529 $aBoundingBox = array(); 6530 for($i=0; $i < $nl; ++$i) { 6531 $wl = $this->GetTextWidth($tmp[$i]); 6532 $bbox = $this->GetTTFBBox($tmp[$i],$dir); 6533 if( $paragraph_align=="left" ) { 6534 $xl = $x; 6535 } 6536 elseif( $paragraph_align=="right" ) { 6537 $xl = $x + ($w-$wl); 6538 } 6539 else { 6540 // Center 6541 $xl = $x + $w/2 - $wl/2 ; 6542 } 6543 6544 $xl -= $bbox[0]; 6545 $yl = $y - $yadj; 6546 $xl = $xl - $xadj; 6547 ImageTTFText ($this->img, $this->font_size, $dir, 6548 $xl, $yl-($h-$fh)+$fh*$i, 6549 $this->current_color,$this->font_file,$tmp[$i]); 6550 6551 if( $debug ) { 6552 // Draw the bounding rectangle around each line 6553 $box=@ImageTTFBBox($this->font_size,$dir,$this->font_file,$tmp[$i]); 6554 $p = array(); 6555 for($j=0; $j < 4; ++$j) { 6556 $p[] = $bbox[$j*2]+$xl; 6557 $p[] = $bbox[$j*2+1]+$yl-($h-$fh)+$fh*$i; 6558 } 6559 6560 // Draw bounding rectangle 6561 $this->PushColor('darkgreen'); 6562 $this->Polygon($p,true); 6563 $this->PopColor(); 6564 } 6565 } 6566 6567 // Get the bounding box 6568 $bbox = $this->GetBBoxTTF($txt,$dir); 6569 for($j=0; $j < 4; ++$j) { 6570 $bbox[$j*2]+= round($x); 6571 $bbox[$j*2+1]+= round($y - ($h-$fh) - $yadj); 6572 } 6573 $aBoundingBox = $bbox; 6574 6575 if( $debug ) { 6576 // Draw a cross at the anchor point 6577 $this->PushColor('red'); 6578 $this->Line($ox-25,$oy,$ox+25,$oy); 6579 $this->Line($ox,$oy-25,$ox,$oy+25); 6580 $this->PopColor(); 6581 } 6582 6583 } 6584 } 6585 6586 function StrokeText($x,$y,$txt,$dir=0,$paragraph_align="left",$debug=false) { 6587 6588 $x = round($x); 6589 $y = round($y); 6590 6591 // Do special language encoding 6592 $txt = $this->langconv->Convert($txt,$this->font_family); 6593 6594 if( !is_numeric($dir) ) 6595 JpGraphError::RaiseL(25094);//(" Direction for text most be given as an angle between 0 and 90."); 6596 6597 if( $this->font_family >= FF_FONT0 && $this->font_family <= FF_FONT2+1) { 6598 $this->_StrokeBuiltinFont($x,$y,$txt,$dir,$paragraph_align,$boundingbox,$debug); 6599 } 6600 elseif($this->font_family >= _FIRST_FONT && $this->font_family <= _LAST_FONT) { 6601 $this->_StrokeTTF($x,$y,$txt,$dir,$paragraph_align,$boundingbox,$debug); 6602 } 6603 else 6604 JpGraphError::RaiseL(25095);//(" Unknown font font family specification. "); 6605 return $boundingbox; 6606 } 6607 6608 function SetMargin($lm,$rm,$tm,$bm) { 6609 $this->left_margin=$lm; 6610 $this->right_margin=$rm; 6611 $this->top_margin=$tm; 6612 $this->bottom_margin=$bm; 6613 $this->plotwidth=$this->width - $this->left_margin-$this->right_margin ; 6614 $this->plotheight=$this->height - $this->top_margin-$this->bottom_margin ; 6615 if( $this->width > 0 && $this->height > 0 ) { 6616 if( $this->plotwidth < 0 || $this->plotheight < 0 ) 6617 JpGraphError::raise("To small plot area. ($lm,$rm,$tm,$bm : $this->plotwidth x $this->plotheight). With the given image size and margins there is to little space left for the plot. Increase the plot size or reduce the margins."); 6618 } 6619 } 6620 6621 function SetTransparent($color) { 6622 imagecolortransparent ($this->img,$this->rgb->allocate($color)); 6623 } 6624 6625 function SetColor($color,$aAlpha=0) { 6626 $this->current_color_name = $color; 6627 $this->current_color=$this->rgb->allocate($color,$aAlpha); 6628 if( $this->current_color == -1 ) { 6629 $tc=imagecolorstotal($this->img); 6630 JpGraphError::RaiseL(25096); 6631//("Can't allocate any more colors. Image has already allocated maximum of <b>$tc colors</b>. This might happen if you have anti-aliasing turned on together with a background image or perhaps gradient fill since this requires many, many colors. Try to turn off anti-aliasing. If there is still a problem try downgrading the quality of the background image to use a smaller pallete to leave some entries for your graphs. You should try to limit the number of colors in your background image to 64. If there is still problem set the constant DEFINE(\"USE_APPROX_COLORS\",true); in jpgraph.php This will use approximative colors when the palette is full. Unfortunately there is not much JpGraph can do about this since the palette size is a limitation of current graphic format and what the underlying GD library suppports."); 6632 } 6633 return $this->current_color; 6634 } 6635 6636 function PushColor($color) { 6637 if( $color != "" ) { 6638 $this->colorstack[$this->colorstackidx]=$this->current_color_name; 6639 $this->colorstack[$this->colorstackidx+1]=$this->current_color; 6640 $this->colorstackidx+=2; 6641 $this->SetColor($color); 6642 } 6643 else { 6644 JpGraphError::RaiseL(25097);//("Color specified as empty string in PushColor()."); 6645 } 6646 } 6647 6648 function PopColor() { 6649 if($this->colorstackidx<1) 6650 JpGraphError::RaiseL(25098);//(" Negative Color stack index. Unmatched call to PopColor()"); 6651 $this->current_color=$this->colorstack[--$this->colorstackidx]; 6652 $this->current_color_name=$this->colorstack[--$this->colorstackidx]; 6653 } 6654 6655 6656 // Why this duplication? Because this way we can call this method 6657 // for any image and not only the current objsct 6658 function AdjSat($sat) { 6659 if( USE_TRUECOLOR ) 6660 return; 6661 $this->_AdjSat($this->img,$sat); 6662 } 6663 6664 function _AdjSat($img,$sat) { 6665 $nbr = imagecolorstotal ($img); 6666 for( $i=0; $i<$nbr; ++$i ) { 6667 $colarr = imagecolorsforindex ($img,$i); 6668 $rgb[0]=$colarr["red"]; 6669 $rgb[1]=$colarr["green"]; 6670 $rgb[2]=$colarr["blue"]; 6671 $rgb = $this->AdjRGBSat($rgb,$sat); 6672 imagecolorset ($img, $i, $rgb[0], $rgb[1], $rgb[2]); 6673 } 6674 } 6675 6676 function AdjBrightContrast($bright,$contr=0) { 6677 if( USE_TRUECOLOR ) 6678 return; 6679 $this->_AdjBrightContrast($this->img,$bright,$contr); 6680 } 6681 6682 function _AdjBrightContrast($img,$bright,$contr=0) { 6683 if( $bright < -1 || $bright > 1 || $contr < -1 || $contr > 1 ) 6684 JpGraphError::RaiseL(25099);//(" Parameters for brightness and Contrast out of range [-1,1]"); 6685 $nbr = imagecolorstotal ($img); 6686 for( $i=0; $i<$nbr; ++$i ) { 6687 $colarr = imagecolorsforindex ($img,$i); 6688 $r = $this->AdjRGBBrightContrast($colarr["red"],$bright,$contr); 6689 $g = $this->AdjRGBBrightContrast($colarr["green"],$bright,$contr); 6690 $b = $this->AdjRGBBrightContrast($colarr["blue"],$bright,$contr); 6691 imagecolorset ($img, $i, $r, $g, $b); 6692 } 6693 } 6694 6695 // Private helper function for adj sat 6696 // Adjust saturation for RGB array $u. $sat is a value between -1 and 1 6697 // Note: Due to GD inability to handle true color the RGB values are only between 6698 // 8 bit. This makes saturation quite sensitive for small increases in parameter sat. 6699 // 6700 // Tip: To get a grayscale picture set sat=-100, values <-100 changes the colors 6701 // to it's complement. 6702 // 6703 // Implementation note: The saturation is implemented directly in the RGB space 6704 // by adjusting the perpendicular distance between the RGB point and the "grey" 6705 // line (1,1,1). Setting $sat>0 moves the point away from the line along the perp. 6706 // distance and a negative value moves the point closer to the line. 6707 // The values are truncated when the color point hits the bounding box along the 6708 // RGB axis. 6709 // DISCLAIMER: I'm not 100% sure this is he correct way to implement a color 6710 // saturation function in RGB space. However, it looks ok and has the expected effect. 6711 function AdjRGBSat($rgb,$sat) { 6712 // TODO: Should be moved to the RGB class 6713 // Grey vector 6714 $v=array(1,1,1); 6715 6716 // Dot product 6717 $dot = $rgb[0]*$v[0]+$rgb[1]*$v[1]+$rgb[2]*$v[2]; 6718 6719 // Normalize dot product 6720 $normdot = $dot/3; // dot/|v|^2 6721 6722 // Direction vector between $u and its projection onto $v 6723 for($i=0; $i<3; ++$i) 6724 $r[$i] = $rgb[$i] - $normdot*$v[$i]; 6725 6726 // Adjustment factor so that sat==1 sets the highest RGB value to 255 6727 if( $sat > 0 ) { 6728 $m=0; 6729 for( $i=0; $i<3; ++$i) { 6730 if( sign($r[$i]) == 1 && $r[$i]>0) 6731 $m=max($m,(255-$rgb[$i])/$r[$i]); 6732 } 6733 $tadj=$m; 6734 } 6735 else 6736 $tadj=1; 6737 6738 $tadj = $tadj*$sat; 6739 for($i=0; $i<3; ++$i) { 6740 $un[$i] = round($rgb[$i] + $tadj*$r[$i]); 6741 if( $un[$i]<0 ) $un[$i]=0; // Truncate color when they reach 0 6742 if( $un[$i]>255 ) $un[$i]=255;// Avoid potential rounding error 6743 } 6744 return $un; 6745 } 6746 6747 // Private helper function for AdjBrightContrast 6748 function AdjRGBBrightContrast($rgb,$bright,$contr) { 6749 // TODO: Should be moved to the RGB class 6750 // First handle contrast, i.e change the dynamic range around grey 6751 if( $contr <= 0 ) { 6752 // Decrease contrast 6753 $adj = abs($rgb-128) * (-$contr); 6754 if( $rgb < 128 ) $rgb += $adj; 6755 else $rgb -= $adj; 6756 } 6757 else { // $contr > 0 6758 // Increase contrast 6759 if( $rgb < 128 ) $rgb = $rgb - ($rgb * $contr); 6760 else $rgb = $rgb + ((255-$rgb) * $contr); 6761 } 6762 6763 // Add (or remove) various amount of white 6764 $rgb += $bright*255; 6765 $rgb=min($rgb,255); 6766 $rgb=max($rgb,0); 6767 return $rgb; 6768 } 6769 6770 function SetLineWeight($weight) { 6771 imagesetthickness($this->img,$weight); 6772 $this->line_weight = $weight; 6773 } 6774 6775 function SetStartPoint($x,$y) { 6776 $this->lastx=round($x); 6777 $this->lasty=round($y); 6778 } 6779 6780 function Arc($cx,$cy,$w,$h,$s,$e) { 6781 // GD Arc doesn't like negative angles 6782 while( $s < 0) $s += 360; 6783 while( $e < 0) $e += 360; 6784 6785 imagearc($this->img,round($cx),round($cy),round($w),round($h), 6786 $s,$e,$this->current_color); 6787 } 6788 6789 function FilledArc($xc,$yc,$w,$h,$s,$e,$style='') { 6790 while( $s < 0 ) $s += 360; 6791 while( $e < 0 ) $e += 360; 6792 if( $style=='' ) 6793 $style=IMG_ARC_PIE; 6794 imagefilledarc($this->img,round($xc),round($yc),round($w),round($h), 6795 round($s),round($e),$this->current_color,$style); 6796 } 6797 6798 function FilledCakeSlice($cx,$cy,$w,$h,$s,$e) { 6799 $this->CakeSlice($cx,$cy,$w,$h,$s,$e,$this->current_color_name); 6800 } 6801 6802 function CakeSlice($xc,$yc,$w,$h,$s,$e,$fillcolor="",$arccolor="") { 6803 $s = round($s); $e = round($e); 6804 $w = round($w); $h = round($h); 6805 $xc = round($xc); $yc = round($yc); 6806 $this->PushColor($fillcolor); 6807 $this->FilledArc($xc,$yc,2*$w,2*$h,$s,$e); 6808 $this->PopColor(); 6809 if( $arccolor != "" ) { 6810 $this->PushColor($arccolor); 6811 // We add 2 pixels to make the Arc() better aligned with 6812 // the filled arc. 6813 imagefilledarc($this->img,$xc,$yc,2*$w,2*$h,$s,$e,$this->current_color,IMG_ARC_NOFILL | IMG_ARC_EDGED ) ; 6814 $this->PopColor(); 6815 } 6816 } 6817 6818 function Ellipse($xc,$yc,$w,$h) { 6819 $this->Arc($xc,$yc,$w,$h,0,360); 6820 } 6821 6822 function Circle($xc,$yc,$r) { 6823 imageellipse($this->img,round($xc),round($yc),$r*2,$r*2,$this->current_color); 6824 } 6825 6826 function FilledCircle($xc,$yc,$r) { 6827 imagefilledellipse($this->img,round($xc),round($yc),2*$r,2*$r,$this->current_color); 6828 } 6829 6830 // Linear Color InterPolation 6831 function lip($f,$t,$p) { 6832 $p = round($p,1); 6833 $r = $f[0] + ($t[0]-$f[0])*$p; 6834 $g = $f[1] + ($t[1]-$f[1])*$p; 6835 $b = $f[2] + ($t[2]-$f[2])*$p; 6836 return array($r,$g,$b); 6837 } 6838 6839 // Set line style dashed, dotted etc 6840 function SetLineStyle($s) { 6841 if( is_numeric($s) ) { 6842 if( $s<1 || $s>4 ) 6843 JpGraphError::RaiseL(25101,$s);//(" Illegal numeric argument to SetLineStyle(): ($s)"); 6844 } 6845 elseif( is_string($s) ) { 6846 if( $s == "solid" ) $s=1; 6847 elseif( $s == "dotted" ) $s=2; 6848 elseif( $s == "dashed" ) $s=3; 6849 elseif( $s == "longdashed" ) $s=4; 6850 else JpGraphError::RaiseL(25102,$s);//(" Illegal string argument to SetLineStyle(): $s"); 6851 } 6852 else JpGraphError::RaiseL(25103,$s);//(" Illegal argument to SetLineStyle $s"); 6853 $this->line_style=$s; 6854 } 6855 6856 // Same as Line but take the line_style into account 6857 function StyleLine($x1,$y1,$x2,$y2) { 6858 switch( $this->line_style ) { 6859 case 1:// Solid 6860 $this->Line($x1,$y1,$x2,$y2); 6861 break; 6862 case 2: // Dotted 6863 $this->DashedLine($x1,$y1,$x2,$y2,1,3); 6864 break; 6865 case 3: // Dashed 6866 $this->DashedLine($x1,$y1,$x2,$y2,2,4); 6867 break; 6868 case 4: // Longdashes 6869 $this->DashedLine($x1,$y1,$x2,$y2,8,6); 6870 break; 6871 default: 6872 JpGraphError::RaiseL(25104,$this->line_style);//(" Unknown line style: $this->line_style "); 6873 break; 6874 } 6875 } 6876 6877 function DashedLine($x1,$y1,$x2,$y2,$dash_length=1,$dash_space=4) { 6878 6879 $x1 = round($x1); 6880 $x2 = round($x2); 6881 $y1 = round($y1); 6882 $y2 = round($y2); 6883 6884 $style = array_fill(0,$dash_length,$this->current_color); 6885 $style = array_pad($style,$dash_length+$dash_space,IMG_COLOR_TRANSPARENT); 6886 imagesetstyle($this->img, $style); 6887 imageline($this->img, $x1, $y1, $x2, $y2, IMG_COLOR_STYLED); 6888 $this->lastx=$x2; $this->lasty=$y2; 6889 } 6890 6891 function Line($x1,$y1,$x2,$y2) { 6892 6893 $x1 = round($x1); 6894 $x2 = round($x2); 6895 $y1 = round($y1); 6896 $y2 = round($y2); 6897 6898 imageline($this->img,$x1,$y1,$x2,$y2,$this->current_color); 6899 $this->lastx=$x2; $this->lasty=$y2; 6900 } 6901 6902 function Polygon($p,$closed=FALSE,$fast=FALSE) { 6903 if( $this->line_weight==0 ) return; 6904 $n=count($p); 6905 $oldx = $p[0]; 6906 $oldy = $p[1]; 6907 if( $fast ) { 6908 for( $i=2; $i < $n; $i+=2 ) { 6909 imageline($this->img,$oldx,$oldy,$p[$i],$p[$i+1],$this->current_color); 6910 $oldx = $p[$i]; 6911 $oldy = $p[$i+1]; 6912 } 6913 if( $closed ) { 6914 imageline($this->img,$p[$n*2-2],$p[$n*2-1],$p[0],$p[1],$this->current_color); 6915 } 6916 } 6917 else { 6918 for( $i=2; $i < $n; $i+=2 ) { 6919 $this->StyleLine($oldx,$oldy,$p[$i],$p[$i+1]); 6920 $oldx = $p[$i]; 6921 $oldy = $p[$i+1]; 6922 } 6923 if( $closed ) 6924 $this->StyleLine($oldx,$oldy,$p[0],$p[1]); 6925 } 6926 } 6927 6928 function FilledPolygon($pts) { 6929 $n=count($pts); 6930 if( $n == 0 ) { 6931 JpGraphError::RaiseL(25105);//('NULL data specified for a filled polygon. Check that your data is not NULL.'); 6932 } 6933 for($i=0; $i < $n; ++$i) 6934 $pts[$i] = round($pts[$i]); 6935 imagefilledpolygon($this->img,$pts,count($pts)/2,$this->current_color); 6936 } 6937 6938 function Rectangle($xl,$yu,$xr,$yl) { 6939 $this->Polygon(array($xl,$yu,$xr,$yu,$xr,$yl,$xl,$yl,$xl,$yu)); 6940 } 6941 6942 function FilledRectangle($xl,$yu,$xr,$yl) { 6943 $this->FilledPolygon(array($xl,$yu,$xr,$yu,$xr,$yl,$xl,$yl)); 6944 } 6945 6946 function FilledRectangle2($xl,$yu,$xr,$yl,$color1,$color2,$style=1) { 6947 // Fill a rectangle with lines of two colors 6948 if( $style===1 ) { 6949 // Horizontal stripe 6950 if( $yl < $yu ) { 6951 $t = $yl; $yl=$yu; $yu=$t; 6952 } 6953 for( $y=$yu; $y <= $yl; ++$y) { 6954 $this->SetColor($color1); 6955 $this->Line($xl,$y,$xr,$y); 6956 ++$y; 6957 $this->SetColor($color2); 6958 $this->Line($xl,$y,$xr,$y); 6959 } 6960 } 6961 else { 6962 if( $xl < $xl ) { 6963 $t = $xl; $xl=$xr; $xr=$t; 6964 } 6965 for( $x=$xl; $x <= $xr; ++$x) { 6966 $this->SetColor($color1); 6967 $this->Line($x,$yu,$x,$yl); 6968 ++$x; 6969 $this->SetColor($color2); 6970 $this->Line($x,$yu,$x,$yl); 6971 } 6972 } 6973 } 6974 6975 function ShadowRectangle($xl,$yu,$xr,$yl,$fcolor=false,$shadow_width=3,$shadow_color=array(102,102,102)) { 6976 // This is complicated by the fact that we must also handle the case where 6977 // the reactangle has no fill color 6978 $this->PushColor($shadow_color); 6979 $this->FilledRectangle($xr-$shadow_width,$yu+$shadow_width,$xr,$yl-$shadow_width-1); 6980 $this->FilledRectangle($xl+$shadow_width,$yl-$shadow_width,$xr,$yl); 6981 //$this->FilledRectangle($xl+$shadow_width,$yu+$shadow_width,$xr,$yl); 6982 $this->PopColor(); 6983 if( $fcolor==false ) 6984 $this->Rectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1); 6985 else { 6986 $this->PushColor($fcolor); 6987 $this->FilledRectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1); 6988 $this->PopColor(); 6989 $this->Rectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1); 6990 } 6991 } 6992 6993 function FilledRoundedRectangle($xt,$yt,$xr,$yl,$r=5) { 6994 if( $r==0 ) { 6995 $this->FilledRectangle($xt,$yt,$xr,$yl); 6996 return; 6997 } 6998 6999 // To avoid overlapping fillings (which will look strange 7000 // when alphablending is enabled) we have no choice but 7001 // to fill the five distinct areas one by one. 7002 7003 // Center square 7004 $this->FilledRectangle($xt+$r,$yt+$r,$xr-$r,$yl-$r); 7005 // Top band 7006 $this->FilledRectangle($xt+$r,$yt,$xr-$r,$yt+$r-1); 7007 // Bottom band 7008 $this->FilledRectangle($xt+$r,$yl-$r+1,$xr-$r,$yl); 7009 // Left band 7010 $this->FilledRectangle($xt,$yt+$r+1,$xt+$r-1,$yl-$r); 7011 // Right band 7012 $this->FilledRectangle($xr-$r+1,$yt+$r,$xr,$yl-$r); 7013 7014 // Topleft & Topright arc 7015 $this->FilledArc($xt+$r,$yt+$r,$r*2,$r*2,180,270); 7016 $this->FilledArc($xr-$r,$yt+$r,$r*2,$r*2,270,360); 7017 7018 // Bottomleft & Bottom right arc 7019 $this->FilledArc($xt+$r,$yl-$r,$r*2,$r*2,90,180); 7020 $this->FilledArc($xr-$r,$yl-$r,$r*2,$r*2,0,90); 7021 7022 } 7023 7024 function RoundedRectangle($xt,$yt,$xr,$yl,$r=5) { 7025 7026 if( $r==0 ) { 7027 $this->Rectangle($xt,$yt,$xr,$yl); 7028 return; 7029 } 7030 7031 // Top & Bottom line 7032 $this->Line($xt+$r,$yt,$xr-$r,$yt); 7033 $this->Line($xt+$r,$yl,$xr-$r,$yl); 7034 7035 // Left & Right line 7036 $this->Line($xt,$yt+$r,$xt,$yl-$r); 7037 $this->Line($xr,$yt+$r,$xr,$yl-$r); 7038 7039 // Topleft & Topright arc 7040 $this->Arc($xt+$r,$yt+$r,$r*2,$r*2,180,270); 7041 $this->Arc($xr-$r,$yt+$r,$r*2,$r*2,270,360); 7042 7043 // Bottomleft & Bottomright arc 7044 $this->Arc($xt+$r,$yl-$r,$r*2,$r*2,90,180); 7045 $this->Arc($xr-$r,$yl-$r,$r*2,$r*2,0,90); 7046 } 7047 7048 function FilledBevel($x1,$y1,$x2,$y2,$depth=2,$color1='white@0.4',$color2='darkgray@0.4') { 7049 $this->FilledRectangle($x1,$y1,$x2,$y2); 7050 $this->Bevel($x1,$y1,$x2,$y2,$depth,$color1,$color2); 7051 } 7052 7053 function Bevel($x1,$y1,$x2,$y2,$depth=2,$color1='white@0.4',$color2='black@0.5') { 7054 $this->PushColor($color1); 7055 for( $i=0; $i < $depth; ++$i ) { 7056 $this->Line($x1+$i,$y1+$i,$x1+$i,$y2-$i); 7057 $this->Line($x1+$i,$y1+$i,$x2-$i,$y1+$i); 7058 } 7059 $this->PopColor(); 7060 7061 $this->PushColor($color2); 7062 for( $i=0; $i < $depth; ++$i ) { 7063 $this->Line($x1+$i,$y2-$i,$x2-$i,$y2-$i); 7064 $this->Line($x2-$i,$y1+$i,$x2-$i,$y2-$i-1); 7065 } 7066 $this->PopColor(); 7067 } 7068 7069 function StyleLineTo($x,$y) { 7070 $this->StyleLine($this->lastx,$this->lasty,$x,$y); 7071 $this->lastx=$x; 7072 $this->lasty=$y; 7073 } 7074 7075 function LineTo($x,$y) { 7076 $this->Line($this->lastx,$this->lasty,$x,$y); 7077 $this->lastx=$x; 7078 $this->lasty=$y; 7079 } 7080 7081 function Point($x,$y) { 7082 imagesetpixel($this->img,round($x),round($y),$this->current_color); 7083 } 7084 7085 function Fill($x,$y) { 7086 imagefill($this->img,round($x),round($y),$this->current_color); 7087 } 7088 7089 function FillToBorder($x,$y,$aBordColor) { 7090 $bc = $this->rgb->allocate($aBordColor); 7091 if( $bc == -1 ) { 7092 JpGraphError::RaiseL(25106);//('Image::FillToBorder : Can not allocate more colors'); 7093 exit(); 7094 } 7095 imagefilltoborder($this->img,round($x),round($y),$bc,$this->current_color); 7096 } 7097 7098 function SetExpired($aFlg=true) { 7099 $this->expired = $aFlg; 7100 } 7101 7102 // Generate image header 7103 function Headers() { 7104 7105 // In case we are running from the command line with the client version of 7106 // PHP we can't send any headers. 7107 $sapi = php_sapi_name(); 7108 if( $sapi == 'cli' ) 7109 return; 7110 7111 if( headers_sent($file,$lineno) ) { 7112 $file=basename($file); 7113 $t = new ErrMsgText(); 7114 $msg = $t->Get(10,$file,$lineno); 7115 die($msg); 7116 } 7117 7118 if ($this->expired) { 7119 header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); 7120 header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT"); 7121 header("Cache-Control: no-cache, must-revalidate"); 7122 header("Pragma: no-cache"); 7123 } 7124 header("Content-type: image/$this->img_format"); 7125 } 7126 7127 // Adjust image quality for formats that allow this 7128 function SetQuality($q) { 7129 $this->quality = $q; 7130 } 7131 7132 // Stream image to browser or to file 7133 function Stream($aFile="") { 7134 $func="image".$this->img_format; 7135 if( $this->img_format=="jpeg" && $this->quality != null ) { 7136 $res = @$func($this->img,$aFile,$this->quality); 7137 } 7138 else { 7139 if( $aFile != "" ) { 7140 $res = @$func($this->img,$aFile); 7141 if( !$res ) 7142 JpGraphError::RaiseL(25107,$aFile);//("Can't write to file '$aFile'. Check that the process running PHP has enough permission."); 7143 } 7144 else { 7145 $res = @$func($this->img); 7146 if( !$res ) 7147 JpGraphError::RaiseL(25108);//("Can't stream image. This is most likely due to a faulty PHP/GD setup. Try to recompile PHP and use the built-in GD library that comes with PHP."); 7148 7149 } 7150 } 7151 } 7152 7153 // Clear resource tide up by image 7154 function Destroy() { 7155 imagedestroy($this->img); 7156 } 7157 7158 // Specify image format. Note depending on your installation 7159 // of PHP not all formats may be supported. 7160 function SetImgFormat($aFormat,$aQuality=75) { 7161 $this->quality = $aQuality; 7162 $aFormat = strtolower($aFormat); 7163 $tst = true; 7164 $supported = imagetypes(); 7165 if( $aFormat=="auto" ) { 7166 if( $supported & IMG_PNG ) 7167 $this->img_format="png"; 7168 elseif( $supported & IMG_JPG ) 7169 $this->img_format="jpeg"; 7170 elseif( $supported & IMG_GIF ) 7171 $this->img_format="gif"; 7172 else 7173 JpGraphError::RaiseL(25109);//("Your PHP (and GD-lib) installation does not appear to support any known graphic formats. You need to first make sure GD is compiled as a module to PHP. If you also want to use JPEG images you must get the JPEG library. Please see the PHP docs for details."); 7174 7175 return true; 7176 } 7177 else { 7178 if( $aFormat=="jpeg" || $aFormat=="png" || $aFormat=="gif" ) { 7179 if( $aFormat=="jpeg" && !($supported & IMG_JPG) ) 7180 $tst=false; 7181 elseif( $aFormat=="png" && !($supported & IMG_PNG) ) 7182 $tst=false; 7183 elseif( $aFormat=="gif" && !($supported & IMG_GIF) ) 7184 $tst=false; 7185 else { 7186 $this->img_format=$aFormat; 7187 return true; 7188 } 7189 } 7190 else 7191 $tst=false; 7192 if( !$tst ) 7193 JpGraphError::RaiseL(25110,$aFormat);//(" Your PHP installation does not support the chosen graphic format: $aFormat"); 7194 } 7195 } 7196} // CLASS 7197 7198//=================================================== 7199// CLASS RotImage 7200// Description: Exactly as Image but draws the image at 7201// a specified angle around a specified rotation point. 7202//=================================================== 7203class RotImage extends Image { 7204 public $a=0; 7205 public $dx=0,$dy=0,$transx=0,$transy=0; 7206 private $m=array(); 7207 7208 function RotImage($aWidth,$aHeight,$a=0,$aFormat=DEFAULT_GFORMAT) { 7209 $this->Image($aWidth,$aHeight,$aFormat); 7210 $this->dx=$this->left_margin+$this->plotwidth/2; 7211 $this->dy=$this->top_margin+$this->plotheight/2; 7212 $this->SetAngle($a); 7213 } 7214 7215 function SetCenter($dx,$dy) { 7216 $old_dx = $this->dx; 7217 $old_dy = $this->dy; 7218 $this->dx=$dx; 7219 $this->dy=$dy; 7220 $this->SetAngle($this->a); 7221 return array($old_dx,$old_dy); 7222 } 7223 7224 function SetTranslation($dx,$dy) { 7225 $old = array($this->transx,$this->transy); 7226 $this->transx = $dx; 7227 $this->transy = $dy; 7228 return $old; 7229 } 7230 7231 function UpdateRotMatrice() { 7232 $a = $this->a; 7233 $a *= M_PI/180; 7234 $sa=sin($a); $ca=cos($a); 7235 // Create the rotation matrix 7236 $this->m[0][0] = $ca; 7237 $this->m[0][1] = -$sa; 7238 $this->m[0][2] = $this->dx*(1-$ca) + $sa*$this->dy ; 7239 $this->m[1][0] = $sa; 7240 $this->m[1][1] = $ca; 7241 $this->m[1][2] = $this->dy*(1-$ca) - $sa*$this->dx ; 7242 } 7243 7244 function SetAngle($a) { 7245 $tmp = $this->a; 7246 $this->a = $a; 7247 $this->UpdateRotMatrice(); 7248 return $tmp; 7249 } 7250 7251 function Circle($xc,$yc,$r) { 7252 list($xc,$yc) = $this->Rotate($xc,$yc); 7253 parent::Circle($xc,$yc,$r); 7254 } 7255 7256 function FilledCircle($xc,$yc,$r) { 7257 list($xc,$yc) = $this->Rotate($xc,$yc); 7258 parent::FilledCircle($xc,$yc,$r); 7259 } 7260 7261 7262 function Arc($xc,$yc,$w,$h,$s,$e) { 7263 list($xc,$yc) = $this->Rotate($xc,$yc); 7264 $s += $this->a; 7265 $e += $this->a; 7266 parent::Arc($xc,$yc,$w,$h,$s,$e); 7267 } 7268 7269 function FilledArc($xc,$yc,$w,$h,$s,$e,$style='') { 7270 list($xc,$yc) = $this->Rotate($xc,$yc); 7271 $s += $this->a; 7272 $e += $this->a; 7273 parent::FilledArc($xc,$yc,$w,$h,$s,$e); 7274 } 7275 7276 function SetMargin($lm,$rm,$tm,$bm) { 7277 parent::SetMargin($lm,$rm,$tm,$bm); 7278 $this->dx=$this->left_margin+$this->plotwidth/2; 7279 $this->dy=$this->top_margin+$this->plotheight/2; 7280 $this->UpdateRotMatrice(); 7281 } 7282 7283 function Rotate($x,$y) { 7284 // Optimization. Ignore rotation if Angle==0 || Angle==360 7285 if( $this->a == 0 || $this->a == 360 ) { 7286 return array($x + $this->transx, $y + $this->transy ); 7287 } 7288 else { 7289 $x1=round($this->m[0][0]*$x + $this->m[0][1]*$y,1) + $this->m[0][2] + $this->transx; 7290 $y1=round($this->m[1][0]*$x + $this->m[1][1]*$y,1) + $this->m[1][2] + $this->transy; 7291 return array($x1,$y1); 7292 } 7293 } 7294 7295 function CopyMerge($fromImg,$toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$fromWidth=-1,$fromHeight=-1,$aMix=100) { 7296 list($toX,$toY) = $this->Rotate($toX,$toY); 7297 parent::CopyMerge($fromImg,$toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$fromWidth,$fromHeight,$aMix); 7298 7299 } 7300 7301 function ArrRotate($pnts) { 7302 $n = count($pnts)-1; 7303 for($i=0; $i < $n; $i+=2) { 7304 list ($x,$y) = $this->Rotate($pnts[$i],$pnts[$i+1]); 7305 $pnts[$i] = $x; $pnts[$i+1] = $y; 7306 } 7307 return $pnts; 7308 } 7309 7310 function DashedLine($x1,$y1,$x2,$y2,$dash_length=1,$dash_space=4) { 7311 list($x1,$y1) = $this->Rotate($x1,$y1); 7312 list($x2,$y2) = $this->Rotate($x2,$y2); 7313 parent::DashedLine($x1,$y1,$x2,$y2,$dash_length,$dash_space); 7314 } 7315 7316 function Line($x1,$y1,$x2,$y2) { 7317 list($x1,$y1) = $this->Rotate($x1,$y1); 7318 list($x2,$y2) = $this->Rotate($x2,$y2); 7319 parent::Line($x1,$y1,$x2,$y2); 7320 } 7321 7322 function Rectangle($x1,$y1,$x2,$y2) { 7323 // Rectangle uses Line() so it will be rotated through that call 7324 parent::Rectangle($x1,$y1,$x2,$y2); 7325 } 7326 7327 function FilledRectangle($x1,$y1,$x2,$y2) { 7328 if( $y1==$y2 || $x1==$x2 ) 7329 $this->Line($x1,$y1,$x2,$y2); 7330 else 7331 $this->FilledPolygon(array($x1,$y1,$x2,$y1,$x2,$y2,$x1,$y2)); 7332 } 7333 7334 function Polygon($pnts,$closed=FALSE,$fast=FALSE) { 7335 //Polygon uses Line() so it will be rotated through that call 7336 parent::Polygon($pnts,$closed,$fast); 7337 } 7338 7339 function FilledPolygon($pnts) { 7340 parent::FilledPolygon($this->ArrRotate($pnts)); 7341 } 7342 7343 function Point($x,$y) { 7344 list($xp,$yp) = $this->Rotate($x,$y); 7345 parent::Point($xp,$yp); 7346 } 7347 7348 function StrokeText($x,$y,$txt,$dir=0,$paragraph_align="left",$debug=false) { 7349 list($xp,$yp) = $this->Rotate($x,$y); 7350 return parent::StrokeText($xp,$yp,$txt,$dir,$paragraph_align,$debug); 7351 } 7352} 7353 7354//=================================================== 7355// CLASS ImgStreamCache 7356// Description: Handle caching of graphs to files 7357//=================================================== 7358class ImgStreamCache { 7359 private $cache_dir, $img=null, $timeout=0; // Infinite timeout 7360 //--------------- 7361 // CONSTRUCTOR 7362 function ImgStreamCache($aImg, $aCacheDir=CACHE_DIR) { 7363 $this->img = $aImg; 7364 $this->cache_dir = $aCacheDir; 7365 } 7366 7367//--------------- 7368// PUBLIC METHODS 7369 7370 // Specify a timeout (in minutes) for the file. If the file is older then the 7371 // timeout value it will be overwritten with a newer version. 7372 // If timeout is set to 0 this is the same as infinite large timeout and if 7373 // timeout is set to -1 this is the same as infinite small timeout 7374 function SetTimeout($aTimeout) { 7375 $this->timeout=$aTimeout; 7376 } 7377 7378 // Output image to browser and also write it to the cache 7379 function PutAndStream($aImage,$aCacheFileName,$aInline,$aStrokeFileName) { 7380 // Some debugging code to brand the image with numbe of colors 7381 // used 7382 GLOBAL $gJpgBrandTiming; 7383 7384 if( $gJpgBrandTiming ) { 7385 global $tim; 7386 $t=$tim->Pop()/1000.0; 7387 $c=$aImage->SetColor("black"); 7388 $t=sprintf(BRAND_TIME_FORMAT,round($t,3)); 7389 imagestring($this->img->img,2,5,$this->img->height-20,$t,$c); 7390 } 7391 7392 // Check if we should stroke the image to an arbitrary file 7393 if( _FORCE_IMGTOFILE ) { 7394 $aStrokeFileName = _FORCE_IMGDIR.GenImgName(); 7395 } 7396 7397 if( $aStrokeFileName!="" ) { 7398 if( $aStrokeFileName == "auto" ) 7399 $aStrokeFileName = GenImgName(); 7400 if( file_exists($aStrokeFileName) ) { 7401 // Delete the old file 7402 if( !@unlink($aStrokeFileName) ) 7403 JpGraphError::RaiseL(25111,$aStrokeFileName);//(" Can't delete cached image $aStrokeFileName. Permission problem?"); 7404 } 7405 $aImage->Stream($aStrokeFileName); 7406 return; 7407 } 7408 7409 if( $aCacheFileName != "" && USE_CACHE) { 7410 7411 $aCacheFileName = $this->cache_dir . $aCacheFileName; 7412 if( file_exists($aCacheFileName) ) { 7413 if( !$aInline ) { 7414 // If we are generating image off-line (just writing to the cache) 7415 // and the file exists and is still valid (no timeout) 7416 // then do nothing, just return. 7417 $diff=time()-filemtime($aCacheFileName); 7418 if( $diff < 0 ) 7419 JpGraphError::RaiseL(25112,$aCacheFileName);//(" Cached imagefile ($aCacheFileName) has file date in the future!!"); 7420 if( $this->timeout>0 && ($diff <= $this->timeout*60) ) 7421 return; 7422 } 7423 if( !@unlink($aCacheFileName) ) 7424 JpGraphError::RaiseL(25113,$aStrokeFileName);//(" Can't delete cached image $aStrokeFileName. Permission problem?"); 7425 $aImage->Stream($aCacheFileName); 7426 } 7427 else { 7428 $this->MakeDirs(dirname($aCacheFileName)); 7429 if( !is_writeable(dirname($aCacheFileName)) ) { 7430 JpGraphError::RaiseL(25114,$aCacheFileName);//('PHP has not enough permissions to write to the cache file '.$aCacheFileName.'. Please make sure that the user running PHP has write permission for this file if you wan to use the cache system with JpGraph.'); 7431 } 7432 $aImage->Stream($aCacheFileName); 7433 } 7434 7435 $res=true; 7436 // Set group to specified 7437 if( CACHE_FILE_GROUP != "" ) 7438 $res = @chgrp($aCacheFileName,CACHE_FILE_GROUP); 7439 if( CACHE_FILE_MOD != "" ) 7440 $res = @chmod($aCacheFileName,CACHE_FILE_MOD); 7441 if( !$res ) 7442 JpGraphError::RaiseL(25115,$aStrokeFileName);//(" Can't set permission for cached image $aStrokeFileName. Permission problem?"); 7443 7444 $aImage->Destroy(); 7445 if( $aInline ) { 7446 if ($fh = @fopen($aCacheFileName, "rb") ) { 7447 $this->img->Headers(); 7448 fpassthru($fh); 7449 return; 7450 } 7451 else 7452 JpGraphError::RaiseL(25116,$aFile);//(" Cant open file from cache [$aFile]"); 7453 } 7454 } 7455 elseif( $aInline ) { 7456 $this->img->Headers(); 7457 $aImage->Stream(); 7458 return; 7459 } 7460 } 7461 7462 // Check if a given image is in cache and in that case 7463 // pass it directly on to web browser. Return false if the 7464 // image file doesn't exist or exists but is to old 7465 function GetAndStream($aCacheFileName) { 7466 $aCacheFileName = $this->cache_dir.$aCacheFileName; 7467 if ( USE_CACHE && file_exists($aCacheFileName) && $this->timeout>=0 ) { 7468 $diff=time()-filemtime($aCacheFileName); 7469 if( $this->timeout>0 && ($diff > $this->timeout*60) ) { 7470 return false; 7471 } 7472 else { 7473 if ($fh = @fopen($aCacheFileName, "rb")) { 7474 $this->img->Headers(); 7475 fpassthru($fh); 7476 return true; 7477 } 7478 else 7479 JpGraphError::RaiseL(25117,$aCacheFileName);//(" Can't open cached image \"$aCacheFileName\" for reading."); 7480 } 7481 } 7482 return false; 7483 } 7484 7485 //--------------- 7486 // PRIVATE METHODS 7487 // Create all necessary directories in a path 7488 function MakeDirs($aFile) { 7489 $dirs = array(); 7490 while ( !(file_exists($aFile)) ) { 7491 $dirs[] = $aFile; 7492 $aFile = dirname($aFile); 7493 } 7494 for ($i = sizeof($dirs)-1; $i>=0; $i--) { 7495 if(! @mkdir($dirs[$i],0777) ) 7496 JpGraphError::RaiseL(25118,$aFile);//(" Can't create directory $aFile. Make sure PHP has write permission to this directory."); 7497 // We also specify mode here after we have changed group. 7498 // This is necessary if Apache user doesn't belong the 7499 // default group and hence can't specify group permission 7500 // in the previous mkdir() call 7501 if( CACHE_FILE_GROUP != "" ) { 7502 $res=true; 7503 $res =@chgrp($dirs[$i],CACHE_FILE_GROUP); 7504 $res = @chmod($dirs[$i],0777); 7505 if( !$res ) 7506 JpGraphError::RaiseL(25119,$aFile);//(" Can't set permissions for $aFile. Permission problems?"); 7507 } 7508 } 7509 return true; 7510 } 7511} // CLASS Cache 7512 7513//=================================================== 7514// CLASS Legend 7515// Description: Responsible for drawing the box containing 7516// all the legend text for the graph 7517//=================================================== 7518DEFINE('_DEFAULT_LPM_SIZE',8); 7519class Legend { 7520 public $txtcol=array(); 7521 private $color=array(0,0,0); // Default fram color 7522 private $fill_color=array(235,235,235); // Default fill color 7523 private $shadow=true; // Shadow around legend "box" 7524 private $shadow_color='darkgray@0.5'; 7525 private $mark_abs_hsize=_DEFAULT_LPM_SIZE,$mark_abs_vsize=_DEFAULT_LPM_SIZE; 7526 private $xmargin=5,$ymargin=3,$shadow_width=2; 7527 private $xlmargin=2, $ylmargin=''; 7528 private $xpos=0.05, $ypos=0.15, $xabspos=-1, $yabspos=-1; 7529 private $halign="right", $valign="top"; 7530 private $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12; 7531 private $font_color='black'; 7532 private $hide=false,$layout_n=1; 7533 private $weight=1,$frameweight=1; 7534 private $csimareas=''; 7535 private $reverse = false ; 7536//--------------- 7537// CONSTRUCTOR 7538 function Legend() { 7539 // Empty 7540 } 7541//--------------- 7542// PUBLIC METHODS 7543 function Hide($aHide=true) { 7544 $this->hide=$aHide; 7545 } 7546 7547 function SetHColMargin($aXMarg) { 7548 $this->xmargin = $aXMarg; 7549 } 7550 7551 function SetVColMargin($aSpacing) { 7552 $this->ymargin = $aSpacing ; 7553 } 7554 7555 function SetLeftMargin($aXMarg) { 7556 $this->xlmargin = $aXMarg; 7557 } 7558 7559 7560 // Synonym 7561 function SetLineSpacing($aSpacing) { 7562 $this->ymargin = $aSpacing ; 7563 } 7564 7565 function SetShadow($aShow='gray',$aWidth=2) { 7566 if( is_string($aShow) ) { 7567 $this->shadow_color = $aShow; 7568 $this->shadow=true; 7569 } 7570 else 7571 $this->shadow=$aShow; 7572 $this->shadow_width=$aWidth; 7573 } 7574 7575 function SetMarkAbsSize($aSize) { 7576 $this->mark_abs_vsize = $aSize ; 7577 $this->mark_abs_hsize = $aSize ; 7578 } 7579 7580 function SetMarkAbsVSize($aSize) { 7581 $this->mark_abs_vsize = $aSize ; 7582 } 7583 7584 function SetMarkAbsHSize($aSize) { 7585 $this->mark_abs_hsize = $aSize ; 7586 } 7587 7588 function SetLineWeight($aWeight) { 7589 $this->weight = $aWeight; 7590 } 7591 7592 function SetFrameWeight($aWeight) { 7593 $this->frameweight = $aWeight; 7594 } 7595 7596 function SetLayout($aDirection=LEGEND_VERT) { 7597 $this->layout_n = $aDirection==LEGEND_VERT ? 1 : 99 ; 7598 } 7599 7600 function SetColumns($aCols) { 7601 $this->layout_n = $aCols ; 7602 } 7603 7604 function SetReverse($f=true) { 7605 $this->reverse = $f ; 7606 } 7607 7608 // Set color on frame around box 7609 function SetColor($aFontColor,$aColor='black') { 7610 $this->font_color=$aFontColor; 7611 $this->color=$aColor; 7612 } 7613 7614 function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) { 7615 $this->font_family = $aFamily; 7616 $this->font_style = $aStyle; 7617 $this->font_size = $aSize; 7618 } 7619 7620 function SetPos($aX,$aY,$aHAlign="right",$aVAlign="top") { 7621 $this->Pos($aX,$aY,$aHAlign,$aVAlign); 7622 } 7623 7624 function SetAbsPos($aX,$aY,$aHAlign="right",$aVAlign="top") { 7625 $this->xabspos=$aX; 7626 $this->yabspos=$aY; 7627 $this->halign=$aHAlign; 7628 $this->valign=$aVAlign; 7629 } 7630 7631 7632 function Pos($aX,$aY,$aHAlign="right",$aVAlign="top") { 7633 if( !($aX<1 && $aY<1) ) 7634 JpGraphError::RaiseL(25120);//(" Position for legend must be given as percentage in range 0-1"); 7635 $this->xpos=$aX; 7636 $this->ypos=$aY; 7637 $this->halign=$aHAlign; 7638 $this->valign=$aVAlign; 7639 } 7640 7641 function SetFillColor($aColor) { 7642 $this->fill_color=$aColor; 7643 } 7644 7645 function Add($aTxt,$aColor,$aPlotmark='',$aLinestyle=0,$csimtarget='',$csimalt='') { 7646 $this->txtcol[]=array($aTxt,$aColor,$aPlotmark,$aLinestyle,$csimtarget,$csimalt); 7647 } 7648 7649 function GetCSIMAreas() { 7650 return $this->csimareas; 7651 } 7652 7653 function Stroke(&$aImg) { 7654 // Constant 7655 $fillBoxFrameWeight=1; 7656 7657 if( $this->hide ) return; 7658 7659 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size); 7660 7661 if( $this->reverse ) { 7662 $this->txtcol = array_reverse($this->txtcol); 7663 } 7664 7665 $n=count($this->txtcol); 7666 if( $n == 0 ) return; 7667 7668 // Find out the max width and height of each column to be able 7669 // to size the legend box. 7670 $numcolumns = ($n > $this->layout_n ? $this->layout_n : $n); 7671 for( $i=0; $i < $numcolumns; ++$i ) { 7672 $colwidth[$i] = $aImg->GetTextWidth($this->txtcol[$i][0]) + 7673 2*$this->xmargin + 2*$this->mark_abs_hsize; 7674 $colheight[$i] = 0; 7675 } 7676 7677 // Find our maximum height in each row 7678 $rows = 0 ; $rowheight[0] = 0; 7679 for( $i=0; $i < $n; ++$i ) { 7680 $h = max($this->mark_abs_vsize,$aImg->GetTextHeight($this->txtcol[$i][0]))+$this->ymargin; 7681 if( $i % $numcolumns == 0 ) { 7682 $rows++; 7683 $rowheight[$rows-1] = 0; 7684 } 7685 $rowheight[$rows-1] = max($rowheight[$rows-1],$h); 7686 } 7687 7688 $abs_height = 0; 7689 for( $i=0; $i < $rows; ++$i ) { 7690 $abs_height += $rowheight[$i] ; 7691 } 7692 7693 // Make sure that the height is at least as high as mark size + ymargin 7694 $abs_height = max($abs_height,$this->mark_abs_vsize); 7695 7696 // We add 3 extra pixels height to compensate for the difficult in 7697 // calculating font height 7698 $abs_height += $this->ymargin+3; 7699 7700 // Find out the maximum width in each column 7701 for( $i=$numcolumns; $i < $n; ++$i ) { 7702 $colwidth[$i % $numcolumns] = max( 7703 $aImg->GetTextWidth($this->txtcol[$i][0])+2*$this->xmargin+2*$this->mark_abs_hsize,$colwidth[$i % $numcolumns]); 7704 } 7705 7706 // Get the total width 7707 $mtw = 0; 7708 for( $i=0; $i < $numcolumns; ++$i ) { 7709 $mtw += $colwidth[$i] ; 7710 } 7711 7712 // Find out maximum width we need for legend box 7713 $abs_width = $mtw+$this->xlmargin; 7714 7715 if( $this->xabspos === -1 && $this->yabspos === -1 ) { 7716 $this->xabspos = $this->xpos*$aImg->width ; 7717 $this->yabspos = $this->ypos*$aImg->height ; 7718 } 7719 7720 // Positioning of the legend box 7721 if( $this->halign == 'left' ) 7722 $xp = $this->xabspos; 7723 elseif( $this->halign == 'center' ) 7724 $xp = $this->xabspos - $abs_width/2; 7725 else 7726 $xp = $aImg->width - $this->xabspos - $abs_width; 7727 7728 $yp=$this->yabspos; 7729 if( $this->valign == 'center' ) 7730 $yp-=$abs_height/2; 7731 elseif( $this->valign == 'bottom' ) 7732 $yp-=$abs_height; 7733 7734 // Stroke legend box 7735 $aImg->SetColor($this->color); 7736 $aImg->SetLineWeight($this->frameweight); 7737 $aImg->SetLineStyle('solid'); 7738 7739 if( $this->shadow ) 7740 $aImg->ShadowRectangle($xp,$yp,$xp+$abs_width+$this->shadow_width, 7741 $yp+$abs_height+$this->shadow_width, 7742 $this->fill_color,$this->shadow_width,$this->shadow_color); 7743 else { 7744 $aImg->SetColor($this->fill_color); 7745 $aImg->FilledRectangle($xp,$yp,$xp+$abs_width,$yp+$abs_height); 7746 $aImg->SetColor($this->color); 7747 $aImg->Rectangle($xp,$yp,$xp+$abs_width,$yp+$abs_height); 7748 } 7749 7750 // x1,y1 is the position for the legend mark 7751 $x1=$xp+$this->mark_abs_hsize+$this->xlmargin; 7752 $y1=$yp + $this->ymargin; 7753 7754 $f2 = round($aImg->GetTextHeight('X')/2); 7755 7756 $grad = new Gradient($aImg); 7757 $patternFactory = null; 7758 7759 // Now stroke each legend in turn 7760 // Each plot has added the following information to the legend 7761 // p[0] = Legend text 7762 // p[1] = Color, 7763 // p[2] = For markers a reference to the PlotMark object 7764 // p[3] = For lines the line style, for gradient the negative gradient style 7765 // p[4] = CSIM target 7766 // p[5] = CSIM Alt text 7767 $i = 1 ; $row = 0; 7768 foreach($this->txtcol as $p) { 7769 7770 // STROKE DEBUG BOX 7771 if( _JPG_DEBUG ) { 7772 $aImg->SetLineWeight(1); 7773 $aImg->SetColor('red'); 7774 $aImg->SetLineStyle('solid'); 7775 $aImg->Rectangle($xp,$y1,$xp+$abs_width,$y1+$rowheight[$row]); 7776 } 7777 7778 $aImg->SetLineWeight($this->weight); 7779 $x1 = round($x1); $y1=round($y1); 7780 if ( !empty($p[2]) && $p[2]->GetType() > -1 ) { 7781 // Make a plot mark legend 7782 $aImg->SetColor($p[1]); 7783 if( is_string($p[3]) || $p[3]>0 ) { 7784 $aImg->SetLineStyle($p[3]); 7785 $aImg->StyleLine($x1-$this->mark_abs_hsize,$y1+$f2,$x1+$this->mark_abs_hsize,$y1+$f2); 7786 } 7787 // Stroke a mark with the standard size 7788 // (As long as it is not an image mark ) 7789 if( $p[2]->GetType() != MARK_IMG ) { 7790 $p[2]->iFormatCallback = ''; 7791 7792 // Since size for circles is specified as the radius 7793 // this means that we must half the size to make the total 7794 // width behave as the other marks 7795 if( $p[2]->GetType() == MARK_FILLEDCIRCLE || $p[2]->GetType() == MARK_CIRCLE ) { 7796 $p[2]->SetSize(min($this->mark_abs_vsize,$this->mark_abs_hsize)/2); 7797 $p[2]->Stroke($aImg,$x1,$y1+$f2); 7798 } 7799 else { 7800 $p[2]->SetSize(min($this->mark_abs_vsize,$this->mark_abs_hsize)); 7801 $p[2]->Stroke($aImg,$x1,$y1+$f2); 7802 } 7803 } 7804 } 7805 elseif ( !empty($p[2]) && (is_string($p[3]) || $p[3]>0 ) ) { 7806 // Draw a styled line 7807 $aImg->SetColor($p[1]); 7808 $aImg->SetLineStyle($p[3]); 7809 $aImg->StyleLine($x1-1,$y1+$f2,$x1+$this->mark_abs_hsize,$y1+$f2); 7810 $aImg->StyleLine($x1-1,$y1+$f2+1,$x1+$this->mark_abs_hsize,$y1+$f2+1); 7811 } 7812 else { 7813 // Draw a colored box 7814 $color = $p[1] ; 7815 // We make boxes slightly larger to better show 7816 $boxsize = min($this->mark_abs_vsize,$this->mark_abs_hsize) + 2 ; 7817 $ym = round($y1 + $f2 - $boxsize/2); 7818 // We either need to plot a gradient or a 7819 // pattern. To differentiate we use a kludge. 7820 // Patterns have a p[3] value of < -100 7821 if( $p[3] < -100 ) { 7822 // p[1][0] == iPattern, p[1][1] == iPatternColor, p[1][2] == iPatternDensity 7823 if( $patternFactory == null ) { 7824 $patternFactory = new RectPatternFactory(); 7825 } 7826 $prect = $patternFactory->Create($p[1][0],$p[1][1],1); 7827 $prect->SetBackground($p[1][3]); 7828 $prect->SetDensity($p[1][2]+1); 7829 $prect->SetPos(new Rectangle($x1,$ym,$boxsize,$boxsize)); 7830 $prect->Stroke($aImg); 7831 $prect=null; 7832 } 7833 else { 7834 if( is_array($color) && count($color)==2 ) { 7835 // The client want a gradient color 7836 $grad->FilledRectangle($x1,$ym, 7837 $x1+$boxsize,$ym+$boxsize, 7838 $color[0],$color[1],-$p[3]); 7839 } 7840 else { 7841 $aImg->SetColor($p[1]); 7842 $aImg->FilledRectangle($x1,$ym,$x1+$boxsize,$ym+$boxsize); 7843 } 7844 $aImg->SetColor($this->color); 7845 $aImg->SetLineWeight($fillBoxFrameWeight); 7846 $aImg->Rectangle($x1,$ym,$x1+$boxsize,$ym+$boxsize); 7847 } 7848 } 7849 $aImg->SetColor($this->font_color); 7850 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size); 7851 $aImg->SetTextAlign("left","top"); 7852 $aImg->StrokeText(round($x1+$this->mark_abs_hsize+$this->xmargin),$y1,$p[0]); 7853 7854 // Add CSIM for Legend if defined 7855 if( !empty($p[4]) ) { 7856 $xe = $x1 + $this->xmargin+$this->mark_abs_hsize+$aImg->GetTextWidth($p[0]); 7857 $ye = $y1 + max($this->mark_abs_vsize,$aImg->GetTextHeight($p[0])); 7858 $coords = "$x1,$y1,$xe,$y1,$xe,$ye,$x1,$ye"; 7859 if( ! empty($p[4]) ) { 7860 $this->csimareas .= "<area shape=\"poly\" coords=\"$coords\" href=\"".$p[4]."\""; 7861 if( !empty($p[5]) ) { 7862 $tmp=sprintf($p[5],$p[0]); 7863 $this->csimareas .= " title=\"$tmp\""; 7864 } 7865 $this->csimareas .= " alt=\"\" />\n"; 7866 } 7867 } 7868 if( $i >= $this->layout_n ) { 7869 $x1 = $xp+$this->mark_abs_hsize+$this->xlmargin; 7870 $y1 += $rowheight[$row++]; 7871 $i = 1; 7872 } 7873 else { 7874 $x1 += $colwidth[($i-1) % $numcolumns] ; 7875 ++$i; 7876 } 7877 } 7878 } 7879} // Class 7880 7881 7882//=================================================== 7883// CLASS DisplayValue 7884// Description: Used to print data values at data points 7885//=================================================== 7886class DisplayValue { 7887 public $margin=5; 7888 public $show=false; 7889 public $valign="",$halign="center"; 7890 public $format="%.1f",$negformat=""; 7891 private $ff=FF_FONT1,$fs=FS_NORMAL,$fsize=10; 7892 private $iFormCallback=''; 7893 private $angle=0; 7894 private $color="navy",$negcolor=""; 7895 private $iHideZero=false; 7896 7897 function Show($aFlag=true) { 7898 $this->show=$aFlag; 7899 } 7900 7901 function SetColor($aColor,$aNegcolor="") { 7902 $this->color = $aColor; 7903 $this->negcolor = $aNegcolor; 7904 } 7905 7906 function SetFont($aFontFamily,$aFontStyle=FS_NORMAL,$aFontSize=10) { 7907 $this->ff=$aFontFamily; 7908 $this->fs=$aFontStyle; 7909 $this->fsize=$aFontSize; 7910 } 7911 7912 function ApplyFont($aImg) { 7913 $aImg->SetFont($this->ff,$this->fs,$this->fsize); 7914 } 7915 7916 function SetMargin($aMargin) { 7917 $this->margin = $aMargin; 7918 } 7919 7920 function SetAngle($aAngle) { 7921 $this->angle = $aAngle; 7922 } 7923 7924 function SetAlign($aHAlign,$aVAlign='') { 7925 $this->halign = $aHAlign; 7926 $this->valign = $aVAlign; 7927 } 7928 7929 function SetFormat($aFormat,$aNegFormat="") { 7930 $this->format= $aFormat; 7931 $this->negformat= $aNegFormat; 7932 } 7933 7934 function SetFormatCallback($aFunc) { 7935 $this->iFormCallback = $aFunc; 7936 } 7937 7938 function HideZero($aFlag=true) { 7939 $this->iHideZero=$aFlag; 7940 } 7941 7942 function Stroke($img,$aVal,$x,$y) { 7943 7944 if( $this->show ) 7945 { 7946 if( $this->negformat=="" ) $this->negformat=$this->format; 7947 if( $this->negcolor=="" ) $this->negcolor=$this->color; 7948 7949 if( $aVal===NULL || (is_string($aVal) && ($aVal=="" || $aVal=="-" || $aVal=="x" ) ) ) 7950 return; 7951 7952 if( is_numeric($aVal) && $aVal==0 && $this->iHideZero ) { 7953 return; 7954 } 7955 7956 // Since the value is used in different cirumstances we need to check what 7957 // kind of formatting we shall use. For example, to display values in a line 7958 // graph we simply display the formatted value, but in the case where the user 7959 // has already specified a text string we don't fo anything. 7960 if( $this->iFormCallback != '' ) { 7961 $f = $this->iFormCallback; 7962 $sval = call_user_func($f,$aVal); 7963 } 7964 elseif( is_numeric($aVal) ) { 7965 if( $aVal >= 0 ) 7966 $sval=sprintf($this->format,$aVal); 7967 else 7968 $sval=sprintf($this->negformat,$aVal); 7969 } 7970 else 7971 $sval=$aVal; 7972 7973 $y = $y-sign($aVal)*$this->margin; 7974 7975 $txt = new Text($sval,$x,$y); 7976 $txt->SetFont($this->ff,$this->fs,$this->fsize); 7977 if( $this->valign == "" ) { 7978 if( $aVal >= 0 ) 7979 $valign = "bottom"; 7980 else 7981 $valign = "top"; 7982 } 7983 else 7984 $valign = $this->valign; 7985 $txt->Align($this->halign,$valign); 7986 7987 $txt->SetOrientation($this->angle); 7988 if( $aVal > 0 ) 7989 $txt->SetColor($this->color); 7990 else 7991 $txt->SetColor($this->negcolor); 7992 $txt->Stroke($img); 7993 } 7994 } 7995} 7996 7997//=================================================== 7998// CLASS Plot 7999// Description: Abstract base class for all concrete plot classes 8000//=================================================== 8001class Plot { 8002 public $numpoints=0; 8003 public $value; 8004 public $legend=''; 8005 public $coords=array(); 8006 public $color="black"; 8007 public $hidelegend=false; 8008 public $line_weight=1; 8009 public $csimtargets=array(); // Array of targets for CSIM 8010 public $csimareas=""; // Resultant CSIM area tags 8011 public $csimalts=null; // ALT:s for corresponding target 8012 public $legendcsimtarget=''; 8013 public $legendcsimalt=''; 8014 protected $weight=1; 8015 protected $center=false; 8016//--------------- 8017// CONSTRUCTOR 8018 function Plot($aDatay,$aDatax=false) { 8019 $this->numpoints = count($aDatay); 8020 if( $this->numpoints==0 ) 8021 JpGraphError::RaiseL(25121);//("Empty input data array specified for plot. Must have at least one data point."); 8022 $this->coords[0]=$aDatay; 8023 if( is_array($aDatax) ) 8024 $this->coords[1]=$aDatax; 8025 $this->value = new DisplayValue(); 8026 } 8027 8028//--------------- 8029// PUBLIC METHODS 8030 8031 // Stroke the plot 8032 // "virtual" function which must be implemented by 8033 // the subclasses 8034 function Stroke($aImg,$aXScale,$aYScale) { 8035 JpGraphError::RaiseL(25122);//("JpGraph: Stroke() must be implemented by concrete subclass to class Plot"); 8036 } 8037 8038 function HideLegend($f=true) { 8039 $this->hidelegend = $f; 8040 } 8041 8042 function DoLegend($graph) { 8043 if( !$this->hidelegend ) 8044 $this->Legend($graph); 8045 } 8046 8047 function StrokeDataValue($img,$aVal,$x,$y) { 8048 $this->value->Stroke($img,$aVal,$x,$y); 8049 } 8050 8051 // Set href targets for CSIM 8052 function SetCSIMTargets($aTargets,$aAlts=null) { 8053 $this->csimtargets=$aTargets; 8054 $this->csimalts=$aAlts; 8055 } 8056 8057 // Get all created areas 8058 function GetCSIMareas() { 8059 return $this->csimareas; 8060 } 8061 8062 // "Virtual" function which gets called before any scale 8063 // or axis are stroked used to do any plot specific adjustment 8064 function PreStrokeAdjust($aGraph) { 8065 if( substr($aGraph->axtype,0,4) == "text" && (isset($this->coords[1])) ) 8066 JpGraphError::RaiseL(25123);//("JpGraph: You can't use a text X-scale with specified X-coords. Use a \"int\" or \"lin\" scale instead."); 8067 return true; 8068 } 8069 8070 // Get minimum values in plot 8071 function Min() { 8072 if( isset($this->coords[1]) ) 8073 $x=$this->coords[1]; 8074 else 8075 $x=""; 8076 if( $x != "" && count($x) > 0 ) 8077 $xm=min($x); 8078 else 8079 $xm=0; 8080 $y=$this->coords[0]; 8081 $cnt = count($y); 8082 if( $cnt > 0 ) { 8083 /* 8084 if( ! isset($y[0]) ) { 8085 JpGraphError('The input data array must have consecutive values from position 0 and forward. The given y-array starts with empty values (NULL)'); 8086 } 8087 $ym = $y[0]; 8088 */ 8089 $i=0; 8090 while( $i<$cnt && !is_numeric($ym=$y[$i]) ) 8091 $i++; 8092 while( $i < $cnt) { 8093 if( is_numeric($y[$i]) ) 8094 $ym=min($ym,$y[$i]); 8095 ++$i; 8096 } 8097 } 8098 else 8099 $ym=""; 8100 return array($xm,$ym); 8101 } 8102 8103 // Get maximum value in plot 8104 function Max() { 8105 if( isset($this->coords[1]) ) 8106 $x=$this->coords[1]; 8107 else 8108 $x=""; 8109 8110 if( $x!="" && count($x) > 0 ) 8111 $xm=max($x); 8112 else { 8113 $xm = $this->numpoints-1; 8114 } 8115 $y=$this->coords[0]; 8116 if( count($y) > 0 ) { 8117 /* 8118 if( !isset($y[0]) ) { 8119 JpGraphError::Raise('The input data array must have consecutive values from position 0 and forward. The given y-array starts with empty values (NULL)'); 8120// $y[0] = 0; 8121// Change in 1.5.1 Don't treat this as an error any more. Just silently convert to 0 8122// Change in 1.17 Treat his as an error again !! This is the right way to do !! 8123 } 8124 */ 8125 $cnt = count($y); 8126 $i=0; 8127 while( $i<$cnt && !is_numeric($ym=$y[$i]) ) 8128 $i++; 8129 while( $i < $cnt ) { 8130 if( is_numeric($y[$i]) ) 8131 $ym=max($ym,$y[$i]); 8132 ++$i; 8133 } 8134 } 8135 else 8136 $ym=""; 8137 return array($xm,$ym); 8138 } 8139 8140 function SetColor($aColor) { 8141 $this->color=$aColor; 8142 } 8143 8144 function SetLegend($aLegend,$aCSIM="",$aCSIMAlt="") { 8145 $this->legend = $aLegend; 8146 $this->legendcsimtarget = $aCSIM; 8147 $this->legendcsimalt = $aCSIMAlt; 8148 } 8149 8150 function SetWeight($aWeight) { 8151 $this->weight=$aWeight; 8152 } 8153 8154 function SetLineWeight($aWeight=1) { 8155 $this->line_weight=$aWeight; 8156 } 8157 8158 function SetCenter($aCenter=true) { 8159 $this->center = $aCenter; 8160 } 8161 8162 // This method gets called by Graph class to plot anything that should go 8163 // into the margin after the margin color has been set. 8164 function StrokeMargin($aImg) { 8165 return true; 8166 } 8167 8168 // Framework function the chance for each plot class to set a legend 8169 function Legend($aGraph) { 8170 if( $this->legend != "" ) 8171 $aGraph->legend->Add($this->legend,$this->color,"",0,$this->legendcsimtarget,$this->legendcsimalt); 8172 } 8173 8174} // Class 8175 8176 8177//=================================================== 8178// CLASS PlotLine 8179// Description: 8180// Data container class to hold properties for a static 8181// line that is drawn directly in the plot area. 8182// Usefull to add static borders inside a plot to show 8183// for example set-values 8184//=================================================== 8185class PlotLine { 8186 public $scaleposition, $direction=-1; 8187 protected $weight=1; 8188 protected $color="black"; 8189 8190//--------------- 8191// CONSTRUCTOR 8192 function PlotLine($aDir=HORIZONTAL,$aPos=0,$aColor="black",$aWeight=1) { 8193 $this->direction = $aDir; 8194 $this->color=$aColor; 8195 $this->weight=$aWeight; 8196 $this->scaleposition=$aPos; 8197 } 8198 8199//--------------- 8200// PUBLIC METHODS 8201 function SetPosition($aScalePosition) { 8202 $this->scaleposition=$aScalePosition; 8203 } 8204 8205 function SetDirection($aDir) { 8206 $this->direction = $aDir; 8207 } 8208 8209 function SetColor($aColor) { 8210 $this->color=$aColor; 8211 } 8212 8213 function SetWeight($aWeight) { 8214 $this->weight=$aWeight; 8215 } 8216 8217 function PreStrokeAdjust($aGraph) { 8218 // Nothing to do 8219 } 8220 8221 function Stroke($aImg,$aXScale,$aYScale) { 8222 $aImg->SetColor($this->color); 8223 $aImg->SetLineWeight($this->weight); 8224 if( $this->direction == VERTICAL ) { 8225 $ymin_abs=$aYScale->Translate($aYScale->GetMinVal()); 8226 $ymax_abs=$aYScale->Translate($aYScale->GetMaxVal()); 8227 $xpos_abs=$aXScale->Translate($this->scaleposition); 8228 $aImg->Line($xpos_abs, $ymin_abs, $xpos_abs, $ymax_abs); 8229 } 8230 elseif( $this->direction == HORIZONTAL ) { 8231 $xmin_abs=$aXScale->Translate($aXScale->GetMinVal()); 8232 $xmax_abs=$aXScale->Translate($aXScale->GetMaxVal()); 8233 $ypos_abs=$aYScale->Translate($this->scaleposition); 8234 $aImg->Line($xmin_abs, $ypos_abs, $xmax_abs, $ypos_abs); 8235 } 8236 else 8237 JpGraphError::RaiseL(25125);//(" Illegal direction for static line"); 8238 } 8239} 8240 8241// <EOF> 8242?> 8243