undef HALF_FREQUENCY_SENDING_TO_CLIENT
[ryzomcore.git] / web / public_php / admin / jpgraph / jpgraph.php
blob0d0dfbfbf9755b89f7a894e9e70b7a966a1cac7f
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/07/07 13:37:14 powles Exp $
8 //
9 // Copyright (c) Aditus Consulting. All rights reserved.
10 //========================================================================
12 require_once('jpg-config.inc');
14 // Version info
15 DEFINE('JPG_VERSION','1.20.3');
17 // For internal use only
18 DEFINE("_JPG_DEBUG",false);
19 DEFINE("_FORCE_IMGTOFILE",false);
20 DEFINE("_FORCE_IMGDIR",'/tmp/jpgimg/');
23 //------------------------------------------------------------------------
24 // Automatic settings of path for cache and font directory
25 // if they have not been previously specified
26 //------------------------------------------------------------------------
27 if(USE_CACHE) {
28 if (!defined('CACHE_DIR')) {
29 if ( strstr( PHP_OS, 'WIN') ) {
30 if( empty($_SERVER['TEMP']) ) {
31 $t = new ErrMsgText();
32 $msg = $t->Get(11,$file,$lineno);
33 die($msg);
35 else {
36 DEFINE('CACHE_DIR', $_SERVER['TEMP'] . '/');
38 } else {
39 DEFINE('CACHE_DIR','/tmp/jpgraph_cache/');
43 elseif( !defined('CACHE_DIR') ) {
44 DEFINE('CACHE_DIR', '');
47 if (!defined('TTF_DIR')) {
48 if (strstr( PHP_OS, 'WIN') ) {
49 $sroot = getenv('SystemRoot');
50 if( empty($sroot) ) {
51 $t = new ErrMsgText();
52 $msg = $t->Get(12,$file,$lineno);
53 die($msg);
55 else {
56 DEFINE('TTF_DIR', $sroot.'/fonts/');
58 } else {
59 DEFINE('TTF_DIR','/usr/X11R6/lib/X11/fonts/truetype/');
63 //------------------------------------------------------------------
64 // Constants which are used as parameters for the method calls
65 //------------------------------------------------------------------
67 // TTF Font families
68 DEFINE("FF_COURIER",10);
69 DEFINE("FF_VERDANA",11);
70 DEFINE("FF_TIMES",12);
71 DEFINE("FF_COMIC",14);
72 DEFINE("FF_ARIAL",15);
73 DEFINE("FF_GEORGIA",16);
74 DEFINE("FF_TREBUCHE",17);
76 // Gnome Vera font
77 // Available from http://www.gnome.org/fonts/
78 DEFINE("FF_VERA",19);
79 DEFINE("FF_VERAMONO",20);
80 DEFINE("FF_VERASERIF",21);
82 // Chinese font
83 DEFINE("FF_SIMSUN",30);
84 DEFINE("FF_CHINESE",31);
85 DEFINE("FF_BIG5",31);
87 // Japanese font
88 DEFINE("FF_MINCHO",40);
89 DEFINE("FF_PMINCHO",41);
90 DEFINE("FF_GOTHIC",42);
91 DEFINE("FF_PGOTHIC",43);
93 // Limits for TTF fonts
94 DEFINE('_FF_FIRST',10);
95 DEFINE('_FF_LAST',43);
97 // Older deprecated fonts
98 DEFINE("FF_BOOK",91); // Deprecated fonts from 1.9
99 DEFINE("FF_HANDWRT",92); // Deprecated fonts from 1.9
101 // TTF Font styles
102 DEFINE("FS_NORMAL",9001);
103 DEFINE("FS_BOLD",9002);
104 DEFINE("FS_ITALIC",9003);
105 DEFINE("FS_BOLDIT",9004);
106 DEFINE("FS_BOLDITALIC",9004);
108 //Definitions for internal font, new style
109 DEFINE("FF_FONT0",1);
110 DEFINE("FF_FONT1",2);
111 DEFINE("FF_FONT2",4);
113 // Tick density
114 DEFINE("TICKD_DENSE",1);
115 DEFINE("TICKD_NORMAL",2);
116 DEFINE("TICKD_SPARSE",3);
117 DEFINE("TICKD_VERYSPARSE",4);
119 // Side for ticks and labels.
120 DEFINE("SIDE_LEFT",-1);
121 DEFINE("SIDE_RIGHT",1);
122 DEFINE("SIDE_DOWN",-1);
123 DEFINE("SIDE_BOTTOM",-1);
124 DEFINE("SIDE_UP",1);
125 DEFINE("SIDE_TOP",1);
127 // Legend type stacked vertical or horizontal
128 DEFINE("LEGEND_VERT",0);
129 DEFINE("LEGEND_HOR",1);
131 // Mark types for plot marks
132 DEFINE("MARK_SQUARE",1);
133 DEFINE("MARK_UTRIANGLE",2);
134 DEFINE("MARK_DTRIANGLE",3);
135 DEFINE("MARK_DIAMOND",4);
136 DEFINE("MARK_CIRCLE",5);
137 DEFINE("MARK_FILLEDCIRCLE",6);
138 DEFINE("MARK_CROSS",7);
139 DEFINE("MARK_STAR",8);
140 DEFINE("MARK_X",9);
141 DEFINE("MARK_LEFTTRIANGLE",10);
142 DEFINE("MARK_RIGHTTRIANGLE",11);
143 DEFINE("MARK_FLASH",12);
144 DEFINE("MARK_IMG",13);
145 DEFINE("MARK_FLAG1",14);
146 DEFINE("MARK_FLAG2",15);
147 DEFINE("MARK_FLAG3",16);
148 DEFINE("MARK_FLAG4",17);
150 // Builtin images
151 DEFINE("MARK_IMG_PUSHPIN",50);
152 DEFINE("MARK_IMG_SPUSHPIN",50);
153 DEFINE("MARK_IMG_LPUSHPIN",51);
154 DEFINE("MARK_IMG_DIAMOND",52);
155 DEFINE("MARK_IMG_SQUARE",53);
156 DEFINE("MARK_IMG_STAR",54);
157 DEFINE("MARK_IMG_BALL",55);
158 DEFINE("MARK_IMG_SBALL",55);
159 DEFINE("MARK_IMG_MBALL",56);
160 DEFINE("MARK_IMG_LBALL",57);
161 DEFINE("MARK_IMG_BEVEL",58);
163 // Inline defines
164 DEFINE("INLINE_YES",1);
165 DEFINE("INLINE_NO",0);
167 // Format for background images
168 DEFINE("BGIMG_FILLPLOT",1);
169 DEFINE("BGIMG_FILLFRAME",2);
170 DEFINE("BGIMG_COPY",3);
171 DEFINE("BGIMG_CENTER",4);
173 // Depth of objects
174 DEFINE("DEPTH_BACK",0);
175 DEFINE("DEPTH_FRONT",1);
177 // Direction
178 DEFINE("VERTICAL",1);
179 DEFINE("HORIZONTAL",0);
182 // Axis styles for scientific style axis
183 DEFINE('AXSTYLE_SIMPLE',1);
184 DEFINE('AXSTYLE_BOXIN',2);
185 DEFINE('AXSTYLE_BOXOUT',3);
186 DEFINE('AXSTYLE_YBOXIN',4);
187 DEFINE('AXSTYLE_YBOXOUT',5);
189 // Style for title backgrounds
190 DEFINE('TITLEBKG_STYLE1',1);
191 DEFINE('TITLEBKG_STYLE2',2);
192 DEFINE('TITLEBKG_STYLE3',3);
193 DEFINE('TITLEBKG_FRAME_NONE',0);
194 DEFINE('TITLEBKG_FRAME_FULL',1);
195 DEFINE('TITLEBKG_FRAME_BOTTOM',2);
196 DEFINE('TITLEBKG_FRAME_BEVEL',3);
197 DEFINE('TITLEBKG_FILLSTYLE_HSTRIPED',1);
198 DEFINE('TITLEBKG_FILLSTYLE_VSTRIPED',2);
199 DEFINE('TITLEBKG_FILLSTYLE_SOLID',3);
201 // Style for background gradient fills
202 DEFINE('BGRAD_FRAME',1);
203 DEFINE('BGRAD_MARGIN',2);
204 DEFINE('BGRAD_PLOT',3);
206 // Width of tab titles
207 DEFINE('TABTITLE_WIDTHFIT',0);
208 DEFINE('TABTITLE_WIDTHFULL',-1);
210 // Defines for 3D skew directions
211 DEFINE('SKEW3D_UP',0);
212 DEFINE('SKEW3D_DOWN',1);
213 DEFINE('SKEW3D_LEFT',2);
214 DEFINE('SKEW3D_RIGHT',3);
219 // Get hold of gradient class (In Version 2.x)
220 // A client of the library has to manually include this
222 require_once 'jpgraph_gradient.php';
225 class ErrMsgText {
226 var $lt=NULL;
227 var $supportedLocales = array('en');
228 function __construct() {
229 GLOBAL $__jpg_err_locale;
230 if( !in_array($__jpg_err_locale,$this->supportedLocales) )
231 $aLoc = 'en';
232 require_once('lang/'.$__jpg_err_locale.'.inc.php');
233 $this->lt = $_jpg_messages;
236 function Get($errnbr,$a1=null,$a2=null,$a3=null,$a4=null,$a5=null) {
237 if( !isset($this->lt[$errnbr]) ) {
238 return 'Internal error: The specified error message does not exist in the chosen locale. (Please blame the translator...))';
240 $ea = $this->lt[$errnbr];
241 $j=0;
242 if( $a1 !== null ) {
243 $argv[$j++] = $a1;
244 if( $a2 !== null ) {
245 $argv[$j++] = $a2;
246 if( $a3 !== null ) {
247 $argv[$j++] = $a3;
248 if( $a4 !== null ) {
249 $argv[$j++] = $a4;
250 if( $a5 !== null ) {
251 $argv[$j++] = $a5;
257 $numargs = $j;
258 if( $ea[1] != $numargs ) {
259 // Error message argument count do not match.
260 // Just return the error message without arguments.
261 return $ea[0];
263 switch( $numargs ) {
264 case 1:
265 $msg = sprintf($ea[0],$argv[0]);
266 break;
267 case 2:
268 $msg = sprintf($ea[0],$argv[0],$argv[1]);
269 break;
270 case 3:
271 $msg = sprintf($ea[0],$argv[0],$argv[1],$argv[2]);
272 break;
273 case 4:
274 $msg = sprintf($ea[0],$argv[0],$argv[1],$argv[2],$argv[3]);
275 break;
276 case 5:
277 $msg = sprintf($ea[0],$argv[0],$argv[1],$argv[2],$argv[3],$argv[4]);
278 break;
279 case 0:
280 default:
281 $msg = sprintf($ea[0]);
282 break;
284 return $msg;
289 // A wrapper class that is used to access the specified error object
290 // (to hide the global error parameter and avoid having a GLOBAL directive
291 // in all methods.
293 GLOBAL $__jpg_err;
294 GLOBAL $__jpg_err_locale ;
295 $__jpg_err_locale = 'en';
296 class JpGraphError {
297 static function Install($aErrObject) {
298 GLOBAL $__jpg_err;
299 $__jpg_err = $aErrObject;
301 static function Raise($aMsg,$aHalt=true){
302 GLOBAL $__jpg_err;
303 $tmp = new $__jpg_err;
304 $tmp->Raise($aMsg,$aHalt);
306 static function RaiseL($errnbr,$a1=null,$a2=null,$a3=null,$a4=null,$a5=null) {
307 GLOBAL $__jpg_err;
308 $t = new ErrMsgText();
309 $msg = $t->Get($errnbr,$a1,$a2,$a3,$a4,$a5);
310 $tmp = new $__jpg_err;
311 $tmp->Raise($msg);
316 // ... and install the default error handler
318 if( USE_IMAGE_ERROR_HANDLER ) {
319 $__jpg_err = "JpGraphErrObjectImg";
321 else {
322 $__jpg_err = "JpGraphErrObject";
326 // Make GD sanity check
328 if( !function_exists("imagetypes") || !function_exists('imagecreatefromstring') ) {
329 JpGraphError::RaiseL(25001);
330 //("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 // Routine to determine if GD1 or GD2 is installed
336 function CheckGDVersion() {
337 $GDfuncList = get_extension_funcs('gd');
338 if( !$GDfuncList ) return 0 ;
339 else {
340 if( in_array('imagegd2',$GDfuncList) &&
341 in_array('imagecreatetruecolor',$GDfuncList))
342 return 2;
343 else
344 return 1;
349 // Check what version of the GD library is installed.
351 $GLOBALS['gd2'] = false;
352 if( USE_LIBRARY_GD2 === 'auto' ) {
353 $gdversion = CheckGDVersion();
354 if( $gdversion == 2 ) {
355 $GLOBALS['gd2'] = true;
356 $GLOBALS['copyfunc'] = 'imagecopyresampled';
358 elseif( $gdversion == 1 ) {
359 $GLOBALS['gd2'] = false;
360 $GLOBALS['copyfunc'] = 'imagecopyresized';
362 else {
363 JpGraphError::RaiseL(25002);
364 //(" Your PHP installation does not seem to have the required GD library. Please see the PHP documentation on how to install and enable the GD library.");
367 else {
368 $GLOBALS['gd2'] = USE_LIBRARY_GD2;
369 $GLOBALS['copyfunc'] = USE_LIBRARY_GD2 ? 'imagecopyresampled' : 'imagecopyresized';
374 // First of all set up a default error handler
378 //=============================================================
379 // The default trivial text error handler.
380 //=============================================================
381 class JpGraphErrObject {
383 var $iTitle = "JpGraph Error";
384 var $iDest = false;
386 function __construct() {
387 // Empty. Reserved for future use
390 function SetTitle($aTitle) {
391 $this->iTitle = $aTitle;
394 function SetStrokeDest($aDest) {
395 $this->iDest = $aDest;
398 // If aHalt is true then execution can't continue. Typical used for fatal errors.
399 function Raise($aMsg,$aHalt=true) {
400 $aMsg = $this->iTitle.' '.$aMsg;
401 if ($this->iDest) {
402 $f = @fopen($this->iDest,'a');
403 if( $f ) {
404 @fwrite($f,$aMsg);
405 @fclose($f);
408 else {
409 echo $aMsg;
411 if( $aHalt )
412 die();
416 //==============================================================
417 // An image based error handler
418 //==============================================================
419 class JpGraphErrObjectImg extends JpGraphErrObject {
421 function Raise($aMsg,$aHalt=true) {
422 $img_iconerror =
423 'iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAMAAAC7IEhfAAAAaV'.
424 'BMVEX//////2Xy8mLl5V/Z2VvMzFi/v1WyslKlpU+ZmUyMjEh/'.
425 'f0VyckJlZT9YWDxMTDjAwMDy8sLl5bnY2K/MzKW/v5yyspKlpY'.
426 'iYmH+MjHY/PzV/f2xycmJlZVlZWU9MTEXY2Ms/PzwyMjLFTjea'.
427 'AAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACx'.
428 'IAAAsSAdLdfvwAAAAHdElNRQfTBgISOCqusfs5AAABLUlEQVR4'.
429 '2tWV3XKCMBBGWfkranCIVClKLd/7P2Q3QsgCxjDTq+6FE2cPH+'.
430 'xJ0Ogn2lQbsT+Wrs+buAZAV4W5T6Bs0YXBBwpKgEuIu+JERAX6'.
431 'wM2rHjmDdEITmsQEEmWADgZm6rAjhXsoMGY9B/NZBwJzBvn+e3'.
432 'wHntCAJdGu9SviwIwoZVDxPB9+Rc0TSEbQr0j3SA1gwdSn6Db0'.
433 '6Tm1KfV6yzWGQO7zdpvyKLKBDmRFjzeB3LYgK7r6A/noDAfjtS'.
434 'IXaIzbJSv6WgUebTMV4EoRB8a2mQiQjgtF91HdKDKZ1gtFtQjk'.
435 'YcWaR5OKOhkYt+ZsTFdJRfPAApOpQYJTNHvCRSJR6SJngQadfc'.
436 'vd69OLMddVOPCGVnmrFD8bVYd3JXfxXPtLR/+mtv59/ALWiiMx'.
437 'qL72fwAAAABJRU5ErkJggg==' ;
439 if( function_exists("imagetypes") )
440 $supported = imagetypes();
441 else
442 $supported = 0;
444 if( !function_exists('imagecreatefromstring') )
445 $supported = 0;
447 if( ob_get_length() || headers_sent() || !($supported & IMG_PNG) ) {
448 // Special case for headers already sent or that the installation doesn't support
449 // the PNG format (which the error icon is encoded in).
450 // Dont return an image since it can't be displayed
451 die($this->iTitle.' '.$aMsg);
454 $aMsg = wordwrap($aMsg,55);
455 $lines = substr_count($aMsg,"\n");
457 // Create the error icon GD
458 $erricon = Image::CreateFromString(base64_decode($img_iconerror));
460 // Create an image that contains the error text.
461 $w=400;
462 $h=100 + 15*max(0,$lines-3);
464 $img = new Image($w,$h);
466 // Drop shadow
467 $img->SetColor("gray");
468 $img->FilledRectangle(5,5,$w-1,$h-1,10);
469 $img->SetColor("gray:0.7");
470 $img->FilledRectangle(5,5,$w-3,$h-3,10);
472 // Window background
473 $img->SetColor("lightblue");
474 $img->FilledRectangle(1,1,$w-5,$h-5);
475 $img->CopyCanvasH($img->img,$erricon,5,30,0,0,40,40);
477 // Window border
478 $img->SetColor("black");
479 $img->Rectangle(1,1,$w-5,$h-5);
480 $img->Rectangle(0,0,$w-4,$h-4);
482 // Window top row
483 $img->SetColor("darkred");
484 for($y=3; $y < 18; $y += 2 )
485 $img->Line(1,$y,$w-6,$y);
487 // "White shadow"
488 $img->SetColor("white");
490 // Left window edge
491 $img->Line(2,2,2,$h-5);
492 $img->Line(2,2,$w-6,2);
494 // "Gray button shadow"
495 $img->SetColor("darkgray");
497 // Gray window shadow
498 $img->Line(2,$h-6,$w-5,$h-6);
499 $img->Line(3,$h-7,$w-5,$h-7);
501 // Window title
502 $m = floor($w/2-5);
503 $l = 100;
504 $img->SetColor("lightgray:1.3");
505 $img->FilledRectangle($m-$l,2,$m+$l,16);
507 // Stroke text
508 $img->SetColor("darkred");
509 $img->SetFont(FF_FONT2,FS_BOLD);
510 $img->StrokeText($m-50,15,$this->iTitle);
511 $img->SetColor("black");
512 $img->SetFont(FF_FONT1,FS_NORMAL);
513 $txt = new Text($aMsg,52,25);
514 $txt->Align("left","top");
515 $txt->Stroke($img);
516 if ($this->iDest) {
517 $img->Stream($this->iDest);
518 } else {
519 $img->Headers();
520 $img->Stream();
522 if( $aHalt )
523 die();
528 // Setup PHP error handler
530 function _phpErrorHandler($errno,$errmsg,$filename, $linenum, $vars) {
531 // Respect current error level
532 if( $errno & error_reporting() ) {
533 JpGraphError::RaiseL(25003,basename($filename),$linenum,$errmsg);
537 if( INSTALL_PHP_ERR_HANDLER ) {
538 set_error_handler("_phpErrorHandler");
542 //Check if there were any warnings, perhaps some wrong includes by the user
544 if( isset($GLOBALS['php_errormsg']) && CATCH_PHPERRMSG &&
545 !preg_match('|Deprecated|', $GLOBALS['php_errormsg'])) {
546 JpGraphError::RaiseL(25004,$GLOBALS['php_errormsg']);
550 // Useful mathematical function
551 function sign($a) {return $a >= 0 ? 1 : -1;}
553 // Utility function to generate an image name based on the filename we
554 // are running from and assuming we use auto detection of graphic format
555 // (top level), i.e it is safe to call this function
556 // from a script that uses JpGraph
557 function GenImgName() {
558 global $_SERVER;
560 // Determine what format we should use when we save the images
561 $supported = imagetypes();
562 if( $supported & IMG_PNG ) $img_format="png";
563 elseif( $supported & IMG_GIF ) $img_format="gif";
564 elseif( $supported & IMG_JPG ) $img_format="jpeg";
566 if( !isset($_SERVER['PHP_SELF']) )
567 JpGraphError::RaiseL(25005);
568 //(" 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.");
569 $fname = basename($_SERVER['PHP_SELF']);
570 if( !empty($_SERVER['QUERY_STRING']) ) {
571 $q = @$_SERVER['QUERY_STRING'];
572 $fname .= '?'.preg_replace("/\W/", "_", $q).'.'.$img_format;
574 else {
575 $fname = substr($fname,0,strlen($fname)-4).'.'.$img_format;
577 return $fname;
580 class LanguageConv {
581 var $g2312 = null ;
583 function Convert($aTxt,$aFF) {
584 if( LANGUAGE_CYRILLIC ) {
585 if( CYRILLIC_FROM_WINDOWS ) {
586 $aTxt = convert_cyr_string($aTxt, "w", "k");
588 $isostring = convert_cyr_string($aTxt, "k", "i");
589 $unistring = LanguageConv::iso2uni($isostring);
590 return $unistring;
592 elseif( $aFF === FF_SIMSUN ) {
593 // Do Chinese conversion
594 if( $this->g2312 == null ) {
595 include_once 'jpgraph_gb2312.php' ;
596 $this->g2312 = new GB2312toUTF8();
598 return $this->g2312->gb2utf8($aTxt);
600 elseif( $aFF === FF_CHINESE ) {
601 if( !function_exists('iconv') ) {
602 JpGraphError::RaiseL(25006);
603 //('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).');
605 return iconv('BIG5','UTF-8',$aTxt);
607 else
608 return $aTxt;
611 // Translate iso encoding to unicode
612 function iso2uni ($isoline){
613 $uniline='';
614 for ($i=0; $i < strlen($isoline); $i++){
615 $thischar=substr($isoline,$i,1);
616 $charcode=ord($thischar);
617 $uniline.=($charcode>175) ? "&#" . (1040+($charcode-176)). ";" : $thischar;
619 return $uniline;
623 //===================================================
624 // CLASS JpgTimer
625 // Description: General timing utility class to handle
626 // time measurement of generating graphs. Multiple
627 // timers can be started.
628 //===================================================
629 class JpgTimer {
630 var $start;
631 var $idx;
632 //---------------
633 // CONSTRUCTOR
634 function __construct() {
635 $this->idx=0;
638 //---------------
639 // PUBLIC METHODS
641 // Push a new timer start on stack
642 function Push() {
643 list($ms,$s)=explode(" ",microtime());
644 $this->start[$this->idx++]=floor($ms*1000) + 1000*$s;
647 // Pop the latest timer start and return the diff with the
648 // current time
649 function Pop() {
650 assert($this->idx>0);
651 list($ms,$s)=explode(" ",microtime());
652 $etime=floor($ms*1000) + (1000*$s);
653 $this->idx--;
654 return $etime-$this->start[$this->idx];
656 } // Class
658 $gJpgBrandTiming = BRAND_TIMING;
659 //===================================================
660 // CLASS DateLocale
661 // Description: Hold localized text used in dates
662 //===================================================
663 class DateLocale {
665 var $iLocale = 'C'; // environmental locale be used by default
667 var $iDayAbb = null;
668 var $iShortDay = null;
669 var $iShortMonth = null;
670 var $iMonthName = null;
672 //---------------
673 // CONSTRUCTOR
674 function __construct() {
675 settype($this->iDayAbb, 'array');
676 settype($this->iShortDay, 'array');
677 settype($this->iShortMonth, 'array');
678 settype($this->iMonthName, 'array');
681 $this->Set('C');
684 //---------------
685 // PUBLIC METHODS
686 function Set($aLocale) {
687 if ( in_array($aLocale, array_keys($this->iDayAbb)) ){
688 $this->iLocale = $aLocale;
689 return TRUE; // already cached nothing else to do!
692 $pLocale = setlocale(LC_TIME, 0); // get current locale for LC_TIME
693 $res = @setlocale(LC_TIME, $aLocale);
694 if ( ! $res ){
695 JpGraphError::RaiseL(25007,$aLocale);
696 //("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.");
697 return FALSE;
700 $this->iLocale = $aLocale;
702 for ( $i = 0, $ofs = 0 - strftime('%w'); $i < 7; $i++, $ofs++ ){
703 $day = strftime('%a', strtotime("$ofs day"));
704 $day{0} = strtoupper($day{0});
705 $this->iDayAbb[$aLocale][]= $day{0};
706 $this->iShortDay[$aLocale][]= $day;
709 for($i=1; $i<=12; ++$i) {
710 list($short ,$full) = explode('|', strftime("%b|%B",strtotime("2001-$i-01")));
711 $this->iShortMonth[$aLocale][] = ucfirst($short);
712 $this->iMonthName [$aLocale][] = ucfirst($full);
715 // Return to original locale
716 setlocale(LC_TIME, $pLocale);
718 return TRUE;
722 function GetDayAbb() {
723 return $this->iDayAbb[$this->iLocale];
726 function GetShortDay() {
727 return $this->iShortDay[$this->iLocale];
730 function GetShortMonth() {
731 return $this->iShortMonth[$this->iLocale];
734 function GetShortMonthName($aNbr) {
735 return $this->iShortMonth[$this->iLocale][$aNbr];
738 function GetLongMonthName($aNbr) {
739 return $this->iMonthName[$this->iLocale][$aNbr];
742 function GetMonth() {
743 return $this->iMonthName[$this->iLocale];
747 $gDateLocale = new DateLocale();
748 $gJpgDateLocale = new DateLocale();
751 //=======================================================
752 // CLASS Footer
753 // Description: Encapsulates the footer line in the Graph
754 //=======================================================
755 class Footer {
756 var $left,$center,$right;
757 var $iLeftMargin = 3;
758 var $iRightMargin = 3;
759 var $iBottomMargin = 3;
761 function __construct() {
762 $this->left = new Text();
763 $this->left->ParagraphAlign('left');
764 $this->center = new Text();
765 $this->center->ParagraphAlign('center');
766 $this->right = new Text();
767 $this->right->ParagraphAlign('right');
770 function Stroke(&$aImg) {
771 $y = $aImg->height - $this->iBottomMargin;
772 $x = $this->iLeftMargin;
773 $this->left->Align('left','bottom');
774 $this->left->Stroke($aImg,$x,$y);
776 $x = ($aImg->width - $this->iLeftMargin - $this->iRightMargin)/2;
777 $this->center->Align('center','bottom');
778 $this->center->Stroke($aImg,$x,$y);
780 $x = $aImg->width - $this->iRightMargin;
781 $this->right->Align('right','bottom');
782 $this->right->Stroke($aImg,$x,$y);
787 //===================================================
788 // CLASS Graph
789 // Description: Main class to handle graphs
790 //===================================================
791 class Graph {
792 var $cache=null; // Cache object (singleton)
793 var $img=null; // Img object (singleton)
794 var $plots=array(); // Array of all plot object in the graph (for Y 1 axis)
795 var $y2plots=array();// Array of all plot object in the graph (for Y 2 axis)
796 var $ynplots=array();
797 var $xscale=null; // X Scale object (could be instance of LinearScale or LogScale
798 var $yscale=null,$y2scale=null, $ynscale=array();
799 var $iIcons = array(); // Array of Icons to add to
800 var $cache_name; // File name to be used for the current graph in the cache directory
801 var $xgrid=null; // X Grid object (linear or logarithmic)
802 var $ygrid=null,$y2grid=null;
803 var $doframe=true,$frame_color=array(0,0,0), $frame_weight=1; // Frame around graph
804 var $boxed=false, $box_color=array(0,0,0), $box_weight=1; // Box around plot area
805 var $doshadow=false,$shadow_width=4,$shadow_color=array(102,102,102); // Shadow for graph
806 var $xaxis=null; // X-axis (instane of Axis class)
807 var $yaxis=null, $y2axis=null, $ynaxis=array(); // Y axis (instance of Axis class)
808 var $margin_color=array(200,200,200); // Margin color of graph
809 var $plotarea_color=array(255,255,255); // Plot area color
810 var $title,$subtitle,$subsubtitle; // Title and subtitle(s) text object
811 var $axtype="linlin"; // Type of axis
812 var $xtick_factor; // Factot to determine the maximum number of ticks depending on the plot with
813 var $texts=null, $y2texts=null; // Text object to ge shown in the graph
814 var $lines=null, $y2lines=null;
815 var $bands=null, $y2bands=null;
816 var $text_scale_off=0, $text_scale_abscenteroff=-1; // Text scale offset in fractions and for centering bars in absolute pixels
817 var $background_image="",$background_image_type=-1,$background_image_format="png";
818 var $background_image_bright=0,$background_image_contr=0,$background_image_sat=0;
819 var $image_bright=0, $image_contr=0, $image_sat=0;
820 var $inline;
821 var $showcsim=0,$csimcolor="red"; //debug stuff, draw the csim boundaris on the image if <>0
822 var $grid_depth=DEPTH_BACK; // Draw grid under all plots as default
823 var $iAxisStyle = AXSTYLE_SIMPLE;
824 var $iCSIMdisplay=false,$iHasStroked = false;
825 var $footer;
826 var $csimcachename = '', $csimcachetimeout = 0;
827 var $iDoClipping = false;
828 var $y2orderback=true;
829 var $tabtitle;
830 var $bkg_gradtype=-1,$bkg_gradstyle=BGRAD_MARGIN;
831 var $bkg_gradfrom='navy', $bkg_gradto='silver';
832 var $titlebackground = false;
833 var $titlebackground_color = 'lightblue',
834 $titlebackground_style = 1,
835 $titlebackground_framecolor = 'blue',
836 $titlebackground_framestyle = 2,
837 $titlebackground_frameweight = 1,
838 $titlebackground_bevelheight = 3 ;
839 var $titlebkg_fillstyle=TITLEBKG_FILLSTYLE_SOLID;
840 var $titlebkg_scolor1='black',$titlebkg_scolor2='white';
841 var $framebevel = false, $framebeveldepth = 2 ;
842 var $framebevelborder = false, $framebevelbordercolor='black';
843 var $framebevelcolor1='white@0.4', $framebevelcolor2='black@0.4';
844 var $background_image_mix=100;
845 var $background_cflag = '';
846 var $background_cflag_type = BGIMG_FILLPLOT;
847 var $background_cflag_mix = 100;
848 var $iImgTrans=false,
849 $iImgTransHorizon = 100,$iImgTransSkewDist=150,
850 $iImgTransDirection = 1, $iImgTransMinSize = true,
851 $iImgTransFillColor='white',$iImgTransHighQ=false,
852 $iImgTransBorder=false,$iImgTransHorizonPos=0.5;
853 var $iYAxisDeltaPos=50;
854 var $iIconDepth=DEPTH_BACK;
855 var $iAxisLblBgType = 0,
856 $iXAxisLblBgFillColor = 'lightgray', $iXAxisLblBgColor = 'black',
857 $iYAxisLblBgFillColor = 'lightgray', $iYAxisLblBgColor = 'black';
858 var $iTables=NULL;
860 //---------------
861 // CONSTRUCTOR
863 // aWIdth Width in pixels of image
864 // aHeight Height in pixels of image
865 // aCachedName Name for image file in cache directory
866 // aTimeOut Timeout in minutes for image in cache
867 // aInline If true the image is streamed back in the call to Stroke()
868 // If false the image is just created in the cache
869 function __construct($aWidth=300,$aHeight=200,$aCachedName="",$aTimeOut=0,$aInline=true) {
870 GLOBAL $gJpgBrandTiming;
871 // If timing is used create a new timing object
872 if( $gJpgBrandTiming ) {
873 global $tim;
874 $tim = new JpgTimer();
875 $tim->Push();
878 if( !is_numeric($aWidth) || !is_numeric($aHeight) ) {
879 JpGraphError::RaiseL(25008);//('Image width/height argument in Graph::Graph() must be numeric');
882 // Automatically generate the image file name based on the name of the script that
883 // generates the graph
884 if( $aCachedName=="auto" )
885 $aCachedName=GenImgName();
887 // Should the image be streamed back to the browser or only to the cache?
888 $this->inline=$aInline;
890 $this->img = new RotImage($aWidth,$aHeight);
892 $this->cache = new ImgStreamCache($this->img);
893 $this->cache->SetTimeOut($aTimeOut);
895 $this->title = new Text();
896 $this->title->ParagraphAlign('center');
897 $this->title->SetFont(FF_FONT2,FS_BOLD);
898 $this->title->SetMargin(3);
899 $this->title->SetAlign('center');
901 $this->subtitle = new Text();
902 $this->subtitle->ParagraphAlign('center');
903 $this->subtitle->SetMargin(2);
904 $this->subtitle->SetAlign('center');
906 $this->subsubtitle = new Text();
907 $this->subsubtitle->ParagraphAlign('center');
908 $this->subsubtitle->SetMargin(2);
909 $this->subsubtitle->SetAlign('center');
911 $this->legend = new Legend();
912 $this->footer = new Footer();
914 // Window doesn't like '?' in the file name so replace it with an '_'
915 $aCachedName = str_replace("?","_",$aCachedName);
917 // If the cached version exist just read it directly from the
918 // cache, stream it back to browser and exit
919 if( $aCachedName!="" && READ_CACHE && $aInline )
920 if( $this->cache->GetAndStream($aCachedName) ) {
921 exit();
924 $this->cache_name = $aCachedName;
925 $this->SetTickDensity(); // Normal density
927 $this->tabtitle = new GraphTabTitle();
929 //---------------
930 // PUBLIC METHODS
931 // Enable final image perspective transformation
932 function Set3DPerspective($aDir=1,$aHorizon=100,$aSkewDist=120,$aQuality=false,$aFillColor='#FFFFFF',$aBorder=false,$aMinSize=true,$aHorizonPos=0.5) {
933 $this->iImgTrans = true;
934 $this->iImgTransHorizon = $aHorizon;
935 $this->iImgTransSkewDist= $aSkewDist;
936 $this->iImgTransDirection = $aDir;
937 $this->iImgTransMinSize = $aMinSize;
938 $this->iImgTransFillColor=$aFillColor;
939 $this->iImgTransHighQ=$aQuality;
940 $this->iImgTransBorder=$aBorder;
941 $this->iImgTransHorizonPos=$aHorizonPos;
944 // Set Image format and optional quality
945 function SetImgFormat($aFormat,$aQuality=75) {
946 $this->img->SetImgFormat($aFormat,$aQuality);
949 // Should the grid be in front or back of the plot?
950 function SetGridDepth($aDepth) {
951 $this->grid_depth=$aDepth;
954 function SetIconDepth($aDepth) {
955 $this->iIconDepth=$aDepth;
958 // Specify graph angle 0-360 degrees.
959 function SetAngle($aAngle) {
960 $this->img->SetAngle($aAngle);
963 function SetAlphaBlending($aFlg=true) {
964 $this->img->SetAlphaBlending($aFlg);
967 // Shortcut to image margin
968 function SetMargin($lm,$rm,$tm,$bm) {
969 $this->img->SetMargin($lm,$rm,$tm,$bm);
972 function SetY2OrderBack($aBack=true) {
973 $this->y2orderback = $aBack;
976 // Rotate the graph 90 degrees and set the margin
977 // when we have done a 90 degree rotation
978 function Set90AndMargin($lm=0,$rm=0,$tm=0,$bm=0) {
979 $lm = $lm ==0 ? floor(0.2 * $this->img->width) : $lm ;
980 $rm = $rm ==0 ? floor(0.1 * $this->img->width) : $rm ;
981 $tm = $tm ==0 ? floor(0.2 * $this->img->height) : $tm ;
982 $bm = $bm ==0 ? floor(0.1 * $this->img->height) : $bm ;
984 $adj = ($this->img->height - $this->img->width)/2;
985 $this->img->SetMargin($tm-$adj,$bm-$adj,$rm+$adj,$lm+$adj);
986 $this->img->SetCenter(floor($this->img->width/2),floor($this->img->height/2));
987 $this->SetAngle(90);
988 if( empty($this->yaxis) || empty($this->xaxis) ) {
989 JpgraphError::RaiseL(25009);//('You must specify what scale to use with a call to Graph::SetScale()');
991 $this->xaxis->SetLabelAlign('right','center');
992 $this->yaxis->SetLabelAlign('center','bottom');
995 function SetClipping($aFlg=true) {
996 $this->iDoClipping = $aFlg ;
999 // Add a plot object to the graph
1000 function Add(&$aPlot) {
1001 if( $aPlot == null )
1002 JpGraphError::RaiseL(25010);//("Graph::Add() You tried to add a null plot to the graph.");
1003 if( is_array($aPlot) && count($aPlot) > 0 )
1004 $cl = $aPlot[0];
1005 else
1006 $cl = $aPlot;
1008 if( is_a($cl,'Text') )
1009 $this->AddText($aPlot);
1010 elseif( is_a($cl,'PlotLine') )
1011 $this->AddLine($aPlot);
1012 elseif( is_a($cl,'PlotBand') )
1013 $this->AddBand($aPlot);
1014 elseif( is_a($cl,'IconPlot') )
1015 $this->AddIcon($aPlot);
1016 elseif( is_a($cl,'GTextTable') )
1017 $this->AddTable($aPlot);
1018 else
1019 $this->plots[] = &$aPlot;
1023 function AddTable(&$aTable) {
1024 if( is_array($aTable) ) {
1025 for($i=0; $i < count($aTable); ++$i )
1026 $this->iTables[]=&$aTable[$i];
1028 else {
1029 $this->iTables[] = &$aTable ;
1033 function AddIcon(&$aIcon) {
1034 if( is_array($aIcon) ) {
1035 for($i=0; $i < count($aIcon); ++$i )
1036 $this->iIcons[]=&$aIcon[$i];
1038 else {
1039 $this->iIcons[] = &$aIcon ;
1043 // Add plot to second Y-scale
1044 function AddY2(&$aPlot) {
1045 if( $aPlot == null )
1046 JpGraphError::RaiseL(25011);//("Graph::AddY2() You tried to add a null plot to the graph.");
1048 if( is_array($aPlot) && count($aPlot) > 0 )
1049 $cl = $aPlot[0];
1050 else
1051 $cl = $aPlot;
1053 if( is_a($cl,'Text') )
1054 $this->AddText($aPlot,true);
1055 elseif( is_a($cl,'PlotLine') )
1056 $this->AddLine($aPlot,true);
1057 elseif( is_a($cl,'PlotBand') )
1058 $this->AddBand($aPlot,true);
1059 else
1060 $this->y2plots[] = &$aPlot;
1063 // Add plot to second Y-scale
1064 function AddY($aN,&$aPlot) {
1066 if( $aPlot == null )
1067 JpGraphError::RaiseL(25012);//("Graph::AddYN() You tried to add a null plot to the graph.");
1069 if( is_array($aPlot) && count($aPlot) > 0 )
1070 $cl = $aPlot[0];
1071 else
1072 $cl = $aPlot;
1074 if( is_a($cl,'Text') || is_a($cl,'PlotLine') || is_a($cl,'PlotBand') )
1075 JpGraph::RaiseL(25013);//('You can only add standard plots to multiple Y-axis');
1076 else
1077 $this->ynplots[$aN][] = &$aPlot;
1080 // Add text object to the graph
1081 function AddText(&$aTxt,$aToY2=false) {
1082 if( $aTxt == null )
1083 JpGraphError::RaiseL(25014);//("Graph::AddText() You tried to add a null text to the graph.");
1084 if( $aToY2 ) {
1085 if( is_array($aTxt) ) {
1086 for($i=0; $i < count($aTxt); ++$i )
1087 $this->y2texts[]=&$aTxt[$i];
1089 else
1090 $this->y2texts[] = &$aTxt;
1092 else {
1093 if( is_array($aTxt) ) {
1094 for($i=0; $i < count($aTxt); ++$i )
1095 $this->texts[]=&$aTxt[$i];
1097 else
1098 $this->texts[] = &$aTxt;
1102 // Add a line object (class PlotLine) to the graph
1103 function AddLine(&$aLine,$aToY2=false) {
1104 if( $aLine == null )
1105 JpGraphError::RaiseL(25015);//("Graph::AddLine() You tried to add a null line to the graph.");
1107 if( $aToY2 ) {
1108 if( is_array($aLine) ) {
1109 for($i=0; $i < count($aLine); ++$i )
1110 $this->y2lines[]=&$aLine[$i];
1112 else
1113 $this->y2lines[] = &$aLine;
1115 else {
1116 if( is_array($aLine) ) {
1117 for($i=0; $i < count($aLine); ++$i )
1118 $this->lines[]=&$aLine[$i];
1120 else
1121 $this->lines[] = &$aLine;
1125 // Add vertical or horizontal band
1126 function AddBand(&$aBand,$aToY2=false) {
1127 if( $aBand == null )
1128 JpGraphError::RaiseL(25016);//(" Graph::AddBand() You tried to add a null band to the graph.");
1130 if( $aToY2 ) {
1131 if( is_array($aBand) ) {
1132 for($i=0; $i < count($aBand); ++$i )
1133 $this->y2bands[] = &$aBand[$i];
1135 else
1136 $this->y2bands[] = &$aBand;
1138 else {
1139 if( is_array($aBand) ) {
1140 for($i=0; $i < count($aBand); ++$i )
1141 $this->bands[] = &$aBand[$i];
1143 else
1144 $this->bands[] = &$aBand;
1148 function SetBackgroundGradient($aFrom='navy',$aTo='silver',$aGradType=2,$aStyle=BGRAD_FRAME) {
1149 $this->bkg_gradtype=$aGradType;
1150 $this->bkg_gradstyle=$aStyle;
1151 $this->bkg_gradfrom = $aFrom;
1152 $this->bkg_gradto = $aTo;
1155 // Set a country flag in the background
1156 function SetBackgroundCFlag($aName,$aBgType=BGIMG_FILLPLOT,$aMix=100) {
1157 $this->background_cflag = $aName;
1158 $this->background_cflag_type = $aBgType;
1159 $this->background_cflag_mix = $aMix;
1162 // Alias for the above method
1163 function SetBackgroundCountryFlag($aName,$aBgType=BGIMG_FILLPLOT,$aMix=100) {
1164 $this->background_cflag = $aName;
1165 $this->background_cflag_type = $aBgType;
1166 $this->background_cflag_mix = $aMix;
1170 // Specify a background image
1171 function SetBackgroundImage($aFileName,$aBgType=BGIMG_FILLPLOT,$aImgFormat="auto") {
1173 if( $GLOBALS['gd2'] && !USE_TRUECOLOR ) {
1174 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.");
1177 // Get extension to determine image type
1178 if( $aImgFormat == "auto" ) {
1179 $e = explode('.',$aFileName);
1180 if( !$e ) {
1181 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');
1184 $valid_formats = array('png', 'jpg', 'gif');
1185 $aImgFormat = strtolower($e[count($e)-1]);
1186 if ($aImgFormat == 'jpeg') {
1187 $aImgFormat = 'jpg';
1189 elseif (!in_array($aImgFormat, $valid_formats) ) {
1190 JpGraphError::RaiseL(25019,$aImgFormat);//('Unknown file extension ($aImgFormat) in Graph::SetBackgroundImage() for filename: '.$aFileName);
1194 $this->background_image = $aFileName;
1195 $this->background_image_type=$aBgType;
1196 $this->background_image_format=$aImgFormat;
1199 function SetBackgroundImageMix($aMix) {
1200 $this->background_image_mix = $aMix ;
1203 // Adjust brightness and constrast for background image
1204 function AdjBackgroundImage($aBright,$aContr=0,$aSat=0) {
1205 $this->background_image_bright=$aBright;
1206 $this->background_image_contr=$aContr;
1207 $this->background_image_sat=$aSat;
1210 // Adjust brightness and constrast for image
1211 function AdjImage($aBright,$aContr=0,$aSat=0) {
1212 $this->image_bright=$aBright;
1213 $this->image_contr=$aContr;
1214 $this->image_sat=$aSat;
1217 // Specify axis style (boxed or single)
1218 function SetAxisStyle($aStyle) {
1219 $this->iAxisStyle = $aStyle ;
1222 // Set a frame around the plot area
1223 function SetBox($aDrawPlotFrame=true,$aPlotFrameColor=array(0,0,0),$aPlotFrameWeight=1) {
1224 $this->boxed = $aDrawPlotFrame;
1225 $this->box_weight = $aPlotFrameWeight;
1226 $this->box_color = $aPlotFrameColor;
1229 // Specify color for the plotarea (not the margins)
1230 function SetColor($aColor) {
1231 $this->plotarea_color=$aColor;
1234 // Specify color for the margins (all areas outside the plotarea)
1235 function SetMarginColor($aColor) {
1236 $this->margin_color=$aColor;
1239 // Set a frame around the entire image
1240 function SetFrame($aDrawImgFrame=true,$aImgFrameColor=array(0,0,0),$aImgFrameWeight=1) {
1241 $this->doframe = $aDrawImgFrame;
1242 $this->frame_color = $aImgFrameColor;
1243 $this->frame_weight = $aImgFrameWeight;
1246 function SetFrameBevel($aDepth=3,$aBorder=false,$aBorderColor='black',$aColor1='white@0.4',$aColor2='darkgray@0.4',$aFlg=true) {
1247 $this->framebevel = $aFlg ;
1248 $this->framebeveldepth = $aDepth ;
1249 $this->framebevelborder = $aBorder ;
1250 $this->framebevelbordercolor = $aBorderColor ;
1251 $this->framebevelcolor1 = $aColor1 ;
1252 $this->framebevelcolor2 = $aColor2 ;
1254 $this->doshadow = false ;
1257 // Set the shadow around the whole image
1258 function SetShadow($aShowShadow=true,$aShadowWidth=5,$aShadowColor=array(102,102,102)) {
1259 $this->doshadow = $aShowShadow;
1260 $this->shadow_color = $aShadowColor;
1261 $this->shadow_width = $aShadowWidth;
1262 $this->footer->iBottomMargin += $aShadowWidth;
1263 $this->footer->iRightMargin += $aShadowWidth;
1266 // Specify x,y scale. Note that if you manually specify the scale
1267 // you must also specify the tick distance with a call to Ticks::Set()
1268 function SetScale($aAxisType,$aYMin=1,$aYMax=1,$aXMin=1,$aXMax=1) {
1269 $this->axtype = $aAxisType;
1271 if( $aYMax < $aYMin || $aXMax < $aXMin )
1272 JpGraphError::RaiseL(25020);//('Graph::SetScale(): Specified Max value must be larger than the specified Min value.');
1274 $yt=substr($aAxisType,-3,3);
1275 if( $yt=="lin" )
1276 $this->yscale = new LinearScale($aYMin,$aYMax);
1277 elseif( $yt == "int" ) {
1278 $this->yscale = new LinearScale($aYMin,$aYMax);
1279 $this->yscale->SetIntScale();
1281 elseif( $yt=="log" )
1282 $this->yscale = new LogScale($aYMin,$aYMax);
1283 else
1284 JpGraphError::RaiseL(25021,$aAxisType);//("Unknown scale specification for Y-scale. ($aAxisType)");
1286 $xt=substr($aAxisType,0,3);
1287 if( $xt == "lin" || $xt == "tex" ) {
1288 $this->xscale = new LinearScale($aXMin,$aXMax,"x");
1289 $this->xscale->textscale = ($xt == "tex");
1291 elseif( $xt == "int" ) {
1292 $this->xscale = new LinearScale($aXMin,$aXMax,"x");
1293 $this->xscale->SetIntScale();
1295 elseif( $xt == "dat" ) {
1296 $this->xscale = new DateScale($aXMin,$aXMax,"x");
1298 elseif( $xt == "log" )
1299 $this->xscale = new LogScale($aXMin,$aXMax,"x");
1300 else
1301 JpGraphError::RaiseL(25022,$aAxisType);//(" Unknown scale specification for X-scale. ($aAxisType)");
1303 $this->xaxis = new Axis($this->img,$this->xscale);
1304 $this->yaxis = new Axis($this->img,$this->yscale);
1305 $this->xgrid = new Grid($this->xaxis);
1306 $this->ygrid = new Grid($this->yaxis);
1307 $this->ygrid->Show();
1310 // Specify secondary Y scale
1311 function SetY2Scale($aAxisType="lin",$aY2Min=1,$aY2Max=1) {
1312 if( $aAxisType=="lin" )
1313 $this->y2scale = new LinearScale($aY2Min,$aY2Max);
1314 elseif( $aAxisType == "int" ) {
1315 $this->y2scale = new LinearScale($aY2Min,$aY2Max);
1316 $this->y2scale->SetIntScale();
1318 elseif( $aAxisType=="log" ) {
1319 $this->y2scale = new LogScale($aY2Min,$aY2Max);
1321 else JpGraphError::RaiseL(25023,$aAxisType);//("JpGraph: Unsupported Y2 axis type: $aAxisType\nMust be one of (lin,log,int)");
1323 $this->y2axis = new Axis($this->img,$this->y2scale);
1324 $this->y2axis->scale->ticks->SetDirection(SIDE_LEFT);
1325 $this->y2axis->SetLabelSide(SIDE_RIGHT);
1326 $this->y2axis->SetPos('max');
1327 $this->y2axis->SetTitleSide(SIDE_RIGHT);
1329 // Deafult position is the max x-value
1330 $this->y2grid = new Grid($this->y2axis);
1333 // Set the delta position (in pixels) between the multiple Y-axis
1334 function SetYDeltaDist($aDist) {
1335 $this->iYAxisDeltaPos = $aDist;
1338 // Specify secondary Y scale
1339 function SetYScale($aN,$aAxisType="lin",$aYMin=1,$aYMax=1) {
1341 if( $aAxisType=="lin" )
1342 $this->ynscale[$aN] = new LinearScale($aYMin,$aYMax);
1343 elseif( $aAxisType == "int" ) {
1344 $this->ynscale[$aN] = new LinearScale($aYMin,$aYMax);
1345 $this->ynscale[$aN]->SetIntScale();
1347 elseif( $aAxisType=="log" ) {
1348 $this->ynscale[$aN] = new LogScale($aYMin,$aYMax);
1350 else JpGraphError::RaiseL(25024,$aAxisType);//("JpGraph: Unsupported Y axis type: $aAxisType\nMust be one of (lin,log,int)");
1352 $this->ynaxis[$aN] = new Axis($this->img,$this->ynscale[$aN]);
1353 $this->ynaxis[$aN]->scale->ticks->SetDirection(SIDE_LEFT);
1354 $this->ynaxis[$aN]->SetLabelSide(SIDE_RIGHT);
1358 // Specify density of ticks when autoscaling 'normal', 'dense', 'sparse', 'verysparse'
1359 // The dividing factor have been determined heuristically according to my aesthetic
1360 // sense (or lack off) y.m.m.v !
1361 function SetTickDensity($aYDensity=TICKD_NORMAL,$aXDensity=TICKD_NORMAL) {
1362 $this->xtick_factor=30;
1363 $this->ytick_factor=25;
1364 switch( $aYDensity ) {
1365 case TICKD_DENSE:
1366 $this->ytick_factor=12;
1367 break;
1368 case TICKD_NORMAL:
1369 $this->ytick_factor=25;
1370 break;
1371 case TICKD_SPARSE:
1372 $this->ytick_factor=40;
1373 break;
1374 case TICKD_VERYSPARSE:
1375 $this->ytick_factor=100;
1376 break;
1377 default:
1378 JpGraphError::RaiseL(25025,$densy);//("JpGraph: Unsupported Tick density: $densy");
1380 switch( $aXDensity ) {
1381 case TICKD_DENSE:
1382 $this->xtick_factor=15;
1383 break;
1384 case TICKD_NORMAL:
1385 $this->xtick_factor=30;
1386 break;
1387 case TICKD_SPARSE:
1388 $this->xtick_factor=45;
1389 break;
1390 case TICKD_VERYSPARSE:
1391 $this->xtick_factor=60;
1392 break;
1393 default:
1394 JpGraphError::RaiseL(25025,$densx);//("JpGraph: Unsupported Tick density: $densx");
1399 // Get a string of all image map areas
1400 function GetCSIMareas() {
1401 if( !$this->iHasStroked )
1402 $this->Stroke(_CSIM_SPECIALFILE);
1404 $csim = $this->title->GetCSIMAreas();
1405 $csim .= $this->subtitle->GetCSIMAreas();
1406 $csim .= $this->subsubtitle->GetCSIMAreas();
1407 $csim .= $this->legend->GetCSIMAreas();
1409 if( $this->y2axis != NULL ) {
1410 $csim .= $this->y2axis->title->GetCSIMAreas();
1413 if( $this->texts != null ) {
1414 $n = count($this->texts);
1415 for($i=0; $i < $n; ++$i ) {
1416 $csim .= $this->texts[$i]->GetCSIMAreas();
1420 if( $this->y2texts != null && $this->y2scale != null ) {
1421 $n = count($this->y2texts);
1422 for($i=0; $i < $n; ++$i ) {
1423 $csim .= $this->y2texts[$i]->GetCSIMAreas();
1427 if( $this->yaxis != null && $this->xaxis != null ) {
1428 $csim .= $this->yaxis->title->GetCSIMAreas();
1429 $csim .= $this->xaxis->title->GetCSIMAreas();
1432 $n = count($this->plots);
1433 for( $i=0; $i < $n; ++$i )
1434 $csim .= $this->plots[$i]->GetCSIMareas();
1436 $n = count($this->y2plots);
1437 for( $i=0; $i < $n; ++$i )
1438 $csim .= $this->y2plots[$i]->GetCSIMareas();
1440 $n = count($this->ynaxis);
1441 for( $i=0; $i < $n; ++$i ) {
1442 $m = count($this->ynplots[$i]);
1443 for($j=0; $j < $m; ++$j ) {
1444 $csim .= $this->ynplots[$i][$j]->GetCSIMareas();
1448 $n = count($this->iTables);
1449 for( $i=0; $i < $n; ++$i ) {
1450 $csim .= $this->iTables[$i]->GetCSIMareas();
1453 return $csim;
1456 // Get a complete <MAP>..</MAP> tag for the final image map
1457 function GetHTMLImageMap($aMapName) {
1458 //$im = "<map name=\"$aMapName\" id=\"$aMapName\">\n";
1459 $im = "<map name=\"$aMapName\" />\n";
1460 $im .= $this->GetCSIMareas();
1461 $im .= "</map>";
1462 return $im;
1465 function CheckCSIMCache($aCacheName,$aTimeOut=60) {
1466 global $_SERVER;
1468 if( $aCacheName=='auto' )
1469 $aCacheName=basename($_SERVER['PHP_SELF']);
1471 $this->csimcachename = CSIMCACHE_DIR.$aCacheName;
1472 $this->csimcachetimeout = $aTimeOut;
1474 // First determine if we need to check for a cached version
1475 // This differs from the standard cache in the sense that the
1476 // image and CSIM map HTML file is written relative to the directory
1477 // the script executes in and not the specified cache directory.
1478 // The reason for this is that the cache directory is not necessarily
1479 // accessible from the HTTP server.
1480 if( $this->csimcachename != '' ) {
1481 $dir = dirname($this->csimcachename);
1482 $base = basename($this->csimcachename);
1483 $base = strtok($base,'.');
1484 $suffix = strtok('.');
1485 $basecsim = $dir.'/'.$base.'_csim_.html';
1486 $baseimg = $dir.'/'.$base.'.'.$this->img->img_format;
1488 $timedout=false;
1490 // Does it exist at all ?
1492 if( file_exists($basecsim) && file_exists($baseimg) ) {
1493 // Check that it hasn't timed out
1494 $diff=time()-filemtime($basecsim);
1495 if( $this->csimcachetimeout>0 && ($diff > $this->csimcachetimeout*60) ) {
1496 $timedout=true;
1497 @unlink($basecsim);
1498 @unlink($baseimg);
1500 else {
1501 if ($fh = @fopen($basecsim, "r")) {
1502 fpassthru($fh);
1503 return true;
1505 else
1506 JpGraphError::RaiseL(25027,$basecsim);//(" Can't open cached CSIM \"$basecsim\" for reading.");
1510 return false;
1513 function StrokeCSIM($aScriptName='auto',$aCSIMName='',$aBorder=0) {
1514 if( $aCSIMName=='' ) {
1515 // create a random map name
1516 srand ((double) microtime() * 1000000);
1517 $r = rand(0,100000);
1518 $aCSIMName='__mapname'.$r.'__';
1521 if( $aScriptName=='auto' )
1522 $aScriptName=basename($_SERVER['PHP_SELF']);
1524 if( empty($_GET[_CSIM_DISPLAY]) ) {
1525 // First determine if we need to check for a cached version
1526 // This differs from the standard cache in the sense that the
1527 // image and CSIM map HTML file is written relative to the directory
1528 // the script executes in and not the specified cache directory.
1529 // The reason for this is that the cache directory is not necessarily
1530 // accessible from the HTTP server.
1531 if( $this->csimcachename != '' ) {
1532 $dir = dirname($this->csimcachename);
1533 $base = basename($this->csimcachename);
1534 $base = strtok($base,'.');
1535 $suffix = strtok('.');
1536 $basecsim = $dir.'/'.$base.'_csim_.html';
1537 $baseimg = $base.'.'.$this->img->img_format;
1539 // Check that apache can write to directory specified
1541 if( file_exists($dir) && !is_writeable($dir) ) {
1542 JpgraphError::RaiseL(25028,$dir);//('Apache/PHP does not have permission to write to the CSIM cache directory ('.$dir.'). Check permissions.');
1545 // Make sure directory exists
1546 $this->cache->MakeDirs($dir);
1548 // Write the image file
1549 $this->Stroke(CSIMCACHE_DIR.$baseimg);
1551 // Construct wrapper HTML and write to file and send it back to browser
1552 $htmlwrap = $this->GetHTMLImageMap($aCSIMName)."\n".
1553 '<img src="'.htmlentities(CSIMCACHE_HTTP_DIR.$baseimg).'" ismap usemap="#'.$aCSIMName.'" border='.$aBorder.' width='.$this->img->width.' height='.$this->img->height." alt=\"\" />\n";
1554 if($fh = @fopen($basecsim,'w') ) {
1555 fwrite($fh,$htmlwrap);
1556 fclose($fh);
1557 echo $htmlwrap;
1559 else
1560 JpGraphError::RaiseL(25029,$basecsim);//(" Can't write CSIM \"$basecsim\" for writing. Check free space and permissions.");
1562 else {
1564 if( $aScriptName=='' ) {
1565 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().');
1566 exit();
1570 // This is a JPGRAPH internal defined that prevents
1571 // us from recursively coming here again
1572 $urlarg='?'._CSIM_DISPLAY.'=1';
1574 // Now reconstruct any user URL argument
1575 reset($_GET);
1576 while( list($key,$value) = each($_GET) ) {
1577 if( is_array($value) ) {
1578 $n = count($value);
1579 for( $i=0; $i < $n; ++$i ) {
1580 $urlarg .= '&'.$key.'%5B%5D='.urlencode($value[$i]);
1583 else {
1584 $urlarg .= '&'.$key.'='.urlencode($value);
1588 // It's not ideal to convert POST argument to GET arguments
1589 // but there is little else we can do. One idea for the
1590 // future might be recreate the POST header in case.
1591 reset($_POST);
1592 while( list($key,$value) = each($_POST) ) {
1593 if( is_array($value) ) {
1594 $n = count($value);
1595 for( $i=0; $i < $n; ++$i ) {
1596 $urlarg .= '&'.$key.'%5B%5D='.urlencode($value[$i]);
1599 else {
1600 $urlarg .= '&'.$key.'='.urlencode($value);
1604 echo $this->GetHTMLImageMap($aCSIMName);
1606 echo "<img src='".htmlentities($aScriptName.$urlarg)."' ismap usemap='#".$aCSIMName.'\' border='.$aBorder.' width='.$this->img->width.' height='.$this->img->height." alt=\"\" />\n";
1609 else {
1610 $this->Stroke();
1614 function GetTextsYMinMax($aY2=false) {
1615 if( $aY2 )
1616 $txts = $this->y2texts;
1617 else
1618 $txts = $this->texts;
1619 $n = count($txts);
1620 $min=null;
1621 $max=null;
1622 for( $i=0; $i < $n; ++$i ) {
1623 if( $txts[$i]->iScalePosY !== null &&
1624 $txts[$i]->iScalePosX !== null ) {
1625 if( $min === null ) {
1626 $min = $max = $txts[$i]->iScalePosY ;
1628 else {
1629 $min = min($min,$txts[$i]->iScalePosY);
1630 $max = max($max,$txts[$i]->iScalePosY);
1634 if( $min !== null ) {
1635 return array($min,$max);
1637 else
1638 return null;
1641 function GetTextsXMinMax($aY2=false) {
1642 if( $aY2 )
1643 $txts = $this->y2texts;
1644 else
1645 $txts = $this->texts;
1646 $n = count($txts);
1647 $min=null;
1648 $max=null;
1649 for( $i=0; $i < $n; ++$i ) {
1650 if( $txts[$i]->iScalePosY !== null &&
1651 $txts[$i]->iScalePosX !== null ) {
1652 if( $min === null ) {
1653 $min = $max = $txts[$i]->iScalePosX ;
1655 else {
1656 $min = min($min,$txts[$i]->iScalePosX);
1657 $max = max($max,$txts[$i]->iScalePosX);
1661 if( $min !== null ) {
1662 return array($min,$max);
1664 else
1665 return null;
1668 function GetXMinMax() {
1669 list($min,$ymin) = $this->plots[0]->Min();
1670 list($max,$ymax) = $this->plots[0]->Max();
1671 foreach( $this->plots as $p ) {
1672 list($xmin,$ymin) = $p->Min();
1673 list($xmax,$ymax) = $p->Max();
1674 $min = Min($xmin,$min);
1675 $max = Max($xmax,$max);
1677 if( $this->y2axis != null ) {
1678 foreach( $this->y2plots as $p ) {
1679 list($xmin,$ymin) = $p->Min();
1680 list($xmax,$ymax) = $p->Max();
1681 $min = Min($xmin,$min);
1682 $max = Max($xmax,$max);
1686 $n = count($this->ynaxis);
1687 for( $i=0; $i < $n; ++$i ) {
1688 if( $this->ynaxis[$i] != null) {
1689 foreach( $this->ynplots[$i] as $p ) {
1690 list($xmin,$ymin) = $p->Min();
1691 list($xmax,$ymax) = $p->Max();
1692 $min = Min($xmin,$min);
1693 $max = Max($xmax,$max);
1698 return array($min,$max);
1701 function AdjustMarginsForTitles() {
1702 $totrequired =
1703 ($this->title->t != '' ?
1704 $this->title->GetTextHeight($this->img) + $this->title->margin + 5 : 0 ) +
1705 ($this->subtitle->t != '' ?
1706 $this->subtitle->GetTextHeight($this->img) + $this->subtitle->margin + 5 : 0 ) +
1707 ($this->subsubtitle->t != '' ?
1708 $this->subsubtitle->GetTextHeight($this->img) + $this->subsubtitle->margin + 5 : 0 ) ;
1711 $btotrequired = 0;
1712 if($this->xaxis != null && !$this->xaxis->hide && !$this->xaxis->hide_labels ) {
1713 // Minimum bottom margin
1714 if( $this->xaxis->title->t != '' ) {
1715 if( $this->img->a == 90 )
1716 $btotrequired = $this->yaxis->title->GetTextHeight($this->img) + 5 ;
1717 else
1718 $btotrequired = $this->xaxis->title->GetTextHeight($this->img) + 5 ;
1720 else
1721 $btotrequired = 0;
1723 if( $this->img->a == 90 ) {
1724 $this->img->SetFont($this->yaxis->font_family,$this->yaxis->font_style,
1725 $this->yaxis->font_size);
1726 $lh = $this->img->GetTextHeight('Mg',$this->yaxis->label_angle);
1728 else {
1729 $this->img->SetFont($this->xaxis->font_family,$this->xaxis->font_style,
1730 $this->xaxis->font_size);
1731 $lh = $this->img->GetTextHeight('Mg',$this->xaxis->label_angle);
1734 $btotrequired += $lh + 5;
1737 if( $this->img->a == 90 ) {
1738 // DO Nothing. It gets too messy to do this properly for 90 deg...
1740 else{
1741 if( $this->img->top_margin < $totrequired ) {
1742 $this->SetMargin($this->img->left_margin,$this->img->right_margin,
1743 $totrequired,$this->img->bottom_margin);
1745 if( $this->img->bottom_margin < $btotrequired ) {
1746 $this->SetMargin($this->img->left_margin,$this->img->right_margin,
1747 $this->img->top_margin,$btotrequired);
1752 // Stroke the graph
1753 // $aStrokeFileName If != "" the image will be written to this file and NOT
1754 // streamed back to the browser
1755 function Stroke($aStrokeFileName="") {
1757 // Fist make a sanity check that user has specified a scale
1758 if( empty($this->yscale) ) {
1759 JpGraphError::RaiseL(25031);//('You must specify what scale to use with a call to Graph::SetScale().');
1762 // Start by adjusting the margin so that potential titles will fit.
1763 $this->AdjustMarginsForTitles();
1765 // Setup scale constants
1766 if( $this->yscale ) $this->yscale->InitConstants($this->img);
1767 if( $this->xscale ) $this->xscale->InitConstants($this->img);
1768 if( $this->y2scale ) $this->y2scale->InitConstants($this->img);
1770 $n=count($this->ynscale);
1771 for($i=0; $i < $n; ++$i) {
1772 if( $this->ynscale[$i] ) $this->ynscale[$i]->InitConstants($this->img);
1775 // If the filename is the predefined value = '_csim_special_'
1776 // we assume that the call to stroke only needs to do enough
1777 // to correctly generate the CSIM maps.
1778 // We use this variable to skip things we don't strictly need
1779 // to do to generate the image map to improve performance
1780 // a best we can. Therefor you will see a lot of tests !$_csim in the
1781 // code below.
1782 $_csim = ($aStrokeFileName===_CSIM_SPECIALFILE);
1784 // We need to know if we have stroked the plot in the
1785 // GetCSIMareas. Otherwise the CSIM hasn't been generated
1786 // and in the case of GetCSIM called before stroke to generate
1787 // CSIM without storing an image to disk GetCSIM must call Stroke.
1788 $this->iHasStroked = true;
1790 // Do any pre-stroke adjustment that is needed by the different plot types
1791 // (i.e bar plots want's to add an offset to the x-labels etc)
1792 for($i=0; $i < count($this->plots) ; ++$i ) {
1793 $this->plots[$i]->PreStrokeAdjust($this);
1794 $this->plots[$i]->DoLegend($this);
1797 // Any plots on the second Y scale?
1798 if( $this->y2scale != null ) {
1799 for($i=0; $i<count($this->y2plots) ; ++$i ) {
1800 $this->y2plots[$i]->PreStrokeAdjust($this);
1801 $this->y2plots[$i]->DoLegend($this);
1805 // Any plots on the extra Y axises?
1806 $n = count($this->ynaxis);
1807 for($i=0; $i<$n ; ++$i ) {
1808 if( $this->ynplots == null || $this->ynplots[$i] == null) {
1809 JpGraphError::RaiseL(25032,$i);//("No plots for Y-axis nbr:$i");
1811 $m = count($this->ynplots[$i]);
1812 for($j=0; $j < $m; ++$j ) {
1813 $this->ynplots[$i][$j]->PreStrokeAdjust($this);
1814 $this->ynplots[$i][$j]->DoLegend($this);
1819 // Bail out if any of the Y-axis not been specified and
1820 // has no plots. (This means it is impossible to do autoscaling and
1821 // no other scale was given so we can't possible draw anything). If you use manual
1822 // scaling you also have to supply the tick steps as well.
1823 if( (!$this->yscale->IsSpecified() && count($this->plots)==0) ||
1824 ($this->y2scale!=null && !$this->y2scale->IsSpecified() && count($this->y2plots)==0) ) {
1825 //$e = "n=".count($this->y2plots)."\n";
1826 // $e = "Can't draw unspecified Y-scale.<br>\nYou have either:<br>\n";
1827 // $e .= "1. Specified an Y axis for autoscaling but have not supplied any plots<br>\n";
1828 // $e .= "2. Specified a scale manually but have forgot to specify the tick steps";
1829 JpGraphError::RaiseL(25026);
1832 // Bail out if no plots and no specified X-scale
1833 if( (!$this->xscale->IsSpecified() && count($this->plots)==0 && count($this->y2plots)==0) )
1834 JpGraphError::RaiseL(25034);//("<strong>JpGraph: Can't draw unspecified X-scale.</strong><br>No plots.<br>");
1836 //Check if we should autoscale y-axis
1837 if( !$this->yscale->IsSpecified() && count($this->plots)>0 ) {
1838 list($min,$max) = $this->GetPlotsYMinMax($this->plots);
1839 $lres = $this->GetLinesYMinMax($this->lines);
1840 if( is_array($lres) ) {
1841 list($linmin,$linmax) = $lres ;
1842 $min = min($min,$linmin);
1843 $max = max($max,$linmax);
1845 $tres = $this->GetTextsYMinMax();
1846 if( is_array($tres) ) {
1847 list($tmin,$tmax) = $tres ;
1848 $min = min($min,$tmin);
1849 $max = max($max,$tmax);
1851 $this->yscale->AutoScale($this->img,$min,$max,
1852 $this->img->plotheight/$this->ytick_factor);
1854 elseif( $this->yscale->IsSpecified() &&
1855 ( $this->yscale->auto_ticks || !$this->yscale->ticks->IsSpecified()) ) {
1856 // The tick calculation will use the user suplied min/max values to determine
1857 // the ticks. If auto_ticks is false the exact user specifed min and max
1858 // values will be used for the scale.
1859 // If auto_ticks is true then the scale might be slightly adjusted
1860 // so that the min and max values falls on an even major step.
1861 $min = $this->yscale->scale[0];
1862 $max = $this->yscale->scale[1];
1863 $this->yscale->AutoScale($this->img,$min,$max,
1864 $this->img->plotheight/$this->ytick_factor,
1865 $this->yscale->auto_ticks);
1868 if( $this->y2scale != null) {
1870 if( !$this->y2scale->IsSpecified() && count($this->y2plots)>0 ) {
1871 list($min,$max) = $this->GetPlotsYMinMax($this->y2plots);
1872 $lres = $this->GetLinesYMinMax($this->y2lines);
1873 if( is_array($lres) ) {
1874 list($linmin,$linmax) = $lres ;
1875 $min = min($min,$linmin);
1876 $max = max($max,$linmax);
1878 $tres = $this->GetTextsYMinMax(true);
1879 if( is_array($tres) ) {
1880 list($tmin,$tmax) = $tres ;
1881 $min = min($min,$tmin);
1882 $max = max($max,$tmax);
1884 $this->y2scale->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor);
1886 elseif( $this->y2scale->IsSpecified() &&
1887 ( $this->y2scale->auto_ticks || !$this->y2scale->ticks->IsSpecified()) ) {
1888 // The tick calculation will use the user suplied min/max values to determine
1889 // the ticks. If auto_ticks is false the exact user specifed min and max
1890 // values will be used for the scale.
1891 // If auto_ticks is true then the scale might be slightly adjusted
1892 // so that the min and max values falls on an even major step.
1893 $min = $this->y2scale->scale[0];
1894 $max = $this->y2scale->scale[1];
1895 $this->y2scale->AutoScale($this->img,$min,$max,
1896 $this->img->plotheight/$this->ytick_factor,
1897 $this->y2scale->auto_ticks);
1902 // Autoscale the multiple Y-axis
1904 $n = count($this->ynaxis);
1905 for( $i=0; $i < $n; ++$i ) {
1906 if( $this->ynscale[$i] != null) {
1907 if( !$this->ynscale[$i]->IsSpecified() && count($this->ynplots[$i])>0 ) {
1908 list($min,$max) = $this->GetPlotsYMinMax($this->ynplots[$i]);
1909 $this->ynscale[$i]->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor);
1911 elseif( $this->ynscale[$i]->IsSpecified() &&
1912 ( $this->ynscale[$i]->auto_ticks || !$this->ynscale[$i]->ticks->IsSpecified()) ) {
1913 // The tick calculation will use the user suplied min/max values to determine
1914 // the ticks. If auto_ticks is false the exact user specifed min and max
1915 // values will be used for the scale.
1916 // If auto_ticks is true then the scale might be slightly adjusted
1917 // so that the min and max values falls on an even major step.
1918 $min = $this->ynscale[$i]->scale[0];
1919 $max = $this->ynscale[$i]->scale[1];
1920 $this->ynscale[$i]->AutoScale($this->img,$min,$max,
1921 $this->img->plotheight/$this->ytick_factor,
1922 $this->ynscale[$i]->auto_ticks);
1928 //Check if we should autoscale x-axis
1929 if( !$this->xscale->IsSpecified() ) {
1930 if( substr($this->axtype,0,4) == "text" ) {
1931 $max=0;
1932 $n = count($this->plots);
1933 for($i=0; $i < $n; ++$i ) {
1934 $p = $this->plots[$i];
1935 // We need some unfortunate sub class knowledge here in order
1936 // to increase number of data points in case it is a line plot
1937 // which has the barcenter set. If not it could mean that the
1938 // last point of the data is outside the scale since the barcenter
1939 // settings means that we will shift the entire plot half a tick step
1940 // to the right in oder to align with the center of the bars.
1941 if( is_a($p,'BarPlot') || empty($p->barcenter)) {
1942 $max=max($max,$p->numpoints-1);
1944 else {
1945 $max=max($max,$p->numpoints);
1948 $min=0;
1949 if( $this->y2axis != null ) {
1950 foreach( $this->y2plots as $p ) {
1951 $max=max($max,$p->numpoints-1);
1954 $n = count($this->ynaxis);
1955 for( $i=0; $i < $n; ++$i ) {
1956 if( $this->ynaxis[$i] != null) {
1957 foreach( $this->ynplots[$i] as $p ) {
1958 $max=max($max,$p->numpoints-1);
1963 $this->xscale->Update($this->img,$min,$max);
1964 $this->xscale->ticks->Set($this->xaxis->tick_step,1);
1965 $this->xscale->ticks->SupressMinorTickMarks();
1967 else {
1968 list($min,$max) = $this->GetXMinMax();
1969 $lres = $this->GetLinesXMinMax($this->lines);
1970 if( $lres ) {
1971 list($linmin,$linmax) = $lres ;
1972 $min = min($min,$linmin);
1973 $max = max($max,$linmax);
1975 $lres = $this->GetLinesXMinMax($this->y2lines);
1976 if( $lres ) {
1977 list($linmin,$linmax) = $lres ;
1978 $min = min($min,$linmin);
1979 $max = max($max,$linmax);
1982 $tres = $this->GetTextsXMinMax();
1983 if( $tres ) {
1984 list($tmin,$tmax) = $tres ;
1985 $min = min($min,$tmin);
1986 $max = max($max,$tmax);
1989 $tres = $this->GetTextsXMinMax(true);
1990 if( $tres ) {
1991 list($tmin,$tmax) = $tres ;
1992 $min = min($min,$tmin);
1993 $max = max($max,$tmax);
1996 $this->xscale->AutoScale($this->img,$min,$max,round($this->img->plotwidth/$this->xtick_factor));
1999 //Adjust position of y-axis and y2-axis to minimum/maximum of x-scale
2000 if( !is_numeric($this->yaxis->pos) && !is_string($this->yaxis->pos) )
2001 $this->yaxis->SetPos($this->xscale->GetMinVal());
2002 if( $this->y2axis != null ) {
2003 if( !is_numeric($this->y2axis->pos) && !is_string($this->y2axis->pos) )
2004 $this->y2axis->SetPos($this->xscale->GetMaxVal());
2005 $this->y2axis->SetTitleSide(SIDE_RIGHT);
2008 $n = count($this->ynaxis);
2009 $nY2adj = $this->y2axis != null ? $this->iYAxisDeltaPos : 0;
2010 for( $i=0; $i < $n; ++$i ) {
2011 if( $this->ynaxis[$i] != null ) {
2012 if( !is_numeric($this->ynaxis[$i]->pos) && !is_string($this->ynaxis[$i]->pos) ) {
2013 $this->ynaxis[$i]->SetPos($this->xscale->GetMaxVal());
2014 $this->ynaxis[$i]->SetPosAbsDelta($i*$this->iYAxisDeltaPos + $nY2adj);
2016 $this->ynaxis[$i]->SetTitleSide(SIDE_RIGHT);
2020 elseif( $this->xscale->IsSpecified() &&
2021 ( $this->xscale->auto_ticks || !$this->xscale->ticks->IsSpecified()) ) {
2022 // The tick calculation will use the user suplied min/max values to determine
2023 // the ticks. If auto_ticks is false the exact user specifed min and max
2024 // values will be used for the scale.
2025 // If auto_ticks is true then the scale might be slightly adjusted
2026 // so that the min and max values falls on an even major step.
2027 $min = $this->xscale->scale[0];
2028 $max = $this->xscale->scale[1];
2031 $this->xscale->AutoScale($this->img,$min,$max,
2032 $this->img->plotwidth/$this->xtick_factor,
2033 false);
2035 if( $this->y2axis != null ) {
2036 if( !is_numeric($this->y2axis->pos) && !is_string($this->y2axis->pos) )
2037 $this->y2axis->SetPos($this->xscale->GetMaxVal());
2038 $this->y2axis->SetTitleSide(SIDE_RIGHT);
2043 // If we have a negative values and x-axis position is at 0
2044 // we need to supress the first and possible the last tick since
2045 // they will be drawn on top of the y-axis (and possible y2 axis)
2046 // The test below might seem strange the reasone being that if
2047 // the user hasn't specified a value for position this will not
2048 // be set until we do the stroke for the axis so as of now it
2049 // is undefined.
2050 // For X-text scale we ignore all this since the tick are usually
2051 // much further in and not close to the Y-axis. Hence the test
2052 // for 'text'
2054 if( ($this->yaxis->pos==$this->xscale->GetMinVal() ||
2055 (is_string($this->yaxis->pos) && $this->yaxis->pos=='min')) &&
2056 !is_numeric($this->xaxis->pos) && $this->yscale->GetMinVal() < 0 &&
2057 substr($this->axtype,0,4) != 'text' && $this->xaxis->pos!="min" ) {
2059 //$this->yscale->ticks->SupressZeroLabel(false);
2060 $this->xscale->ticks->SupressFirst();
2061 if( $this->y2axis != null ) {
2062 $this->xscale->ticks->SupressLast();
2065 elseif( !is_numeric($this->yaxis->pos) && $this->yaxis->pos=='max' ) {
2066 $this->xscale->ticks->SupressLast();
2070 if( !$_csim ) {
2071 $this->StrokePlotArea();
2072 if( $this->iIconDepth == DEPTH_BACK ) {
2073 $this->StrokeIcons();
2076 $this->StrokeAxis();
2078 // Stroke bands
2079 if( $this->bands != null && !$_csim)
2080 for($i=0; $i < count($this->bands); ++$i) {
2081 // Stroke all bands that asks to be in the background
2082 if( $this->bands[$i]->depth == DEPTH_BACK )
2083 $this->bands[$i]->Stroke($this->img,$this->xscale,$this->yscale);
2086 if( $this->y2bands != null && $this->y2scale != null && !$_csim )
2087 for($i=0; $i < count($this->y2bands); ++$i) {
2088 // Stroke all bands that asks to be in the foreground
2089 if( $this->y2bands[$i]->depth == DEPTH_BACK )
2090 $this->y2bands[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
2094 if( $this->grid_depth == DEPTH_BACK && !$_csim) {
2095 $this->ygrid->Stroke();
2096 $this->xgrid->Stroke();
2099 // Stroke Y2-axis
2100 if( $this->y2axis != null && !$_csim) {
2101 $this->y2axis->Stroke($this->xscale);
2102 $this->y2grid->Stroke();
2105 // Stroke yn-axis
2106 $n = count($this->ynaxis);
2107 for( $i=0; $i < $n; ++$i ) {
2108 $this->ynaxis[$i]->Stroke($this->xscale);
2111 $oldoff=$this->xscale->off;
2112 if(substr($this->axtype,0,4)=="text") {
2113 if( $this->text_scale_abscenteroff > -1 ) {
2114 // For a text scale the scale factor is the number of pixel per step.
2115 // Hence we can use the scale factor as a substitute for number of pixels
2116 // per major scale step and use that in order to adjust the offset so that
2117 // an object of width "abscenteroff" becomes centered.
2118 $this->xscale->off += round($this->xscale->scale_factor/2)-round($this->text_scale_abscenteroff/2);
2120 else {
2121 $this->xscale->off +=
2122 ceil($this->xscale->scale_factor*$this->text_scale_off*$this->xscale->ticks->minor_step);
2126 if( $this->iDoClipping ) {
2127 $oldimage = $this->img->CloneCanvasH();
2130 if( ! $this->y2orderback ) {
2131 // Stroke all plots for Y axis
2132 for($i=0; $i < count($this->plots); ++$i) {
2133 $this->plots[$i]->Stroke($this->img,$this->xscale,$this->yscale);
2134 $this->plots[$i]->StrokeMargin($this->img);
2138 // Stroke all plots for Y2 axis
2139 if( $this->y2scale != null )
2140 for($i=0; $i< count($this->y2plots); ++$i ) {
2141 $this->y2plots[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
2144 if( $this->y2orderback ) {
2145 // Stroke all plots for Y1 axis
2146 for($i=0; $i < count($this->plots); ++$i) {
2147 $this->plots[$i]->Stroke($this->img,$this->xscale,$this->yscale);
2148 $this->plots[$i]->StrokeMargin($this->img);
2152 $n = count($this->ynaxis);
2153 for( $i=0; $i < $n; ++$i ) {
2154 $m = count($this->ynplots[$i]);
2155 for( $j=0; $j < $m; ++$j ) {
2156 $this->ynplots[$i][$j]->Stroke($this->img,$this->xscale,$this->ynscale[$i]);
2157 $this->ynplots[$i][$j]->StrokeMargin($this->img);
2161 if( $this->iIconDepth == DEPTH_FRONT) {
2162 $this->StrokeIcons();
2165 if( $this->iDoClipping ) {
2166 // Clipping only supports graphs at 0 and 90 degrees
2167 if( $this->img->a == 0 ) {
2168 $this->img->CopyCanvasH($oldimage,$this->img->img,
2169 $this->img->left_margin,$this->img->top_margin,
2170 $this->img->left_margin,$this->img->top_margin,
2171 $this->img->plotwidth+1,$this->img->plotheight);
2173 elseif( $this->img->a == 90 ) {
2174 $adj = ($this->img->height - $this->img->width)/2;
2175 $this->img->CopyCanvasH($oldimage,$this->img->img,
2176 $this->img->bottom_margin-$adj,$this->img->left_margin+$adj,
2177 $this->img->bottom_margin-$adj,$this->img->left_margin+$adj,
2178 $this->img->plotheight+1,$this->img->plotwidth);
2180 else {
2181 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.');
2183 $this->img->Destroy();
2184 $this->img->SetCanvasH($oldimage);
2187 $this->xscale->off=$oldoff;
2189 if( $this->grid_depth == DEPTH_FRONT && !$_csim ) {
2190 $this->ygrid->Stroke();
2191 $this->xgrid->Stroke();
2194 // Stroke bands
2195 if( $this->bands!= null )
2196 for($i=0; $i < count($this->bands); ++$i) {
2197 // Stroke all bands that asks to be in the foreground
2198 if( $this->bands[$i]->depth == DEPTH_FRONT )
2199 $this->bands[$i]->Stroke($this->img,$this->xscale,$this->yscale);
2202 if( $this->y2bands!= null && $this->y2scale != null )
2203 for($i=0; $i < count($this->y2bands); ++$i) {
2204 // Stroke all bands that asks to be in the foreground
2205 if( $this->y2bands[$i]->depth == DEPTH_FRONT )
2206 $this->y2bands[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
2210 // Stroke any lines added
2211 if( $this->lines != null ) {
2212 for($i=0; $i < count($this->lines); ++$i) {
2213 $this->lines[$i]->Stroke($this->img,$this->xscale,$this->yscale);
2217 if( $this->y2lines != null && $this->y2scale != null ) {
2218 for($i=0; $i < count($this->y2lines); ++$i) {
2219 $this->y2lines[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
2223 // Finally draw the axis again since some plots may have nagged
2224 // the axis in the edges.However we do no stroke the labels again
2225 // since any user defined callback would be called twice. It also
2226 // enhances performance.
2228 if( !$_csim )
2229 $this->StrokeAxis(false);
2231 if( $this->y2scale != null && !$_csim )
2232 $this->y2axis->Stroke($this->xscale,false);
2234 if( !$_csim ) {
2235 $this->StrokePlotBox();
2238 // The titles and legends never gets rotated so make sure
2239 // that the angle is 0 before stroking them
2240 $aa = $this->img->SetAngle(0);
2241 $this->StrokeTitles();
2242 $this->footer->Stroke($this->img);
2243 $this->legend->Stroke($this->img);
2244 $this->img->SetAngle($aa);
2245 $this->StrokeTexts();
2246 $this->StrokeTables();
2248 if( !$_csim ) {
2250 $this->img->SetAngle($aa);
2252 // Draw an outline around the image map
2253 if(_JPG_DEBUG) {
2254 $this->DisplayClientSideaImageMapAreas();
2257 // Adjust the appearance of the image
2258 $this->AdjustSaturationBrightnessContrast();
2260 // Should we do any final image transformation
2261 if( $this->iImgTrans ) {
2262 if( !class_exists('ImgTrans') ) {
2263 require_once('jpgraph_imgtrans.php');
2264 //JpGraphError::Raise('In order to use image transformation you must include the file jpgraph_imgtrans.php in your script.');
2267 $tform = new ImgTrans($this->img->img);
2268 $this->img->img = $tform->Skew3D($this->iImgTransHorizon,$this->iImgTransSkewDist,
2269 $this->iImgTransDirection,$this->iImgTransHighQ,
2270 $this->iImgTransMinSize,$this->iImgTransFillColor,
2271 $this->iImgTransBorder);
2274 // If the filename is given as the special "__handle"
2275 // then the image handler is returned and the image is NOT
2276 // streamed back
2277 if( $aStrokeFileName == _IMG_HANDLER ) {
2278 return $this->img->img;
2280 else {
2281 // Finally stream the generated picture
2282 $this->cache->PutAndStream($this->img,$this->cache_name,$this->inline,$aStrokeFileName);
2287 function SetAxisLabelBackground($aType,$aXFColor='lightgray',$aXColor='black',$aYFColor='lightgray',$aYColor='black') {
2288 $this->iAxisLblBgType = $aType;
2289 $this->iXAxisLblBgFillColor = $aXFColor;
2290 $this->iXAxisLblBgColor = $aXColor;
2291 $this->iYAxisLblBgFillColor = $aYFColor;
2292 $this->iYAxisLblBgColor = $aYColor;
2295 //---------------
2296 // PRIVATE METHODS
2298 function StrokeAxisLabelBackground() {
2299 // Types
2300 // 0 = No background
2301 // 1 = Only X-labels, length of axis
2302 // 2 = Only Y-labels, length of axis
2303 // 3 = As 1 but extends to width of graph
2304 // 4 = As 2 but extends to height of graph
2305 // 5 = Combination of 3 & 4
2306 // 6 = Combination of 1 & 2
2308 $t = $this->iAxisLblBgType ;
2309 if( $t < 1 ) return;
2310 // Stroke optional X-axis label background color
2311 if( $t == 1 || $t == 3 || $t == 5 || $t == 6 ) {
2312 $this->img->PushColor($this->iXAxisLblBgFillColor);
2313 if( $t == 1 || $t == 6 ) {
2314 $xl = $this->img->left_margin;
2315 $yu = $this->img->height - $this->img->bottom_margin + 1;
2316 $xr = $this->img->width - $this->img->right_margin ;
2317 $yl = $this->img->height-1-$this->frame_weight;
2319 else { // t==3 || t==5
2320 $xl = $this->frame_weight;
2321 $yu = $this->img->height - $this->img->bottom_margin + 1;
2322 $xr = $this->img->width - 1 - $this->frame_weight;
2323 $yl = $this->img->height-1-$this->frame_weight;
2326 $this->img->FilledRectangle($xl,$yu,$xr,$yl);
2327 $this->img->PopColor();
2329 // Check if we should add the vertical lines at left and right edge
2330 if( $this->iXAxisLblBgColor !== '' ) {
2331 $this->img->PushColor($this->iXAxisLblBgColor);
2332 if( $t == 1 || $t == 6 ) {
2333 $this->img->Line($xl,$yu,$xl,$yl);
2334 $this->img->Line($xr,$yu,$xr,$yl);
2336 else {
2337 $xl = $this->img->width - $this->img->right_margin ;
2338 $this->img->Line($xl,$yu-1,$xr,$yu-1);
2340 $this->img->PopColor();
2344 if( $t == 2 || $t == 4 || $t == 5 || $t == 6 ) {
2345 $this->img->PushColor($this->iYAxisLblBgFillColor);
2346 if( $t == 2 || $t == 6 ) {
2347 $xl = $this->frame_weight;
2348 $yu = $this->frame_weight+$this->img->top_margin;
2349 $xr = $this->img->left_margin - 1;
2350 $yl = $this->img->height - $this->img->bottom_margin + 1;
2352 else {
2353 $xl = $this->frame_weight;
2354 $yu = $this->frame_weight;
2355 $xr = $this->img->left_margin - 1;
2356 $yl = $this->img->height-1-$this->frame_weight;
2359 $this->img->FilledRectangle($xl,$yu,$xr,$yl);
2360 $this->img->PopColor();
2362 // Check if we should add the vertical lines at left and right edge
2363 if( $this->iXAxisLblBgColor !== '' ) {
2364 $this->img->PushColor($this->iXAxisLblBgColor);
2365 if( $t == 2 || $t == 6 ) {
2366 $this->img->Line($xl,$yu-1,$xr,$yu-1);
2367 $this->img->Line($xl,$yl-1,$xr,$yl-1);
2369 else {
2370 $this->img->Line($xr+1,$yu,$xr+1,$this->img->top_margin);
2372 $this->img->PopColor();
2378 function StrokeAxis($aStrokeLabels=true) {
2380 if( $aStrokeLabels ) {
2381 $this->StrokeAxisLabelBackground();
2384 // Stroke axis
2385 if( $this->iAxisStyle != AXSTYLE_SIMPLE ) {
2386 switch( $this->iAxisStyle ) {
2387 case AXSTYLE_BOXIN :
2388 $toppos = SIDE_DOWN;
2389 $bottompos = SIDE_UP;
2390 $leftpos = SIDE_RIGHT;
2391 $rightpos = SIDE_LEFT;
2392 break;
2393 case AXSTYLE_BOXOUT :
2394 $toppos = SIDE_UP;
2395 $bottompos = SIDE_DOWN;
2396 $leftpos = SIDE_LEFT;
2397 $rightpos = SIDE_RIGHT;
2398 break;
2399 case AXSTYLE_YBOXIN:
2400 $toppos = -100;
2401 $bottompos = SIDE_UP;
2402 $leftpos = SIDE_RIGHT;
2403 $rightpos = SIDE_LEFT;
2404 break;
2405 case AXSTYLE_YBOXOUT:
2406 $toppos = -100;
2407 $bottompos = SIDE_DOWN;
2408 $leftpos = SIDE_LEFT;
2409 $rightpos = SIDE_RIGHT;
2410 break;
2411 default:
2412 JpGRaphError::RaiseL(25036,$this->iAxisStyle); //('Unknown AxisStyle() : '.$this->iAxisStyle);
2413 break;
2415 $this->xaxis->SetPos('min');
2417 // By default we hide the first label so it doesn't cross the
2418 // Y-axis in case the positon hasn't been set by the user.
2419 // However, if we use a box we always want the first value
2420 // displayed so we make sure it will be displayed.
2421 $this->xscale->ticks->SupressFirst(false);
2423 $this->xaxis->SetLabelSide(SIDE_DOWN);
2424 $this->xaxis->scale->ticks->SetSide($bottompos);
2425 $this->xaxis->Stroke($this->yscale);
2427 if( $toppos != -100 ) {
2428 // To avoid side effects we work on a new copy
2429 $maxis = $this->xaxis;
2430 $maxis->SetPos('max');
2431 $maxis->SetLabelSide(SIDE_UP);
2432 $maxis->SetLabelMargin(7);
2433 $this->xaxis->scale->ticks->SetSide($toppos);
2434 $maxis->Stroke($this->yscale);
2437 $this->yaxis->SetPos('min');
2438 $this->yaxis->SetLabelMargin(10);
2439 $this->yaxis->SetLabelSide(SIDE_LEFT);
2440 $this->yaxis->scale->ticks->SetSide($leftpos);
2441 $this->yaxis->Stroke($this->xscale);
2443 $myaxis = $this->yaxis;
2444 $myaxis->SetPos('max');
2445 $myaxis->SetLabelMargin(10);
2446 $myaxis->SetLabelSide(SIDE_RIGHT);
2447 $myaxis->title->Set('');
2448 $myaxis->scale->ticks->SetSide($rightpos);
2449 $myaxis->Stroke($this->xscale);
2452 else {
2453 $this->xaxis->Stroke($this->yscale,$aStrokeLabels);
2454 $this->yaxis->Stroke($this->xscale,$aStrokeLabels);
2459 // Private helper function for backgound image
2460 function LoadBkgImage($aImgFormat='',$aFile='',$aImgStr='') {
2461 if( $aImgStr != '' ) {
2462 return Image::CreateFromString($aImgStr);
2464 if( $aFile == '' )
2465 $aFile = $this->background_image;
2466 // Remove case sensitivity and setup appropriate function to create image
2467 // Get file extension. This should be the LAST '.' separated part of the filename
2468 $e = explode('.',$aFile);
2469 $ext = strtolower($e[count($e)-1]);
2470 if ($ext == "jpeg") {
2471 $ext = "jpg";
2474 if( trim($ext) == '' )
2475 $ext = 'png'; // Assume PNG if no extension specified
2477 if( $aImgFormat == '' )
2478 $imgtag = $ext;
2479 else
2480 $imgtag = $aImgFormat;
2482 $supported = imagetypes();
2483 if( ( $ext == 'jpg' && !($supported & IMG_JPG) ) ||
2484 ( $ext == 'gif' && !($supported & IMG_GIF) ) ||
2485 ( $ext == 'png' && !($supported & IMG_PNG) ) ) {
2486 JpGraphError::RaiseL(25037,$aFile);//('The image format of your background image ('.$aFile.') is not supported in your system configuration. ');
2490 if( $imgtag == "jpg" || $imgtag == "jpeg")
2492 $f = "imagecreatefromjpeg";
2493 $imgtag = "jpg";
2495 else
2497 $f = "imagecreatefrom".$imgtag;
2500 // Compare specified image type and file extension
2501 if( $imgtag != $ext ) {
2502 //$t = "Background image seems to be of different type (has different file extension) than specified imagetype. Specified: '".$aImgFormat."'File: '".$aFile."'";
2503 JpGraphError::RaiseL(25038, $aImgFormat, $aFile);
2506 $img = @$f($aFile);
2507 if( !$img ) {
2508 JpGraphError::RaiseL(25039,$aFile);//(" Can't read background image: '".$aFile."'");
2510 return $img;
2513 function StrokeBackgroundGrad() {
2514 if( $this->bkg_gradtype < 0 )
2515 return;
2516 $grad = new Gradient($this->img);
2517 if( $this->bkg_gradstyle == BGRAD_PLOT ) {
2518 $xl = $this->img->left_margin;
2519 $yt = $this->img->top_margin;
2520 $xr = $xl + $this->img->plotwidth+1 ;
2521 $yb = $yt + $this->img->plotheight ;
2522 $grad->FilledRectangle($xl,$yt,$xr,$yb,$this->bkg_gradfrom,$this->bkg_gradto,$this->bkg_gradtype);
2524 else {
2525 $xl = 0;
2526 $yt = 0;
2527 $xr = $xl + $this->img->width - 1;
2528 $yb = $yt + $this->img->height;
2529 if( $this->doshadow ) {
2530 $xr -= $this->shadow_width;
2531 $yb -= $this->shadow_width;
2533 if( $this->doframe ) {
2534 $yt += $this->frame_weight;
2535 $yb -= $this->frame_weight;
2536 $xl += $this->frame_weight;
2537 $xr -= $this->frame_weight;
2539 $aa = $this->img->SetAngle(0);
2540 $grad->FilledRectangle($xl,$yt,$xr,$yb,$this->bkg_gradfrom,$this->bkg_gradto,$this->bkg_gradtype);
2541 $aa = $this->img->SetAngle($aa);
2545 function StrokeFrameBackground() {
2546 if( $this->background_image != "" && $this->background_cflag != "" ) {
2547 JpGraphError::RaiseL(25040);//('It is not possible to specify both a background image and a background country flag.');
2549 if( $this->background_image != "" ) {
2550 $bkgimg = $this->LoadBkgImage($this->background_image_format);
2551 $this->img->_AdjBrightContrast($bkgimg,$this->background_image_bright,
2552 $this->background_image_contr);
2553 $this->img->_AdjSat($bkgimg,$this->background_image_sat);
2555 elseif( $this->background_cflag != "" ) {
2556 if( ! class_exists('FlagImages') ) {
2557 JpGraphError::RaiseL(25041);//('In order to use Country flags as backgrounds you must include the "jpgraph_flags.php" file.');
2559 $fobj = new FlagImages(FLAGSIZE4);
2560 $dummy='';
2561 $bkgimg = $fobj->GetImgByName($this->background_cflag,$dummy);
2562 $this->background_image_mix = $this->background_cflag_mix;
2563 $this->background_image_type = $this->background_cflag_type;
2565 else {
2566 return ;
2569 $bw = ImageSX($bkgimg);
2570 $bh = ImageSY($bkgimg);
2572 // No matter what the angle is we always stroke the image and frame
2573 // assuming it is 0 degree
2574 $aa = $this->img->SetAngle(0);
2576 switch( $this->background_image_type ) {
2577 case BGIMG_FILLPLOT: // Resize to just fill the plotarea
2578 $this->FillMarginArea();
2579 $this->StrokeFrame();
2580 $this->FillPlotArea();
2581 $this->img->CopyMerge($bkgimg,
2582 $this->img->left_margin,$this->img->top_margin,
2583 0,0,$this->img->plotwidth+1,$this->img->plotheight,
2584 $bw,$bh,$this->background_image_mix);
2585 break;
2586 case BGIMG_FILLFRAME: // Fill the whole area from upper left corner, resize to just fit
2587 $hadj=0; $vadj=0;
2588 if( $this->doshadow ) {
2589 $hadj = $this->shadow_width;
2590 $vadj = $this->shadow_width;
2592 $this->FillMarginArea();
2593 $this->FillPlotArea();
2594 $this->img->CopyMerge($bkgimg,0,0,0,0,$this->img->width-$hadj,$this->img->height-$vadj,
2595 $bw,$bh,$this->background_image_mix);
2596 $this->StrokeFrame();
2597 break;
2598 case BGIMG_COPY: // Just copy the image from left corner, no resizing
2599 $this->FillMarginArea();
2600 $this->FillPlotArea();
2601 $this->img->CopyMerge($bkgimg,0,0,0,0,$bw,$bh,
2602 $bw,$bh,$this->background_image_mix);
2603 $this->StrokeFrame();
2604 break;
2605 case BGIMG_CENTER: // Center original image in the plot area
2606 $this->FillMarginArea();
2607 $this->FillPlotArea();
2608 $centerx = round($this->img->plotwidth/2+$this->img->left_margin-$bw/2);
2609 $centery = round($this->img->plotheight/2+$this->img->top_margin-$bh/2);
2610 $this->img->CopyMerge($bkgimg,$centerx,$centery,0,0,$bw,$bh,
2611 $bw,$bh,$this->background_image_mix);
2612 $this->StrokeFrame();
2613 break;
2614 default:
2615 JpGraphError::RaiseL(25042);//(" Unknown background image layout");
2617 $this->img->SetAngle($aa);
2620 // Private
2621 // Draw a frame around the image
2622 function StrokeFrame() {
2623 if( !$this->doframe ) return;
2625 if( $this->background_image_type <= 1 &&
2626 ($this->bkg_gradtype < 0 || ($this->bkg_gradtype > 0 && $this->bkg_gradstyle==BGRAD_PLOT)) ) {
2627 $c = $this->margin_color;
2629 else {
2630 $c = false;
2633 if( $this->doshadow ) {
2634 $this->img->SetColor($this->frame_color);
2635 $this->img->ShadowRectangle(0,0,$this->img->width,$this->img->height,
2636 $c,$this->shadow_width,$this->shadow_color);
2638 elseif( $this->framebevel ) {
2639 if( $c ) {
2640 $this->img->SetColor($this->margin_color);
2641 $this->img->FilledRectangle(0,0,$this->img->width-1,$this->img->height-1);
2643 $this->img->Bevel(1,1,$this->img->width-2,$this->img->height-2,
2644 $this->framebeveldepth,
2645 $this->framebevelcolor1,$this->framebevelcolor2);
2646 if( $this->framebevelborder ) {
2647 $this->img->SetColor($this->framebevelbordercolor);
2648 $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);
2651 else {
2652 $this->img->SetLineWeight($this->frame_weight);
2653 if( $c ) {
2654 $this->img->SetColor($this->margin_color);
2655 $this->img->FilledRectangle(0,0,$this->img->width-1,$this->img->height-1);
2657 $this->img->SetColor($this->frame_color);
2658 $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);
2662 function FillMarginArea() {
2663 $hadj=0; $vadj=0;
2664 if( $this->doshadow ) {
2665 $hadj = $this->shadow_width;
2666 $vadj = $this->shadow_width;
2669 $this->img->SetColor($this->margin_color);
2670 // $this->img->FilledRectangle(0,0,$this->img->width-1-$hadj,$this->img->height-1-$vadj);
2672 $this->img->FilledRectangle(0,0,$this->img->width-1-$hadj,$this->img->top_margin);
2673 $this->img->FilledRectangle(0,$this->img->top_margin,$this->img->left_margin,$this->img->height-1-$hadj);
2674 $this->img->FilledRectangle($this->img->left_margin+1,
2675 $this->img->height-$this->img->bottom_margin,
2676 $this->img->width-1-$hadj,
2677 $this->img->height-1-$hadj);
2678 $this->img->FilledRectangle($this->img->width-$this->img->right_margin,
2679 $this->img->top_margin+1,
2680 $this->img->width-1-$hadj,
2681 $this->img->height-$this->img->bottom_margin-1);
2684 function FillPlotArea() {
2685 $this->img->PushColor($this->plotarea_color);
2686 $this->img->FilledRectangle($this->img->left_margin,
2687 $this->img->top_margin,
2688 $this->img->width-$this->img->right_margin,
2689 $this->img->height-$this->img->bottom_margin);
2690 $this->img->PopColor();
2693 // Stroke the plot area with either a solid color or a background image
2694 function StrokePlotArea() {
2695 // Note: To be consistent we really should take a possible shadow
2696 // into account. However, that causes some problem for the LinearScale class
2697 // since in the current design it does not have any links to class Graph which
2698 // means it has no way of compensating for the adjusted plotarea in case of a
2699 // shadow. So, until I redesign LinearScale we can't compensate for this.
2700 // So just set the two adjustment parameters to zero for now.
2701 $boxadj = 0; //$this->doframe ? $this->frame_weight : 0 ;
2702 $adj = 0; //$this->doshadow ? $this->shadow_width : 0 ;
2704 if( $this->background_image != "" || $this->background_cflag != "" ) {
2705 $this->StrokeFrameBackground();
2707 else {
2708 $aa = $this->img->SetAngle(0);
2709 $this->StrokeFrame();
2710 $aa = $this->img->SetAngle($aa);
2711 $this->StrokeBackgroundGrad();
2712 if( $this->bkg_gradtype < 0 ||
2713 ($this->bkg_gradtype > 0 && $this->bkg_gradstyle==BGRAD_MARGIN) ) {
2714 $this->FillPlotArea();
2719 function StrokeIcons() {
2720 $n = count($this->iIcons);
2721 for( $i=0; $i < $n; ++$i ) {
2722 $this->iIcons[$i]->StrokeWithScale($this->img,$this->xscale,$this->yscale);
2726 function StrokePlotBox() {
2727 // Should we draw a box around the plot area?
2728 if( $this->boxed ) {
2729 $this->img->SetLineWeight(1);
2730 $this->img->SetLineStyle('solid');
2731 $this->img->SetColor($this->box_color);
2732 for($i=0; $i < $this->box_weight; ++$i ) {
2733 $this->img->Rectangle(
2734 $this->img->left_margin-$i,$this->img->top_margin-$i,
2735 $this->img->width-$this->img->right_margin+$i,
2736 $this->img->height-$this->img->bottom_margin+$i);
2741 function SetTitleBackgroundFillStyle($aStyle,$aColor1='black',$aColor2='white') {
2742 $this->titlebkg_fillstyle = $aStyle;
2743 $this->titlebkg_scolor1 = $aColor1;
2744 $this->titlebkg_scolor2 = $aColor2;
2747 function SetTitleBackground($aBackColor='gray', $aStyle=TITLEBKG_STYLE1, $aFrameStyle=TITLEBKG_FRAME_NONE, $aFrameColor='black', $aFrameWeight=1, $aBevelHeight=3, $aEnable=true) {
2748 $this->titlebackground = $aEnable;
2749 $this->titlebackground_color = $aBackColor;
2750 $this->titlebackground_style = $aStyle;
2751 $this->titlebackground_framecolor = $aFrameColor;
2752 $this->titlebackground_framestyle = $aFrameStyle;
2753 $this->titlebackground_frameweight = $aFrameWeight;
2754 $this->titlebackground_bevelheight = $aBevelHeight ;
2758 function StrokeTitles() {
2760 $margin=3;
2762 if( $this->titlebackground ) {
2764 // Find out height
2765 $this->title->margin += 2 ;
2766 $h = $this->title->GetTextHeight($this->img)+$this->title->margin+$margin;
2767 if( $this->subtitle->t != "" && !$this->subtitle->hide ) {
2768 $h += $this->subtitle->GetTextHeight($this->img)+$margin+
2769 $this->subtitle->margin;
2770 $h += 2;
2772 if( $this->subsubtitle->t != "" && !$this->subsubtitle->hide ) {
2773 $h += $this->subsubtitle->GetTextHeight($this->img)+$margin+
2774 $this->subsubtitle->margin;
2775 $h += 2;
2777 $this->img->PushColor($this->titlebackground_color);
2778 if( $this->titlebackground_style === TITLEBKG_STYLE1 ) {
2779 // Inside the frame
2780 if( $this->framebevel ) {
2781 $x1 = $y1 = $this->framebeveldepth + 1 ;
2782 $x2 = $this->img->width - $this->framebeveldepth - 2 ;
2783 $this->title->margin += $this->framebeveldepth + 1 ;
2784 $h += $y1 ;
2785 $h += 2;
2787 else {
2788 $x1 = $y1 = $this->frame_weight;
2789 $x2 = $this->img->width - 2*$x1;
2792 elseif( $this->titlebackground_style === TITLEBKG_STYLE2 ) {
2793 // Cover the frame as well
2794 $x1 = $y1 = 0;
2795 $x2 = $this->img->width - 1 ;
2797 elseif( $this->titlebackground_style === TITLEBKG_STYLE3 ) {
2798 // Cover the frame as well (the difference is that
2799 // for style==3 a bevel frame border is on top
2800 // of the title background)
2801 $x1 = $y1 = 0;
2802 $x2 = $this->img->width - 1 ;
2803 $h += $this->framebeveldepth ;
2804 $this->title->margin += $this->framebeveldepth ;
2806 else {
2807 JpGraphError::RaiseL(25043);//('Unknown title background style.');
2810 if( $this->titlebackground_framestyle === 3 ) {
2811 $h += $this->titlebackground_bevelheight*2 + 1 ;
2812 $this->title->margin += $this->titlebackground_bevelheight ;
2815 if( $this->doshadow ) {
2816 $x2 -= $this->shadow_width ;
2819 $indent=0;
2820 if( $this->titlebackground_framestyle == TITLEBKG_FRAME_BEVEL ) {
2821 $ind = $this->titlebackground_bevelheight;
2824 if( $this->titlebkg_fillstyle==TITLEBKG_FILLSTYLE_HSTRIPED ) {
2825 $this->img->FilledRectangle2($x1+$ind,$y1+$ind,$x2-$ind,$h-$ind,
2826 $this->titlebkg_scolor1,
2827 $this->titlebkg_scolor2);
2829 elseif( $this->titlebkg_fillstyle==TITLEBKG_FILLSTYLE_VSTRIPED ) {
2830 $this->img->FilledRectangle2($x1+$ind,$y1+$ind,$x2-$ind,$h-$ind,
2831 $this->titlebkg_scolor1,
2832 $this->titlebkg_scolor2,2);
2834 else {
2835 // Solid fill
2836 $this->img->FilledRectangle($x1,$y1,$x2,$h);
2838 $this->img->PopColor();
2840 $this->img->PushColor($this->titlebackground_framecolor);
2841 $this->img->SetLineWeight($this->titlebackground_frameweight);
2842 if( $this->titlebackground_framestyle == TITLEBKG_FRAME_FULL ) {
2843 // Frame background
2844 $this->img->Rectangle($x1,$y1,$x2,$h);
2846 elseif( $this->titlebackground_framestyle == TITLEBKG_FRAME_BOTTOM ) {
2847 // Bottom line only
2848 $this->img->Line($x1,$h,$x2,$h);
2850 elseif( $this->titlebackground_framestyle == TITLEBKG_FRAME_BEVEL ) {
2851 $this->img->Bevel($x1,$y1,$x2,$h,$this->titlebackground_bevelheight);
2853 $this->img->PopColor();
2855 // This is clumsy. But we neeed to stroke the whole graph frame if it is
2856 // set to bevel to get the bevel shading on top of the text background
2857 if( $this->framebevel && $this->doframe &&
2858 $this->titlebackground_style === 3 ) {
2859 $this->img->Bevel(1,1,$this->img->width-2,$this->img->height-2,
2860 $this->framebeveldepth,
2861 $this->framebevelcolor1,$this->framebevelcolor2);
2862 if( $this->framebevelborder ) {
2863 $this->img->SetColor($this->framebevelbordercolor);
2864 $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);
2869 // Stroke title
2870 $y = $this->title->margin;
2871 if( $this->title->halign == 'center' )
2872 $this->title->Center(0,$this->img->width,$y);
2873 elseif( $this->title->halign == 'left' ) {
2874 $this->title->SetPos($this->title->margin+2,$y);
2876 elseif( $this->title->halign == 'right' ) {
2877 $indent = 0;
2878 if( $this->doshadow )
2879 $indent = $this->shadow_width+2;
2880 $this->title->SetPos($this->img->width-$this->title->margin-$indent,$y,'right');
2882 $this->title->Stroke($this->img);
2884 // ... and subtitle
2885 $y += $this->title->GetTextHeight($this->img) + $margin + $this->subtitle->margin;
2886 if( $this->subtitle->halign == 'center' )
2887 $this->subtitle->Center(0,$this->img->width,$y);
2888 elseif( $this->subtitle->halign == 'left' ) {
2889 $this->subtitle->SetPos($this->subtitle->margin+2,$y);
2891 elseif( $this->subtitle->halign == 'right' ) {
2892 $indent = 0;
2893 if( $this->doshadow )
2894 $indent = $this->shadow_width+2;
2895 $this->subtitle->SetPos($this->img->width-$this->subtitle->margin-$indent,$y,'right');
2897 $this->subtitle->Stroke($this->img);
2899 // ... and subsubtitle
2900 $y += $this->subtitle->GetTextHeight($this->img) + $margin + $this->subsubtitle->margin;
2901 if( $this->subsubtitle->halign == 'center' )
2902 $this->subsubtitle->Center(0,$this->img->width,$y);
2903 elseif( $this->subsubtitle->halign == 'left' ) {
2904 $this->subsubtitle->SetPos($this->subsubtitle->margin+2,$y);
2906 elseif( $this->subsubtitle->halign == 'right' ) {
2907 $indent = 0;
2908 if( $this->doshadow )
2909 $indent = $this->shadow_width+2;
2910 $this->subsubtitle->SetPos($this->img->width-$this->subsubtitle->margin-$indent,$y,'right');
2912 $this->subsubtitle->Stroke($this->img);
2914 // ... and fancy title
2915 $this->tabtitle->Stroke($this->img);
2919 function StrokeTexts() {
2920 // Stroke any user added text objects
2921 if( $this->texts != null ) {
2922 for($i=0; $i < count($this->texts); ++$i) {
2923 $this->texts[$i]->StrokeWithScale($this->img,$this->xscale,$this->yscale);
2927 if( $this->y2texts != null && $this->y2scale != null ) {
2928 for($i=0; $i < count($this->y2texts); ++$i) {
2929 $this->y2texts[$i]->StrokeWithScale($this->img,$this->xscale,$this->y2scale);
2935 function StrokeTables() {
2936 if( $this->iTables != null ) {
2937 $n = count($this->iTables);
2938 for( $i=0; $i < $n; ++$i ) {
2939 $this->iTables[$i]->StrokeWithScale($this->img,$this->xscale,$this->yscale);
2944 function DisplayClientSideaImageMapAreas() {
2945 // Debug stuff - display the outline of the image map areas
2946 $csim='';
2947 foreach ($this->plots as $p) {
2948 $csim.= $p->GetCSIMareas();
2950 $csim .= $this->legend->GetCSIMareas();
2951 if (preg_match_all("/area shape=\"(\w+)\" coords=\"([0-9\, ]+)\"/", $csim, $coords)) {
2952 $this->img->SetColor($this->csimcolor);
2953 $n = count($coords[0]);
2954 for ($i=0; $i < $n; $i++) {
2955 if ($coords[1][$i]=="poly") {
2956 preg_match_all('/\s*([0-9]+)\s*,\s*([0-9]+)\s*,*/',$coords[2][$i],$pts);
2957 $this->img->SetStartPoint($pts[1][count($pts[0])-1],$pts[2][count($pts[0])-1]);
2958 $m = count($pts[0]);
2959 for ($j=0; $j < $m; $j++) {
2960 $this->img->LineTo($pts[1][$j],$pts[2][$j]);
2962 } else if ($coords[1][$i]=="rect") {
2963 $pts = preg_split('/,/', $coords[2][$i]);
2964 $this->img->SetStartPoint($pts[0],$pts[1]);
2965 $this->img->LineTo($pts[2],$pts[1]);
2966 $this->img->LineTo($pts[2],$pts[3]);
2967 $this->img->LineTo($pts[0],$pts[3]);
2968 $this->img->LineTo($pts[0],$pts[1]);
2974 function AdjustSaturationBrightnessContrast() {
2975 // Adjust the brightness and contrast of the image
2976 if( $this->image_contr || $this->image_bright )
2977 $this->img->AdjBrightContrast($this->image_bright,$this->image_contr);
2978 if( $this->image_sat )
2979 $this->img->AdjSat($this->image_sat);
2982 // Text scale offset in fractions of a major scale step
2983 function SetTextScaleOff($aOff) {
2984 $this->text_scale_off = $aOff;
2985 $this->xscale->text_scale_off = $aOff;
2988 // Text width of bar to be centered in absolute pixels
2989 function SetTextScaleAbsCenterOff($aOff) {
2990 $this->text_scale_abscenteroff = $aOff;
2993 // Get Y min and max values for added lines
2994 function GetLinesYMinMax( $aLines ) {
2995 $n = count($aLines);
2996 if( $n == 0 ) return false;
2997 $min = $aLines[0]->scaleposition ;
2998 $max = $min ;
2999 $flg = false;
3000 for( $i=0; $i < $n; ++$i ) {
3001 if( $aLines[$i]->direction == HORIZONTAL ) {
3002 $flg = true ;
3003 $v = $aLines[$i]->scaleposition ;
3004 if( $min > $v ) $min = $v ;
3005 if( $max < $v ) $max = $v ;
3008 return $flg ? array($min,$max) : false ;
3011 // Get X min and max values for added lines
3012 function GetLinesXMinMax( $aLines ) {
3013 $n = count($aLines);
3014 if( $n == 0 ) return false ;
3015 $min = $aLines[0]->scaleposition ;
3016 $max = $min ;
3017 $flg = false;
3018 for( $i=0; $i < $n; ++$i ) {
3019 if( $aLines[$i]->direction == VERTICAL ) {
3020 $flg = true ;
3021 $v = $aLines[$i]->scaleposition ;
3022 if( $min > $v ) $min = $v ;
3023 if( $max < $v ) $max = $v ;
3026 return $flg ? array($min,$max) : false ;
3029 // Get min and max values for all included plots
3030 function GetPlotsYMinMax(&$aPlots) {
3031 $n = count($aPlots);
3032 $i=0;
3033 do {
3034 list($xmax,$max) = $aPlots[$i]->Max();
3035 } while( ++$i < $n && !is_numeric($max) );
3037 $i=0;
3038 do {
3039 list($xmin,$min) = $aPlots[$i]->Min();
3040 } while( ++$i < $n && !is_numeric($min) );
3042 if( !is_numeric($min) || !is_numeric($max) ) {
3043 JpGraphError::RaiseL(25044);//('Cannot use autoscaling since it is impossible to determine a valid min/max value of the Y-axis (only null values).');
3046 list($xmax,$max) = $aPlots[0]->Max();
3047 list($xmin,$min) = $aPlots[0]->Min();
3048 for($i=0; $i < count($aPlots); ++$i ) {
3049 list($xmax,$ymax)=$aPlots[$i]->Max();
3050 list($xmin,$ymin)=$aPlots[$i]->Min();
3051 if (is_numeric($ymax)) $max=max($max,$ymax);
3052 if (is_numeric($ymin)) $min=min($min,$ymin);
3054 if( $min == '' ) $min = 0;
3055 if( $max == '' ) $max = 0;
3056 if( $min == 0 && $max == 0 ) {
3057 // Special case if all values are 0
3058 $min=0;$max=1;
3060 return array($min,$max);
3063 } // Class
3066 //===================================================
3067 // CLASS TTF
3068 // Description: Handle TTF font names
3069 //===================================================
3070 class TTF {
3071 var $font_files,$style_names;
3072 //---------------
3073 // CONSTRUCTOR
3074 function __construct() {
3075 $this->style_names=array(FS_NORMAL=>'normal',FS_BOLD=>'bold',FS_ITALIC=>'italic',FS_BOLDITALIC=>'bolditalic');
3076 // File names for available fonts
3077 $this->font_files=array(
3078 FF_COURIER => array(FS_NORMAL=>'cour.ttf', FS_BOLD=>'courbd.ttf', FS_ITALIC=>'couri.ttf', FS_BOLDITALIC=>'courbi.ttf' ),
3079 FF_GEORGIA => array(FS_NORMAL=>'georgia.ttf', FS_BOLD=>'georgiab.ttf', FS_ITALIC=>'georgiai.ttf', FS_BOLDITALIC=>'' ),
3080 FF_TREBUCHE =>array(FS_NORMAL=>'trebuc.ttf', FS_BOLD=>'trebucbd.ttf', FS_ITALIC=>'trebucit.ttf', FS_BOLDITALIC=>'trebucbi.ttf' ),
3081 FF_VERDANA => array(FS_NORMAL=>'verdana.ttf', FS_BOLD=>'verdanab.ttf', FS_ITALIC=>'verdanai.ttf', FS_BOLDITALIC=>'' ),
3082 FF_TIMES => array(FS_NORMAL=>'times.ttf', FS_BOLD=>'timesbd.ttf', FS_ITALIC=>'timesi.ttf', FS_BOLDITALIC=>'timesbi.ttf' ),
3083 FF_COMIC => array(FS_NORMAL=>'comic.ttf', FS_BOLD=>'comicbd.ttf', FS_ITALIC=>'', FS_BOLDITALIC=>'' ),
3084 FF_ARIAL => array(FS_NORMAL=>'arial.ttf', FS_BOLD=>'arialbd.ttf', FS_ITALIC=>'ariali.ttf', FS_BOLDITALIC=>'arialbi.ttf' ) ,
3085 FF_VERA => array(FS_NORMAL=>'Vera.ttf', FS_BOLD=>'VeraBd.ttf', FS_ITALIC=>'VeraIt.ttf', FS_BOLDITALIC=>'VeraBI.ttf' ),
3086 FF_VERAMONO => array(FS_NORMAL=>'VeraMono.ttf', FS_BOLD=>'VeraMoBd.ttf', FS_ITALIC=>'VeraMoIt.ttf', FS_BOLDITALIC=>'VeraMoBI.ttf' ),
3087 FF_VERASERIF => array(FS_NORMAL=>'VeraSe.ttf', FS_BOLD=>'VeraSeBd.ttf', FS_ITALIC=>'', FS_BOLDITALIC=>'' ) ,
3088 FF_SIMSUN => array(FS_NORMAL=>'simsun.ttc', FS_BOLD=>'simhei.ttf', FS_ITALIC=>'', FS_BOLDITALIC=>'' ),
3089 FF_CHINESE => array(FS_NORMAL=>CHINESE_TTF_FONT, FS_BOLD=>'', FS_ITALIC=>'', FS_BOLDITALIC=>'' ),
3090 FF_MINCHO => array(FS_NORMAL=>MINCHO_TTF_FONT, FS_BOLD=>'', FS_ITALIC=>'', FS_BOLDITALIC=>'' ),
3091 FF_PMINCHO => array(FS_NORMAL=>PMINCHO_TTF_FONT, FS_BOLD=>'', FS_ITALIC=>'', FS_BOLDITALIC=>'' ),
3092 FF_GOTHIC => array(FS_NORMAL=>GOTHIC_TTF_FONT, FS_BOLD=>'', FS_ITALIC=>'', FS_BOLDITALIC=>'' ),
3093 FF_PGOTHIC => array(FS_NORMAL=>PGOTHIC_TTF_FONT, FS_BOLD=>'', FS_ITALIC=>'', FS_BOLDITALIC=>'' ),
3094 FF_MINCHO => array(FS_NORMAL=>PMINCHO_TTF_FONT, FS_BOLD=>'', FS_ITALIC=>'', FS_BOLDITALIC=>'' )
3098 //---------------
3099 // PUBLIC METHODS
3100 // Create the TTF file from the font specification
3101 function File($family,$style=FS_NORMAL) {
3103 if( $family == FF_HANDWRT || $family==FF_BOOK ) {
3104 JpGraphError::RaiseL(25045);//('Font families FF_HANDWRT and FF_BOOK are no longer available due to copyright problem with these fonts. Fonts can no longer be distributed with JpGraph. Please download fonts from http://corefonts.sourceforge.net/');
3107 $fam = @$this->font_files[$family];
3108 if( !$fam ) {
3109 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/");
3111 $f = @$fam[$style];
3113 if( $f==='' )
3114 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].'.');
3115 if( !$f ) {
3116 JpGraphError::RaiseL(25048,$fam);//("Unknown font style specification [$fam].");
3119 if ($family >= FF_MINCHO && $family <= FF_PGOTHIC) {
3120 $f = MBTTF_DIR.$f;
3121 } else {
3122 $f = TTF_DIR.$f;
3125 if( file_exists($f) === false || is_readable($f) === false ) {
3126 JpGraphError::RaiseL(25049,$f);//("Font file \"$f\" is not readable or does not exist.");
3128 return $f;
3130 } // Class
3132 //===================================================
3133 // CLASS LineProperty
3134 // Description: Holds properties for a line
3135 //===================================================
3136 class LineProperty {
3137 var $iWeight=1, $iColor="black",$iStyle="solid";
3138 var $iShow=true;
3140 //---------------
3141 // PUBLIC METHODS
3142 function SetColor($aColor) {
3143 $this->iColor = $aColor;
3146 function SetWeight($aWeight) {
3147 $this->iWeight = $aWeight;
3150 function SetStyle($aStyle) {
3151 $this->iStyle = $aStyle;
3154 function Show($aShow=true) {
3155 $this->iShow=$aShow;
3158 function Stroke(&$aImg,$aX1,$aY1,$aX2,$aY2) {
3159 if( $this->iShow ) {
3160 $aImg->PushColor($this->iColor);
3161 $oldls = $aImg->line_style;
3162 $oldlw = $aImg->line_weight;
3163 $aImg->SetLineWeight($this->iWeight);
3164 $aImg->SetLineStyle($this->iStyle);
3165 $aImg->StyleLine($aX1,$aY1,$aX2,$aY2);
3166 $aImg->PopColor($this->iColor);
3167 $aImg->line_style = $oldls;
3168 $aImg->line_weight = $oldlw;
3175 //===================================================
3176 // CLASS Text
3177 // Description: Arbitrary text object that can be added to the graph
3178 //===================================================
3179 class Text {
3180 var $t,$x=0,$y=0,$halign="left",$valign="top",$color=array(0,0,0);
3181 var $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12;
3182 var $hide=false, $dir=0;
3183 var $boxed=false; // Should the text be boxed
3184 var $paragraph_align="left";
3185 var $margin=0;
3186 var $icornerradius=0,$ishadowwidth=3;
3187 var $iScalePosY=null,$iScalePosX=null;
3188 var $iWordwrap=0;
3189 var $fcolor='white',$bcolor='black',$shadow=false;
3190 var $iCSIMarea='',$iCSIMalt='',$iCSIMtarget='';
3192 //---------------
3193 // CONSTRUCTOR
3195 // Create new text at absolute pixel coordinates
3196 function __construct($aTxt="",$aXAbsPos=0,$aYAbsPos=0) {
3197 if( ! is_string($aTxt) ) {
3198 JpGraphError::RaiseL(25050);//('First argument to Text::Text() must be s atring.');
3200 $this->t = $aTxt;
3201 $this->x = round($aXAbsPos);
3202 $this->y = round($aYAbsPos);
3203 $this->margin = 0;
3205 //---------------
3206 // PUBLIC METHODS
3207 // Set the string in the text object
3208 function Set($aTxt) {
3209 $this->t = $aTxt;
3212 // Alias for Pos()
3213 function SetPos($aXAbsPos=0,$aYAbsPos=0,$aHAlign="left",$aVAlign="top") {
3214 $this->Pos($aXAbsPos,$aYAbsPos,$aHAlign,$aVAlign);
3217 // Specify the position and alignment for the text object
3218 function Pos($aXAbsPos=0,$aYAbsPos=0,$aHAlign="left",$aVAlign="top") {
3219 $this->x = $aXAbsPos;
3220 $this->y = $aYAbsPos;
3221 $this->halign = $aHAlign;
3222 $this->valign = $aVAlign;
3225 function SetScalePos($aX,$aY) {
3226 $this->iScalePosX = $aX;
3227 $this->iScalePosY = $aY;
3230 // Specify alignment for the text
3231 function Align($aHAlign,$aVAlign="top",$aParagraphAlign="") {
3232 $this->halign = $aHAlign;
3233 $this->valign = $aVAlign;
3234 if( $aParagraphAlign != "" )
3235 $this->paragraph_align = $aParagraphAlign;
3238 // Alias
3239 function SetAlign($aHAlign,$aVAlign="top",$aParagraphAlign="") {
3240 $this->Align($aHAlign,$aVAlign,$aParagraphAlign);
3243 // Specifies the alignment for a multi line text
3244 function ParagraphAlign($aAlign) {
3245 $this->paragraph_align = $aAlign;
3248 // Specifies the alignment for a multi line text
3249 function SetParagraphAlign($aAlign) {
3250 $this->paragraph_align = $aAlign;
3253 function SetShadow($aShadowColor='gray',$aShadowWidth=3) {
3254 $this->ishadowwidth=$aShadowWidth;
3255 $this->shadow=$aShadowColor;
3256 $this->boxed=true;
3259 function SetWordWrap($aCol) {
3260 $this->iWordwrap = $aCol ;
3263 // Specify that the text should be boxed. fcolor=frame color, bcolor=border color,
3264 // $shadow=drop shadow should be added around the text.
3265 function SetBox($aFrameColor=array(255,255,255),$aBorderColor=array(0,0,0),$aShadowColor=false,$aCornerRadius=4,$aShadowWidth=3) {
3266 if( $aFrameColor==false )
3267 $this->boxed=false;
3268 else
3269 $this->boxed=true;
3270 $this->fcolor=$aFrameColor;
3271 $this->bcolor=$aBorderColor;
3272 // For backwards compatibility when shadow was just true or false
3273 if( $aShadowColor === true )
3274 $aShadowColor = 'gray';
3275 $this->shadow=$aShadowColor;
3276 $this->icornerradius=$aCornerRadius;
3277 $this->ishadowwidth=$aShadowWidth;
3280 // Hide the text
3281 function Hide($aHide=true) {
3282 $this->hide=$aHide;
3285 // This looks ugly since it's not a very orthogonal design
3286 // but I added this "inverse" of Hide() to harmonize
3287 // with some classes which I designed more recently (especially)
3288 // jpgraph_gantt
3289 function Show($aShow=true) {
3290 $this->hide=!$aShow;
3293 // Specify font
3294 function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
3295 $this->font_family=$aFamily;
3296 $this->font_style=$aStyle;
3297 $this->font_size=$aSize;
3300 // Center the text between $left and $right coordinates
3301 function Center($aLeft,$aRight,$aYAbsPos=false) {
3302 $this->x = $aLeft + ($aRight-$aLeft )/2;
3303 $this->halign = "center";
3304 if( is_numeric($aYAbsPos) )
3305 $this->y = $aYAbsPos;
3308 // Set text color
3309 function SetColor($aColor) {
3310 $this->color = $aColor;
3313 function SetAngle($aAngle) {
3314 $this->SetOrientation($aAngle);
3317 // Orientation of text. Note only TTF fonts can have an arbitrary angle
3318 function SetOrientation($aDirection=0) {
3319 if( is_numeric($aDirection) )
3320 $this->dir=$aDirection;
3321 elseif( $aDirection=="h" )
3322 $this->dir = 0;
3323 elseif( $aDirection=="v" )
3324 $this->dir = 90;
3325 else JpGraphError::RaiseL(25051);//(" Invalid direction specified for text.");
3328 // Total width of text
3329 function GetWidth(&$aImg) {
3330 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
3331 $w = $aImg->GetTextWidth($this->t,$this->dir);
3332 return $w;
3335 // Hight of font
3336 function GetFontHeight(&$aImg) {
3337 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
3338 $h = $aImg->GetFontHeight();
3339 return $h;
3343 function GetTextHeight(&$aImg) {
3344 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
3345 $h = $aImg->GetTextHeight($this->t,$this->dir);
3346 return $h;
3349 function GetHeight(&$aImg) {
3350 // Synonym for GetTextHeight()
3351 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
3352 $h = $aImg->GetTextHeight($this->t,$this->dir);
3353 return $h;
3356 // Set the margin which will be interpretated differently depending
3357 // on the context.
3358 function SetMargin($aMarg) {
3359 $this->margin = $aMarg;
3362 function StrokeWithScale(&$aImg,$axscale,$ayscale) {
3363 if( $this->iScalePosX === null ||
3364 $this->iScalePosY === null ) {
3365 $this->Stroke($aImg);
3367 else {
3368 $this->Stroke($aImg,
3369 round($axscale->Translate($this->iScalePosX)),
3370 round($ayscale->Translate($this->iScalePosY)));
3374 function SetCSIMTarget($aTarget,$aAlt=null) {
3375 $this->iCSIMtarget = $aTarget;
3376 $this->iCSIMalt = $aAlt;
3379 function GetCSIMareas() {
3380 if( $this->iCSIMtarget !== '' )
3381 return $this->iCSIMarea;
3382 else
3383 return '';
3386 // Display text in image
3387 function Stroke(&$aImg,$x=null,$y=null) {
3389 if( !empty($x) ) $this->x = round($x);
3390 if( !empty($y) ) $this->y = round($y);
3392 // Insert newlines
3393 if( $this->iWordwrap > 0 ) {
3394 $this->t = wordwrap($this->t,$this->iWordwrap,"\n");
3397 // If position been given as a fraction of the image size
3398 // calculate the absolute position
3399 if( $this->x < 1 && $this->x > 0 ) $this->x *= $aImg->width;
3400 if( $this->y < 1 && $this->y > 0 ) $this->y *= $aImg->height;
3402 $aImg->PushColor($this->color);
3403 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
3404 $aImg->SetTextAlign($this->halign,$this->valign);
3405 if( $this->boxed ) {
3406 if( $this->fcolor=="nofill" )
3407 $this->fcolor=false;
3408 $aImg->SetLineWeight(1);
3409 $bbox = $aImg->StrokeBoxedText($this->x,$this->y,$this->t,
3410 $this->dir,$this->fcolor,$this->bcolor,$this->shadow,
3411 $this->paragraph_align,5,5,$this->icornerradius,
3412 $this->ishadowwidth);
3414 else {
3415 $bbox = $aImg->StrokeText($this->x,$this->y,$this->t,$this->dir,$this->paragraph_align);
3418 // Create CSIM targets
3419 $coords = $bbox[0].','.$bbox[1].','.$bbox[2].','.$bbox[3].','.$bbox[4].','.$bbox[5].','.$bbox[6].','.$bbox[7];
3420 $this->iCSIMarea = "<area shape=\"poly\" coords=\"$coords\" href=\"".$this->iCSIMtarget."\"";
3421 $this->iCSIMarea .= " alt=\"".$this->iCSIMalt."\" title=\"".$this->iCSIMalt."\" />\n";
3423 $aImg->PopColor($this->color);
3426 } // Class
3428 class GraphTabTitle extends Text{
3429 var $corner = 6 , $posx = 7, $posy = 4;
3430 var $color='darkred',$fillcolor='lightyellow',$bordercolor='black';
3431 var $align = 'left', $width=TABTITLE_WIDTHFIT;
3432 function __construct() {
3433 parent::__construct();
3434 $this->t = '';
3435 $this->font_style = FS_BOLD;
3436 $this->hide = true;
3439 function SetColor($aTxtColor,$aFillColor='lightyellow',$aBorderColor='black') {
3440 $this->color = $aTxtColor;
3441 $this->fillcolor = $aFillColor;
3442 $this->bordercolor = $aBorderColor;
3445 function SetFillColor($aFillColor) {
3446 $this->fillcolor = $aFillColor;
3449 function SetTabAlign($aAlign) {
3450 // Synonym for SetPos
3451 $this->align = $aAlign;
3454 function SetPos($aXAbsPos = 0, $aYAbsPos = 0, $aHAlign = 'left', $aVAlign = 'top') {
3455 //$this->align = $aXAbsPos;
3456 throw new \Exception('Invalid function call. should use SetTabAlign()');
3459 function SetWidth($aWidth) {
3460 $this->width = $aWidth ;
3463 function Set($t) {
3464 $this->t = $t;
3465 $this->hide = false;
3468 function SetCorner($aD) {
3469 $this->corner = $aD ;
3472 // parent class compat function
3473 function Stroke(&$aImg, $x = NULL, $y = NULL) {
3474 if( $this->hide )
3475 return;
3476 $this->boxed = false;
3477 $w = $this->GetWidth($aImg) + 2*$this->posx;
3478 $h = $this->GetTextHeight($aImg) + 2*$this->posy;
3480 $x = $aImg->left_margin;
3481 $y = $aImg->top_margin;
3483 if( $this->width === TABTITLE_WIDTHFIT ) {
3484 if( $this->align == 'left' ) {
3485 $p = array($x, $y,
3486 $x, $y-$h+$this->corner,
3487 $x + $this->corner,$y-$h,
3488 $x + $w - $this->corner, $y-$h,
3489 $x + $w, $y-$h+$this->corner,
3490 $x + $w, $y);
3492 elseif( $this->align == 'center' ) {
3493 $x += round($aImg->plotwidth/2) - round($w/2);
3494 $p = array($x, $y,
3495 $x, $y-$h+$this->corner,
3496 $x + $this->corner, $y-$h,
3497 $x + $w - $this->corner, $y-$h,
3498 $x + $w, $y-$h+$this->corner,
3499 $x + $w, $y);
3501 else {
3502 $x += $aImg->plotwidth -$w;
3503 $p = array($x, $y,
3504 $x, $y-$h+$this->corner,
3505 $x + $this->corner,$y-$h,
3506 $x + $w - $this->corner, $y-$h,
3507 $x + $w, $y-$h+$this->corner,
3508 $x + $w, $y);
3511 else {
3512 if( $this->width === TABTITLE_WIDTHFULL )
3513 $w = $aImg->plotwidth ;
3514 else
3515 $w = $this->width ;
3517 // Make the tab fit the width of the plot area
3518 $p = array($x, $y,
3519 $x, $y-$h+$this->corner,
3520 $x + $this->corner,$y-$h,
3521 $x + $w - $this->corner, $y-$h,
3522 $x + $w, $y-$h+$this->corner,
3523 $x + $w, $y);
3526 if( $this->halign == 'left' ) {
3527 $aImg->SetTextAlign('left','bottom');
3528 $x += $this->posx;
3529 $y -= $this->posy;
3531 elseif( $this->halign == 'center' ) {
3532 $aImg->SetTextAlign('center','bottom');
3533 $x += $w/2;
3534 $y -= $this->posy;
3536 else {
3537 $aImg->SetTextAlign('right','bottom');
3538 $x += $w - $this->posx;
3539 $y -= $this->posy;
3542 $aImg->SetColor($this->fillcolor);
3543 $aImg->FilledPolygon($p);
3545 $aImg->SetColor($this->bordercolor);
3546 $aImg->Polygon($p,true);
3548 $aImg->SetColor($this->color);
3549 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
3550 $aImg->StrokeText($x,$y,$this->t,0,'center');
3555 //===================================================
3556 // CLASS SuperScriptText
3557 // Description: Format a superscript text
3558 //===================================================
3559 class SuperScriptText extends Text {
3560 var $iSuper="";
3561 var $sfont_family="",$sfont_style="",$sfont_size=8;
3562 var $iSuperMargin=2,$iVertOverlap=4,$iSuperScale=0.65;
3563 var $iSDir=0;
3564 var $iSimple=false;
3566 function __construct($aTxt="",$aSuper="",$aXAbsPos=0,$aYAbsPos=0) {
3567 parent::__construct($aTxt,$aXAbsPos,$aYAbsPos);
3568 $this->iSuper = $aSuper;
3571 function FromReal($aVal,$aPrecision=2) {
3572 // Convert a floating point number to scientific notation
3573 $neg=1.0;
3574 if( $aVal < 0 ) {
3575 $neg = -1.0;
3576 $aVal = -$aVal;
3579 $l = floor(log10($aVal));
3580 $a = sprintf("%0.".$aPrecision."f",round($aVal / pow(10,$l),$aPrecision));
3581 $a *= $neg;
3582 if( $this->iSimple && ($a == 1 || $a==-1) ) $a = '';
3584 if( $a != '' )
3585 $this->t = $a.' * 10';
3586 else {
3587 if( $neg == 1 )
3588 $this->t = '10';
3589 else
3590 $this->t = '-10';
3592 $this->iSuper = $l;
3595 function Set($aTxt,$aSuper="") {
3596 $this->t = $aTxt;
3597 $this->iSuper = $aSuper;
3600 function SetSuperFont($aFontFam,$aFontStyle=FS_NORMAL,$aFontSize=8) {
3601 $this->sfont_family = $aFontFam;
3602 $this->sfont_style = $aFontStyle;
3603 $this->sfont_size = $aFontSize;
3606 // Total width of text
3607 function GetWidth(&$aImg) {
3608 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
3609 $w = $aImg->GetTextWidth($this->t);
3610 $aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size);
3611 $w += $aImg->GetTextWidth($this->iSuper);
3612 $w += $this->iSuperMargin;
3613 return $w;
3616 // Hight of font (approximate the height of the text)
3617 function GetFontHeight(&$aImg) {
3618 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
3619 $h = $aImg->GetFontHeight();
3620 $aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size);
3621 $h += $aImg->GetFontHeight();
3622 return $h;
3625 // Hight of text
3626 function GetTextHeight(&$aImg) {
3627 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
3628 $h = $aImg->GetTextHeight($this->t);
3629 $aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size);
3630 $h += $aImg->GetTextHeight($this->iSuper);
3631 return $h;
3634 function Stroke(&$aImg,$ax=-1,$ay=-1) {
3636 // To position the super script correctly we need different
3637 // cases to handle the alignmewnt specified since that will
3638 // determine how we can interpret the x,y coordinates
3640 $w = parent::GetWidth($aImg);
3641 $h = parent::GetTextHeight($aImg);
3642 switch( $this->valign ) {
3643 case 'top':
3644 $sy = $this->y;
3645 break;
3646 case 'center':
3647 $sy = $this->y - $h/2;
3648 break;
3649 case 'bottom':
3650 $sy = $this->y - $h;
3651 break;
3652 default:
3653 JpGraphError::RaiseL(25052);//('PANIC: Internal error in SuperScript::Stroke(). Unknown vertical alignment for text');
3654 exit();
3657 switch( $this->halign ) {
3658 case 'left':
3659 $sx = $this->x + $w;
3660 break;
3661 case 'center':
3662 $sx = $this->x + $w/2;
3663 break;
3664 case 'right':
3665 $sx = $this->x;
3666 break;
3667 default:
3668 JpGraphError::RaiseL(25053);//('PANIC: Internal error in SuperScript::Stroke(). Unknown horizontal alignment for text');
3669 exit();
3672 $sx += $this->iSuperMargin;
3673 $sy += $this->iVertOverlap;
3675 // Should we automatically determine the font or
3676 // has the user specified it explicetly?
3677 if( $this->sfont_family == "" ) {
3678 if( $this->font_family <= FF_FONT2 ) {
3679 if( $this->font_family == FF_FONT0 ) {
3680 $sff = FF_FONT0;
3682 elseif( $this->font_family == FF_FONT1 ) {
3683 if( $this->font_style == FS_NORMAL )
3684 $sff = FF_FONT0;
3685 else
3686 $sff = FF_FONT1;
3688 else {
3689 $sff = FF_FONT1;
3691 $sfs = $this->font_style;
3692 $sfz = $this->font_size;
3694 else {
3695 // TTF fonts
3696 $sff = $this->font_family;
3697 $sfs = $this->font_style;
3698 $sfz = floor($this->font_size*$this->iSuperScale);
3699 if( $sfz < 8 ) $sfz = 8;
3701 $this->sfont_family = $sff;
3702 $this->sfont_style = $sfs;
3703 $this->sfont_size = $sfz;
3705 else {
3706 $sff = $this->sfont_family;
3707 $sfs = $this->sfont_style;
3708 $sfz = $this->sfont_size;
3711 parent::Stroke($aImg,$ax,$ay);
3714 // For the builtin fonts we need to reduce the margins
3715 // since the bounding bx reported for the builtin fonts
3716 // are much larger than for the TTF fonts.
3717 if( $sff <= FF_FONT2 ) {
3718 $sx -= 2;
3719 $sy += 3;
3722 $aImg->SetTextAlign('left','bottom');
3723 $aImg->SetFont($sff,$sfs,$sfz);
3724 $aImg->PushColor($this->color);
3725 $aImg->StrokeText($sx,$sy,$this->iSuper,$this->iSDir,'left');
3726 $aImg->PopColor();
3731 //===================================================
3732 // CLASS Grid
3733 // Description: responsible for drawing grid lines in graph
3734 //===================================================
3735 class Grid {
3736 var $img;
3737 var $scale;
3738 var $grid_color='#DDDDDD',$grid_mincolor='#DDDDDD';
3739 var $type="solid";
3740 var $show=false, $showMinor=false,$weight=1;
3741 var $fill=false,$fillcolor=array('#EFEFEF','#BBCCFF');
3742 //---------------
3743 // CONSTRUCTOR
3744 function __construct(&$aAxis) {
3745 $this->scale = &$aAxis->scale;
3746 $this->img = &$aAxis->img;
3748 //---------------
3749 // PUBLIC METHODS
3750 function SetColor($aMajColor,$aMinColor=false) {
3751 $this->grid_color=$aMajColor;
3752 if( $aMinColor === false )
3753 $aMinColor = $aMajColor ;
3754 $this->grid_mincolor = $aMinColor;
3757 function SetWeight($aWeight) {
3758 $this->weight=$aWeight;
3761 // Specify if grid should be dashed, dotted or solid
3762 function SetLineStyle($aType) {
3763 $this->type = $aType;
3766 // Decide if both major and minor grid should be displayed
3767 function Show($aShowMajor=true,$aShowMinor=false) {
3768 $this->show=$aShowMajor;
3769 $this->showMinor=$aShowMinor;
3772 function SetFill($aFlg=true,$aColor1='lightgray',$aColor2='lightblue') {
3773 $this->fill = $aFlg;
3774 $this->fillcolor = array( $aColor1, $aColor2 );
3777 // Display the grid
3778 function Stroke() {
3779 if( $this->showMinor ) {
3780 $tmp = $this->grid_color;
3781 $this->grid_color = $this->grid_mincolor;
3782 $this->DoStroke($this->scale->ticks->ticks_pos);
3784 $this->grid_color = $tmp;
3785 $this->DoStroke($this->scale->ticks->maj_ticks_pos);
3787 else {
3788 $this->DoStroke($this->scale->ticks->maj_ticks_pos);
3792 //--------------
3793 // Private methods
3794 // Draw the grid
3795 function DoStroke(&$aTicksPos) {
3796 if( !$this->show )
3797 return;
3798 $nbrgrids = count($aTicksPos);
3800 if( $this->scale->type=="y" ) {
3801 $xl=$this->img->left_margin;
3802 $xr=$this->img->width-$this->img->right_margin;
3804 if( $this->fill ) {
3805 // Draw filled areas
3806 $y2 = $aTicksPos[0];
3807 $i=1;
3808 while( $i < $nbrgrids ) {
3809 $y1 = $y2;
3810 $y2 = $aTicksPos[$i++];
3811 $this->img->SetColor($this->fillcolor[$i & 1]);
3812 $this->img->FilledRectangle($xl,$y1,$xr,$y2);
3816 $this->img->SetColor($this->grid_color);
3817 $this->img->SetLineWeight($this->weight);
3819 // Draw grid lines
3820 for($i=0; $i<$nbrgrids; ++$i) {
3821 $y=$aTicksPos[$i];
3822 if( $this->type == "solid" )
3823 $this->img->Line($xl,$y,$xr,$y);
3824 elseif( $this->type == "dotted" )
3825 $this->img->DashedLine($xl,$y,$xr,$y,1,6);
3826 elseif( $this->type == "dashed" )
3827 $this->img->DashedLine($xl,$y,$xr,$y,2,4);
3828 elseif( $this->type == "longdashed" )
3829 $this->img->DashedLine($xl,$y,$xr,$y,8,6);
3832 elseif( $this->scale->type=="x" ) {
3833 $yu=$this->img->top_margin;
3834 $yl=$this->img->height-$this->img->bottom_margin;
3835 $limit=$this->img->width-$this->img->right_margin;
3837 if( $this->fill ) {
3838 // Draw filled areas
3839 $x2 = $aTicksPos[0];
3840 $i=1;
3841 while( $i < $nbrgrids ) {
3842 $x1 = $x2;
3843 $x2 = min($aTicksPos[$i++],$limit) ;
3844 $this->img->SetColor($this->fillcolor[$i & 1]);
3845 $this->img->FilledRectangle($x1,$yu,$x2,$yl);
3849 $this->img->SetColor($this->grid_color);
3850 $this->img->SetLineWeight($this->weight);
3852 // We must also test for limit since we might have
3853 // an offset and the number of ticks is calculated with
3854 // assumption offset==0 so we might end up drawing one
3855 // to many gridlines
3856 $i=0;
3857 $x=$aTicksPos[$i];
3858 while( $i<count($aTicksPos) && ($x=$aTicksPos[$i]) <= $limit ) {
3859 if( $this->type == "solid" )
3860 $this->img->Line($x,$yl,$x,$yu);
3861 elseif( $this->type == "dotted" )
3862 $this->img->DashedLine($x,$yl,$x,$yu,1,6);
3863 elseif( $this->type == "dashed" )
3864 $this->img->DashedLine($x,$yl,$x,$yu,2,4);
3865 elseif( $this->type == "longdashed" )
3866 $this->img->DashedLine($x,$yl,$x,$yu,8,6);
3867 ++$i;
3870 else {
3871 JpGraphError::RaiseL(25054,$this->scale->type);//('Internal error: Unknown grid axis ['.$this->scale->type.']');
3873 return true;
3875 } // Class
3877 //===================================================
3878 // CLASS Axis
3879 // Description: Defines X and Y axis. Notes that at the
3880 // moment the code is not really good since the axis on
3881 // several occasion must know wheter it's an X or Y axis.
3882 // This was a design decision to make the code easier to
3883 // follow.
3884 //===================================================
3885 class Axis {
3886 var $pos = false;
3887 var $weight=1;
3888 var $color=array(0,0,0),$label_color=array(0,0,0);
3889 var $img=null,$scale=null;
3890 var $hide=false;
3891 var $ticks_label=false, $ticks_label_colors=null;
3892 var $show_first_label=true,$show_last_label=true;
3893 var $label_step=1; // Used by a text axis to specify what multiple of major steps
3894 // should be labeled.
3895 var $tick_step=1;
3896 var $labelPos=0; // Which side of the axis should the labels be?
3897 var $title=null,$title_adjust,$title_margin,$title_side=SIDE_LEFT;
3898 var $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12,$label_angle=0;
3899 var $tick_label_margin=5;
3900 var $label_halign = '',$label_valign = '', $label_para_align='left';
3901 var $hide_line=false,$hide_labels=false;
3902 var $iDeltaAbsPos=0;
3903 //var $hide_zero_label=false;
3905 //---------------
3906 // CONSTRUCTOR
3907 function __construct(&$img,&$aScale,$color=array(0,0,0)) {
3908 $this->img = &$img;
3909 $this->scale = &$aScale;
3910 $this->color = $color;
3911 $this->title=new Text("");
3913 if( $aScale->type=="y" ) {
3914 $this->title_margin = 25;
3915 $this->title_adjust="middle";
3916 $this->title->SetOrientation(90);
3917 $this->tick_label_margin=7;
3918 $this->labelPos=SIDE_LEFT;
3919 //$this->SetLabelFormat('%.1f');
3921 else {
3922 $this->title_margin = 5;
3923 $this->title_adjust="high";
3924 $this->title->SetOrientation(0);
3925 $this->tick_label_margin=5;
3926 $this->labelPos=SIDE_DOWN;
3927 $this->title_side=SIDE_DOWN;
3928 //$this->SetLabelFormat('%.0f');
3931 //---------------
3932 // PUBLIC METHODS
3934 function SetLabelFormat($aFormStr) {
3935 $this->scale->ticks->SetLabelFormat($aFormStr);
3938 function SetLabelFormatString($aFormStr,$aDate=false) {
3939 $this->scale->ticks->SetLabelFormat($aFormStr,$aDate);
3942 function SetLabelFormatCallback($aFuncName) {
3943 $this->scale->ticks->SetFormatCallback($aFuncName);
3946 function SetLabelAlign($aHAlign,$aVAlign="top",$aParagraphAlign='left') {
3947 $this->label_halign = $aHAlign;
3948 $this->label_valign = $aVAlign;
3949 $this->label_para_align = $aParagraphAlign;
3952 // Don't display the first label
3953 function HideFirstTickLabel($aShow=false) {
3954 $this->show_first_label=$aShow;
3957 function HideLastTickLabel($aShow=false) {
3958 $this->show_last_label=$aShow;
3961 // Manually specify the major and (optional) minor tick position and labels
3962 function SetTickPositions($aMajPos,$aMinPos=NULL,$aLabels=NULL) {
3963 $this->scale->ticks->SetTickPositions($aMajPos,$aMinPos,$aLabels);
3966 // Manually specify major tick positions and optional labels
3967 function SetMajTickPositions($aMajPos,$aLabels=NULL) {
3968 $this->scale->ticks->SetTickPositions($aMajPos,NULL,$aLabels);
3971 // Hide minor or major tick marks
3972 function HideTicks($aHideMinor=true,$aHideMajor=true) {
3973 $this->scale->ticks->SupressMinorTickMarks($aHideMinor);
3974 $this->scale->ticks->SupressTickMarks($aHideMajor);
3977 // Hide zero label
3978 function HideZeroLabel($aFlag=true) {
3979 $this->scale->ticks->SupressZeroLabel();
3980 //$this->hide_zero_label = $aFlag;
3983 function HideFirstLastLabel() {
3984 // The two first calls to ticks method will supress
3985 // automatically generated scale values. However, that
3986 // will not affect manually specified value, e.g text-scales.
3987 // therefor we also make a kludge here to supress manually
3988 // specified scale labels.
3989 $this->scale->ticks->SupressLast();
3990 $this->scale->ticks->SupressFirst();
3991 $this->show_first_label = false;
3992 $this->show_last_label = false;
3995 // Hide the axis
3996 function Hide($aHide=true) {
3997 $this->hide=$aHide;
4000 // Hide the actual axis-line, but still print the labels
4001 function HideLine($aHide=true) {
4002 $this->hide_line = $aHide;
4005 function HideLabels($aHide=true) {
4006 $this->hide_labels = $aHide;
4010 // Weight of axis
4011 function SetWeight($aWeight) {
4012 $this->weight = $aWeight;
4015 // Axis color
4016 function SetColor($aColor,$aLabelColor=false) {
4017 $this->color = $aColor;
4018 if( !$aLabelColor ) $this->label_color = $aColor;
4019 else $this->label_color = $aLabelColor;
4022 // Title on axis
4023 function SetTitle($aTitle,$aAdjustAlign="high") {
4024 $this->title->Set($aTitle);
4025 $this->title_adjust=$aAdjustAlign;
4028 // Specify distance from the axis
4029 function SetTitleMargin($aMargin) {
4030 $this->title_margin=$aMargin;
4033 // Which side of the axis should the axis title be?
4034 function SetTitleSide($aSideOfAxis) {
4035 $this->title_side = $aSideOfAxis;
4038 // Utility function to set the direction for tick marks
4039 function SetTickDirection($aDir) {
4040 // Will be deprecated from 1.7
4041 if( ERR_DEPRECATED )
4042 JpGraphError::RaiseL(25055);//('Axis::SetTickDirection() is deprecated. Use Axis::SetTickSide() instead');
4043 $this->scale->ticks->SetSide($aDir);
4046 function SetTickSide($aDir) {
4047 $this->scale->ticks->SetSide($aDir);
4050 // Specify text labels for the ticks. One label for each data point
4051 function SetTickLabels($aLabelArray,$aLabelColorArray=null) {
4052 $this->ticks_label = $aLabelArray;
4053 $this->ticks_label_colors = $aLabelColorArray;
4056 // How far from the axis should the labels be drawn
4057 function SetTickLabelMargin($aMargin) {
4058 if( ERR_DEPRECATED )
4059 JpGraphError::RaiseL(25056);//('SetTickLabelMargin() is deprecated. Use Axis::SetLabelMargin() instead.');
4060 $this->tick_label_margin=$aMargin;
4063 function SetLabelMargin($aMargin) {
4064 $this->tick_label_margin=$aMargin;
4067 // Specify that every $step of the ticks should be displayed starting
4068 // at $start
4069 // DEPRECATED FUNCTION: USE SetTextTickInterval() INSTEAD
4070 function SetTextTicks($step,$start=0) {
4071 JpGraphError::RaiseL(25057);//(" SetTextTicks() is deprecated. Use SetTextTickInterval() instead.");
4074 // Specify that every $step of the ticks should be displayed starting
4075 // at $start
4076 function SetTextTickInterval($aStep,$aStart=0) {
4077 $this->scale->ticks->SetTextLabelStart($aStart);
4078 $this->tick_step=$aStep;
4081 // Specify that every $step tick mark should have a label
4082 // should be displayed starting
4083 function SetTextLabelInterval($aStep,$aStart=0) {
4084 if( $aStep < 1 )
4085 JpGraphError::RaiseL(25058);//(" Text label interval must be specified >= 1.");
4086 $this->scale->ticks->SetTextLabelStart($aStart);
4087 $this->label_step=$aStep;
4090 // Which side of the axis should the labels be on?
4091 function SetLabelPos($aSidePos) {
4092 // This will be deprecated from 1.7
4093 if( ERR_DEPRECATED )
4094 JpGraphError::RaiseL(25059);//('SetLabelPos() is deprecated. Use Axis::SetLabelSide() instead.');
4095 $this->labelPos=$aSidePos;
4098 function SetLabelSide($aSidePos) {
4099 $this->labelPos=$aSidePos;
4102 // Set the font
4103 function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
4104 $this->font_family = $aFamily;
4105 $this->font_style = $aStyle;
4106 $this->font_size = $aSize;
4109 // Position for axis line on the "other" scale
4110 function SetPos($aPosOnOtherScale) {
4111 $this->pos=$aPosOnOtherScale;
4114 // Set the position of the axis to be X-pixels delta to the right
4115 // of the max X-position (used to position the multiple Y-axis)
4116 function SetPosAbsDelta($aDelta) {
4117 $this->iDeltaAbsPos=$aDelta;
4120 // Specify the angle for the tick labels
4121 function SetLabelAngle($aAngle) {
4122 $this->label_angle = $aAngle;
4125 // Stroke the axis.
4126 function Stroke($aOtherAxisScale,$aStrokeLabels=true) {
4127 if( $this->hide ) return;
4128 if( is_numeric($this->pos) ) {
4129 $pos=$aOtherAxisScale->Translate($this->pos);
4131 else { // Default to minimum of other scale if pos not set
4132 if( ($aOtherAxisScale->GetMinVal() >= 0 && $this->pos==false) || $this->pos=="min" ) {
4133 $pos = $aOtherAxisScale->scale_abs[0];
4135 elseif($this->pos == "max") {
4136 $pos = $aOtherAxisScale->scale_abs[1];
4138 else { // If negative set x-axis at 0
4139 $this->pos=0;
4140 $pos=$aOtherAxisScale->Translate(0);
4143 $pos += $this->iDeltaAbsPos;
4144 $this->img->SetLineWeight($this->weight);
4145 $this->img->SetColor($this->color);
4146 $this->img->SetFont($this->font_family,$this->font_style,$this->font_size);
4147 if( $this->scale->type == "x" ) {
4148 if( !$this->hide_line )
4149 $this->img->FilledRectangle($this->img->left_margin,$pos,
4150 $this->img->width-$this->img->right_margin,$pos+$this->weight-1);
4151 if( $this->title_side == SIDE_DOWN ) {
4152 $y = $pos + $this->img->GetFontHeight() + $this->title_margin + $this->title->margin;
4153 $yalign = 'top';
4155 else {
4156 $y = $pos - $this->img->GetFontHeight() - $this->title_margin - $this->title->margin;
4157 $yalign = 'bottom';
4160 if( $this->title_adjust=="high" )
4161 $this->title->Pos($this->img->width-$this->img->right_margin,$y,"right",$yalign);
4162 elseif( $this->title_adjust=="middle" || $this->title_adjust=="center" )
4163 $this->title->Pos(($this->img->width-$this->img->left_margin-$this->img->right_margin)/2+$this->img->left_margin,$y,"center",$yalign);
4164 elseif($this->title_adjust=="low")
4165 $this->title->Pos($this->img->left_margin,$y,"left",$yalign);
4166 else {
4167 JpGraphError::RaiseL(25060,$this->title_adjust);//('Unknown alignment specified for X-axis title. ('.$this->title_adjust.')');
4170 elseif( $this->scale->type == "y" ) {
4171 // Add line weight to the height of the axis since
4172 // the x-axis could have a width>1 and we want the axis to fit nicely together.
4173 if( !$this->hide_line )
4174 $this->img->FilledRectangle($pos-$this->weight+1,$this->img->top_margin,
4175 $pos,$this->img->height-$this->img->bottom_margin+$this->weight-1);
4176 $x=$pos ;
4177 if( $this->title_side == SIDE_LEFT ) {
4178 $x -= $this->title_margin;
4179 $x -= $this->title->margin;
4180 $halign="right";
4182 else {
4183 $x += $this->title_margin;
4184 $x += $this->title->margin;
4185 $halign="left";
4187 // If the user has manually specified an hor. align
4188 // then we override the automatic settings with this
4189 // specifed setting. Since default is 'left' we compare
4190 // with that. (This means a manually set 'left' align
4191 // will have no effect.)
4192 if( $this->title->halign != 'left' )
4193 $halign = $this->title->halign;
4194 if( $this->title_adjust=="high" )
4195 $this->title->Pos($x,$this->img->top_margin,$halign,"top");
4196 elseif($this->title_adjust=="middle" || $this->title_adjust=="center")
4197 $this->title->Pos($x,($this->img->height-$this->img->top_margin-$this->img->bottom_margin)/2+$this->img->top_margin,$halign,"center");
4198 elseif($this->title_adjust=="low")
4199 $this->title->Pos($x,$this->img->height-$this->img->bottom_margin,$halign,"bottom");
4200 else
4201 JpGraphError::RaiseL(25061,$this->title_adjust);//('Unknown alignment specified for Y-axis title. ('.$this->title_adjust.')');
4204 $this->scale->ticks->Stroke($this->img,$this->scale,$pos);
4205 if( $aStrokeLabels ) {
4206 if( !$this->hide_labels )
4207 $this->StrokeLabels($pos);
4208 $this->title->Stroke($this->img);
4212 //---------------
4213 // PRIVATE METHODS
4214 // Draw all the tick labels on major tick marks
4215 function StrokeLabels($aPos,$aMinor=false,$aAbsLabel=false) {
4217 $this->img->SetColor($this->label_color);
4218 $this->img->SetFont($this->font_family,$this->font_style,$this->font_size);
4219 $yoff=$this->img->GetFontHeight()/2;
4221 // Only draw labels at major tick marks
4222 $nbr = count($this->scale->ticks->maj_ticks_label);
4224 // We have the option to not-display the very first mark
4225 // (Usefull when the first label might interfere with another
4226 // axis.)
4227 $i = $this->show_first_label ? 0 : 1 ;
4228 if( !$this->show_last_label ) --$nbr;
4229 // Now run through all labels making sure we don't overshoot the end
4230 // of the scale.
4231 $ncolor=0;
4232 if( isset($this->ticks_label_colors) )
4233 $ncolor=count($this->ticks_label_colors);
4235 while( $i<$nbr ) {
4236 // $tpos holds the absolute text position for the label
4237 $tpos=$this->scale->ticks->maj_ticklabels_pos[$i];
4239 // Note. the $limit is only used for the x axis since we
4240 // might otherwise overshoot if the scale has been centered
4241 // This is due to us "loosing" the last tick mark if we center.
4242 if( $this->scale->type=="x" && $tpos > $this->img->width-$this->img->right_margin+1 ) {
4243 return;
4245 // we only draw every $label_step label
4246 if( ($i % $this->label_step)==0 ) {
4248 // Set specific label color if specified
4249 if( $ncolor > 0 )
4250 $this->img->SetColor($this->ticks_label_colors[$i % $ncolor]);
4252 // If the label has been specified use that and in other case
4253 // just label the mark with the actual scale value
4254 $m=$this->scale->ticks->GetMajor();
4256 // ticks_label has an entry for each data point and is the array
4257 // that holds the labels set by the user. If the user hasn't
4258 // specified any values we use whats in the automatically asigned
4259 // labels in the maj_ticks_label
4260 if( isset($this->ticks_label[$i*$m]) )
4261 $label=$this->ticks_label[$i*$m];
4262 else {
4263 if( $aAbsLabel )
4264 $label=abs($this->scale->ticks->maj_ticks_label[$i]);
4265 else
4266 $label=$this->scale->ticks->maj_ticks_label[$i];
4267 if( $this->scale->textscale && $this->scale->ticks->label_formfunc == '' ) {
4268 ++$label;
4272 //if( $this->hide_zero_label && $label==0.0 ) {
4273 // ++$i;
4274 // continue;
4275 //}
4277 if( $this->scale->type == "x" ) {
4278 if( $this->labelPos == SIDE_DOWN ) {
4279 if( $this->label_angle==0 || $this->label_angle==90 ) {
4280 if( $this->label_halign=='' && $this->label_valign=='')
4281 $this->img->SetTextAlign('center','top');
4282 else
4283 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
4286 else {
4287 if( $this->label_halign=='' && $this->label_valign=='')
4288 $this->img->SetTextAlign("right","top");
4289 else
4290 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
4293 $this->img->StrokeText($tpos,$aPos+$this->tick_label_margin,$label,
4294 $this->label_angle,$this->label_para_align);
4296 else {
4297 if( $this->label_angle==0 || $this->label_angle==90 ) {
4298 if( $this->label_halign=='' && $this->label_valign=='')
4299 $this->img->SetTextAlign("center","bottom");
4300 else
4301 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
4303 else {
4304 if( $this->label_halign=='' && $this->label_valign=='')
4305 $this->img->SetTextAlign("right","bottom");
4306 else
4307 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
4309 $this->img->StrokeText($tpos,$aPos-$this->tick_label_margin,$label,
4310 $this->label_angle,$this->label_para_align);
4313 else {
4314 // scale->type == "y"
4315 //if( $this->label_angle!=0 )
4316 //JpGraphError::Raise(" Labels at an angle are not supported on Y-axis");
4317 if( $this->labelPos == SIDE_LEFT ) { // To the left of y-axis
4318 if( $this->label_halign=='' && $this->label_valign=='')
4319 $this->img->SetTextAlign("right","center");
4320 else
4321 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
4322 $this->img->StrokeText($aPos-$this->tick_label_margin,$tpos,$label,$this->label_angle,$this->label_para_align);
4324 else { // To the right of the y-axis
4325 if( $this->label_halign=='' && $this->label_valign=='')
4326 $this->img->SetTextAlign("left","center");
4327 else
4328 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
4329 $this->img->StrokeText($aPos+$this->tick_label_margin,$tpos,$label,$this->label_angle,$this->label_para_align);
4333 ++$i;
4337 } // Class
4339 //===================================================
4340 // CLASS Ticks
4341 // Description: Abstract base class for drawing linear and logarithmic
4342 // tick marks on axis
4343 //===================================================
4344 class Ticks {
4345 var $minor_abs_size=3, $major_abs_size=5;
4346 var $direction=1; // Should ticks be in(=1) the plot area or outside (=-1)?
4347 var $scale;
4348 var $is_set=false;
4349 var $precision;
4350 var $supress_zerolabel=false,$supress_first=false;
4351 var $supress_last=false,$supress_tickmarks=false,$supress_minor_tickmarks=false;
4352 var $mincolor="",$majcolor="";
4353 var $weight=1;
4354 var $label_formatstr=''; // C-style format string to use for labels
4355 var $label_formfunc='';
4356 var $label_dateformatstr='';
4357 var $label_usedateformat=FALSE;
4360 //---------------
4361 // CONSTRUCTOR
4362 function __construct(&$aScale) {
4363 $this->scale=&$aScale;
4364 $this->precision = -1;
4367 //---------------
4368 // PUBLIC METHODS
4369 // Set format string for automatic labels
4370 function SetLabelFormat($aFormatString,$aDate=FALSE) {
4371 $this->label_formatstr=$aFormatString;
4372 $this->label_usedateformat=$aDate;
4375 function SetLabelDateFormat($aFormatString) {
4376 $this->label_dateformatstr=$aFormatString;
4379 function SetFormatCallback($aCallbackFuncName) {
4380 $this->label_formfunc = $aCallbackFuncName;
4383 // Don't display the first zero label
4384 function SupressZeroLabel($aFlag=true) {
4385 $this->supress_zerolabel=$aFlag;
4388 // Don't display minor tick marks
4389 function SupressMinorTickMarks($aHide=true) {
4390 $this->supress_minor_tickmarks=$aHide;
4393 // Don't display major tick marks
4394 function SupressTickMarks($aHide=true) {
4395 $this->supress_tickmarks=$aHide;
4398 // Hide the first tick mark
4399 function SupressFirst($aHide=true) {
4400 $this->supress_first=$aHide;
4403 // Hide the last tick mark
4404 function SupressLast($aHide=true) {
4405 $this->supress_last=$aHide;
4408 // Size (in pixels) of minor tick marks
4409 function GetMinTickAbsSize() {
4410 return $this->minor_abs_size;
4413 // Size (in pixels) of major tick marks
4414 function GetMajTickAbsSize() {
4415 return $this->major_abs_size;
4418 function SetSize($aMajSize,$aMinSize=3) {
4419 $this->major_abs_size = $aMajSize;
4420 $this->minor_abs_size = $aMinSize;
4423 // Have the ticks been specified
4424 function IsSpecified() {
4425 return $this->is_set;
4428 // Set the distance between major and minor tick marks
4429 function Set($aMaj,$aMin) {
4430 // "Virtual method"
4431 // Should be implemented by the concrete subclass
4432 // if any action is wanted.
4435 // Specify number of decimals in automatic labels
4436 // Deprecated from 1.4. Use SetFormatString() instead
4437 function SetPrecision($aPrecision) {
4438 if( ERR_DEPRECATED )
4439 JpGraphError::RaiseL(25063);//('Ticks::SetPrecision() is deprecated. Use Ticks::SetLabelFormat() (or Ticks::SetFormatCallback()) instead');
4440 $this->precision=$aPrecision;
4443 function SetSide($aSide) {
4444 $this->direction=$aSide;
4447 // Which side of the axis should the ticks be on
4448 function SetDirection($aSide=SIDE_RIGHT) {
4449 $this->direction=$aSide;
4452 // Set colors for major and minor tick marks
4453 function SetMarkColor($aMajorColor,$aMinorColor="") {
4454 $this->SetColor($aMajorColor,$aMinorColor);
4457 function SetColor($aMajorColor,$aMinorColor="") {
4458 $this->majcolor=$aMajorColor;
4460 // If not specified use same as major
4461 if( $aMinorColor=="" )
4462 $this->mincolor=$aMajorColor;
4463 else
4464 $this->mincolor=$aMinorColor;
4467 function SetWeight($aWeight) {
4468 $this->weight=$aWeight;
4471 } // Class
4473 //===================================================
4474 // CLASS LinearTicks
4475 // Description: Draw linear ticks on axis
4476 //===================================================
4477 class LinearTicks extends Ticks {
4478 var $minor_step=1, $major_step=2;
4479 var $xlabel_offset=0,$xtick_offset=0;
4480 var $label_offset=0; // What offset should the displayed label have
4481 // i.e should we display 0,1,2 or 1,2,3,4 or 2,3,4 etc
4482 var $text_label_start=0;
4483 var $iManualTickPos = NULL, $iManualMinTickPos = NULL, $iManualTickLabels = NULL;
4484 var $maj_ticks_pos = array(), $maj_ticklabels_pos = array(),
4485 $ticks_pos = array(), $maj_ticks_label = array();
4487 //---------------
4488 // CONSTRUCTOR
4489 function __construct() {
4490 parent::__construct();
4491 $this->precision = -1;
4494 //---------------
4495 // PUBLIC METHODS
4498 // Return major step size in world coordinates
4499 function GetMajor() {
4500 return $this->major_step;
4503 // Return minor step size in world coordinates
4504 function GetMinor() {
4505 return $this->minor_step;
4508 // Set Minor and Major ticks (in world coordinates)
4509 function Set($aMajStep,$aMinStep=false) {
4510 if( $aMinStep==false )
4511 $aMinStep=$aMajStep;
4513 if( $aMajStep <= 0 || $aMinStep <= 0 ) {
4514 JpGraphError::RaiseL(25064);
4515 //(" 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.");
4518 $this->major_step=$aMajStep;
4519 $this->minor_step=$aMinStep;
4520 $this->is_set = true;
4523 function SetMajTickPositions($aMajPos,$aLabels=NULL) {
4524 $this->SetTickPositions($aMajPos,NULL,$aLabels);
4527 function SetTickPositions($aMajPos,$aMinPos=NULL,$aLabels=NULL) {
4528 if( !is_array($aMajPos) || ($aMinPos!==NULL && !is_array($aMinPos)) ) {
4529 JpGraphError::RaiseL(25065);//('Tick positions must be specifued as an array()');
4530 return;
4532 $n=count($aMajPos);
4533 if( is_array($aLabels) && (count($aLabels) != $n) ) {
4534 JpGraphError::RaiseL(25066);//('When manually specifying tick positions and labels the number of labels must be the same as the number of specified ticks.');
4535 return;
4537 $this->iManualTickPos = $aMajPos;
4538 $this->iManualMinTickPos = $aMinPos;
4539 $this->iManualTickLabels = $aLabels;
4542 // Specify all the tick positions manually and possible also the exact labels
4543 function _doManualTickPos($aScale) {
4544 $n=count($this->iManualTickPos);
4545 $m=count($this->iManualMinTickPos);
4546 $doLbl=count($this->iManualTickLabels) > 0;
4547 $this->use_manualtickpos=true;
4549 $this->maj_ticks_pos = array();
4550 $this->maj_ticklabels_pos = array();
4551 $this->ticks_pos = array();
4553 // Now loop through the supplied positions and translate them to screen coordinates
4554 // and store them in the maj_label_positions
4555 $minScale = $aScale->scale[0];
4556 $maxScale = $aScale->scale[1];
4557 $j=0;
4558 for($i=0; $i < $n ; ++$i ) {
4559 // First make sure that the first tick is not lower than the lower scale value
4560 if( !isset($this->iManualTickPos[$i]) ||
4561 $this->iManualTickPos[$i] < $minScale || $this->iManualTickPos[$i] > $maxScale) {
4562 continue;
4566 $this->maj_ticks_pos[$j] = $aScale->Translate($this->iManualTickPos[$i]);
4567 $this->maj_ticklabels_pos[$j] = $this->maj_ticks_pos[$j];
4569 // Set the minor tick marks the same as major if not specified
4570 if( $m <= 0 ) {
4571 $this->ticks_pos[$j] = $this->maj_ticks_pos[$j];
4574 if( $doLbl ) {
4575 $this->maj_ticks_label[$j] = $this->iManualTickLabels[$i];
4577 else {
4578 $this->maj_ticks_label[$j]=$this->_doLabelFormat($this->iManualTickPos[$i],$i,$n);
4580 ++$j;
4583 // Some sanity check
4584 if( count($this->maj_ticks_pos) < 2 ) {
4585 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.');
4588 // Setup the minor tick marks
4589 $j=0;
4590 for($i=0; $i < $m; ++$i ) {
4591 if( empty($this->iManualMinTickPos[$i]) ||
4592 $this->iManualMinTickPos[$i] < $minScale || $this->iManualMinTickPos[$i] > $maxScale)
4593 continue;
4594 $this->ticks_pos[$j] = $aScale->Translate($this->iManualMinTickPos[$i]);
4595 ++$j;
4599 function _doAutoTickPos($aScale) {
4600 $maj_step_abs = $aScale->scale_factor*$this->major_step;
4601 $min_step_abs = $aScale->scale_factor*$this->minor_step;
4603 if( $min_step_abs==0 || $maj_step_abs==0 ) {
4604 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')");
4606 // We need to make this an int since comparing it below
4607 // with the result from round() can give wrong result, such that
4608 // (40 < 40) == TRUE !!!
4609 $limit = (int)$aScale->scale_abs[1];
4611 if( $aScale->textscale ) {
4612 // This can only be true for a X-scale (horizontal)
4613 // Define ticks for a text scale. This is slightly different from a
4614 // normal linear type of scale since the position might be adjusted
4615 // and the labels start at on
4616 $label = (float)$aScale->GetMinVal()+$this->text_label_start+$this->label_offset;
4617 $start_abs=$aScale->scale_factor*$this->text_label_start;
4618 $nbrmajticks=ceil(($aScale->GetMaxVal()-$aScale->GetMinVal()-$this->text_label_start )/$this->major_step)+1;
4619 $x = $aScale->scale_abs[0]+$start_abs+$this->xlabel_offset*$min_step_abs;
4620 for( $i=0; $label <= $aScale->GetMaxVal()+$this->label_offset; ++$i ) {
4621 // Apply format to label
4622 $this->maj_ticks_label[$i]=$this->_doLabelFormat($label,$i,$nbrmajticks);
4623 $label+=$this->major_step;
4625 // The x-position of the tick marks can be different from the labels.
4626 // Note that we record the tick position (not the label) so that the grid
4627 // happen upon tick marks and not labels.
4628 $xtick=$aScale->scale_abs[0]+$start_abs+$this->xtick_offset*$min_step_abs+$i*$maj_step_abs;
4629 $this->maj_ticks_pos[$i]=$xtick;
4630 $this->maj_ticklabels_pos[$i] = round($x);
4631 $x += $maj_step_abs;
4634 else {
4635 $label = $aScale->GetMinVal();
4636 $abs_pos = $aScale->scale_abs[0];
4637 $j=0; $i=0;
4638 $step = round($maj_step_abs/$min_step_abs);
4639 if( $aScale->type == "x" ) {
4640 // For a normal linear type of scale the major ticks will always be multiples
4641 // of the minor ticks. In order to avoid any rounding issues the major ticks are
4642 // defined as every "step" minor ticks and not calculated separately
4643 $nbrmajticks=ceil(($aScale->GetMaxVal()-$aScale->GetMinVal()-$this->text_label_start )/$this->major_step)+1;
4644 while( round($abs_pos) <= $limit ) {
4645 $this->ticks_pos[] = round($abs_pos);
4646 $this->ticks_label[] = $label;
4647 if( $i % $step == 0 && $j < $nbrmajticks ) {
4648 $this->maj_ticks_pos[$j] = round($abs_pos);
4649 $this->maj_ticklabels_pos[$j] = round($abs_pos);
4650 $this->maj_ticks_label[$j]=$this->_doLabelFormat($label,$j,$nbrmajticks);
4651 ++$j;
4653 ++$i;
4654 $abs_pos += $min_step_abs;
4655 $label+=$this->minor_step;
4658 elseif( $aScale->type == "y" ) {
4659 $nbrmajticks=floor(($aScale->GetMaxVal()-$aScale->GetMinVal())/$this->major_step)+1;
4660 while( round($abs_pos) >= $limit ) {
4661 $this->ticks_pos[$i] = round($abs_pos);
4662 $this->ticks_label[$i]=$label;
4663 if( $i % $step == 0 && $j < $nbrmajticks) {
4664 $this->maj_ticks_pos[$j] = round($abs_pos);
4665 $this->maj_ticklabels_pos[$j] = round($abs_pos);
4666 $this->maj_ticks_label[$j]=$this->_doLabelFormat($label,$j,$nbrmajticks);
4667 ++$j;
4669 ++$i;
4670 $abs_pos += $min_step_abs;
4671 $label += $this->minor_step;
4677 function _doLabelFormat($aVal,$aIdx,$aNbrTicks) {
4679 // If precision hasn't been specified set it to a sensible value
4680 if( $this->precision==-1 ) {
4681 $t = log10($this->minor_step);
4682 if( $t > 0 )
4683 $precision = 0;
4684 else
4685 $precision = -floor($t);
4687 else
4688 $precision = $this->precision;
4690 if( $this->label_formfunc != '' ) {
4691 $f=$this->label_formfunc;
4692 $l = call_user_func($f,$aVal);
4694 elseif( $this->label_formatstr != '' || $this->label_dateformatstr != '' ) {
4695 if( $this->label_usedateformat ) {
4696 $l = date($this->label_formatstr,$aVal);
4698 else {
4699 if( $this->label_dateformatstr !== '' )
4700 $l = date($this->label_dateformatstr,$aVal);
4701 else
4702 $l = sprintf($this->label_formatstr,$aVal);
4705 else {
4706 $l = sprintf('%01.'.$precision.'f',round($aVal,$precision));
4709 if( ($this->supress_zerolabel && $l==0) || ($this->supress_first && $aIdx==0) ||
4710 ($this->supress_last && $aIdx==$aNbrTicks-1) ) {
4711 $l='';
4713 return $l;
4716 // Stroke ticks on either X or Y axis
4717 function _StrokeTicks(&$aImg,$aScale,$aPos) {
4718 $hor = $aScale->type == 'x';
4719 $aImg->SetLineWeight($this->weight);
4721 // We need to make this an int since comparing it below
4722 // with the result from round() can give wrong result, such that
4723 // (40 < 40) == TRUE !!!
4724 $limit = (int)$aScale->scale_abs[1];
4726 // A text scale doesn't have any minor ticks
4727 if( !$aScale->textscale ) {
4728 // Stroke minor ticks
4729 $yu = $aPos - $this->direction*$this->GetMinTickAbsSize();
4730 $xr = $aPos + $this->direction*$this->GetMinTickAbsSize();
4731 $n = count($this->ticks_pos);
4732 for($i=0; $i < $n; ++$i ) {
4733 if( !$this->supress_tickmarks && !$this->supress_minor_tickmarks) {
4734 if( $this->mincolor!="" ) $aImg->PushColor($this->mincolor);
4735 if( $hor ) {
4736 //if( $this->ticks_pos[$i] <= $limit )
4737 $aImg->Line($this->ticks_pos[$i],$aPos,$this->ticks_pos[$i],$yu);
4739 else {
4740 //if( $this->ticks_pos[$i] >= $limit )
4741 $aImg->Line($aPos,$this->ticks_pos[$i],$xr,$this->ticks_pos[$i]);
4743 if( $this->mincolor!="" ) $aImg->PopColor();
4748 // Stroke major ticks
4749 $yu = $aPos - $this->direction*$this->GetMajTickAbsSize();
4750 $xr = $aPos + $this->direction*$this->GetMajTickAbsSize();
4751 $nbrmajticks=ceil(($aScale->GetMaxVal()-$aScale->GetMinVal()-$this->text_label_start )/$this->major_step)+1;
4752 $n = count($this->maj_ticks_pos);
4753 for($i=0; $i < $n ; ++$i ) {
4754 if(!($this->xtick_offset > 0 && $i==$nbrmajticks-1) && !$this->supress_tickmarks) {
4755 if( $this->majcolor!="" ) $aImg->PushColor($this->majcolor);
4756 if( $hor ) {
4757 //if( $this->maj_ticks_pos[$i] <= $limit )
4758 $aImg->Line($this->maj_ticks_pos[$i],$aPos,$this->maj_ticks_pos[$i],$yu);
4760 else {
4761 //if( $this->maj_ticks_pos[$i] >= $limit )
4762 $aImg->Line($aPos,$this->maj_ticks_pos[$i],$xr,$this->maj_ticks_pos[$i]);
4764 if( $this->majcolor!="" ) $aImg->PopColor();
4770 // Draw linear ticks
4771 function Stroke(&$aImg,$aScale,$aPos) {
4772 if( $this->iManualTickPos != NULL )
4773 $this->_doManualTickPos($aScale);
4774 else
4775 $this->_doAutoTickPos($aScale);
4776 $this->_StrokeTicks($aImg,$aScale,$aPos, $aScale->type == 'x' );
4779 //---------------
4780 // PRIVATE METHODS
4781 // Spoecify the offset of the displayed tick mark with the tick "space"
4782 // Legal values for $o is [0,1] used to adjust where the tick marks and label
4783 // should be positioned within the major tick-size
4784 // $lo specifies the label offset and $to specifies the tick offset
4785 // this comes in handy for example in bar graphs where we wont no offset for the
4786 // tick but have the labels displayed halfway under the bars.
4787 function SetXLabelOffset($aLabelOff,$aTickOff=-1) {
4788 $this->xlabel_offset=$aLabelOff;
4789 if( $aTickOff==-1 ) // Same as label offset
4790 $this->xtick_offset=$aLabelOff;
4791 else
4792 $this->xtick_offset=$aTickOff;
4793 if( $aLabelOff>0 )
4794 $this->SupressLast(); // The last tick wont fit
4797 // Which tick label should we start with?
4798 function SetTextLabelStart($aTextLabelOff) {
4799 $this->text_label_start=$aTextLabelOff;
4802 } // Class
4804 //===================================================
4805 // CLASS LinearScale
4806 // Description: Handle linear scaling between screen and world
4807 //===================================================
4808 class LinearScale {
4809 var $scale=array(0,0);
4810 var $scale_abs=array(0,0);
4811 var $scale_factor; // Scale factor between world and screen
4812 var $world_size; // Plot area size in world coordinates
4813 var $world_abs_size; // Plot area size in pixels
4814 var $off; // Offset between image edge and plot area
4815 var $type; // is this x or y scale ?
4816 var $ticks=null; // Store ticks
4817 var $text_scale_off = 0;
4818 var $autoscale_min=false; // Forced minimum value, auto determine max
4819 var $autoscale_max=false; // Forced maximum value, auto determine min
4820 var $gracetop=0,$gracebottom=0;
4821 var $intscale=false; // Restrict autoscale to integers
4822 var $textscale=false; // Just a flag to let the Plot class find out if
4823 // we are a textscale or not. This is a cludge since
4824 // this ionformatyion is availabale in Graph::axtype but
4825 // we don't have access to the graph object in the Plots
4826 // stroke method. So we let graph store the status here
4827 // when the linear scale is created. A real cludge...
4828 var $auto_ticks=false; // When using manual scale should the ticks be automatically set?
4829 var $name = 'lin';
4830 //---------------
4831 // CONSTRUCTOR
4832 function __construct($aMin=0,$aMax=0,$aType="y") {
4833 assert($aType=="x" || $aType=="y" );
4834 assert($aMin<=$aMax);
4836 $this->type=$aType;
4837 $this->scale=array($aMin,$aMax);
4838 $this->world_size=$aMax-$aMin;
4839 $this->ticks = new LinearTicks();
4842 //---------------
4843 // PUBLIC METHODS
4844 // Check if scale is set or if we should autoscale
4845 // We should do this is either scale or ticks has not been set
4846 function IsSpecified() {
4847 if( $this->GetMinVal()==$this->GetMaxVal() ) { // Scale not set
4848 return false;
4850 return true;
4853 // Set the minimum data value when the autoscaling is used.
4854 // Usefull if you want a fix minimum (like 0) but have an
4855 // automatic maximum
4856 function SetAutoMin($aMin) {
4857 $this->autoscale_min=$aMin;
4860 // Set the minimum data value when the autoscaling is used.
4861 // Usefull if you want a fix minimum (like 0) but have an
4862 // automatic maximum
4863 function SetAutoMax($aMax) {
4864 $this->autoscale_max=$aMax;
4867 // If the user manually specifies a scale should the ticks
4868 // still be set automatically?
4869 function SetAutoTicks($aFlag=true) {
4870 $this->auto_ticks = $aFlag;
4873 // Specify scale "grace" value (top and bottom)
4874 function SetGrace($aGraceTop,$aGraceBottom=0) {
4875 if( $aGraceTop<0 || $aGraceBottom < 0 )
4876 JpGraphError::RaiseL(25069);//(" Grace must be larger then 0");
4877 $this->gracetop=$aGraceTop;
4878 $this->gracebottom=$aGraceBottom;
4881 // Get the minimum value in the scale
4882 function GetMinVal() {
4883 return $this->scale[0];
4886 // get maximum value for scale
4887 function GetMaxVal() {
4888 return $this->scale[1];
4891 // Specify a new min/max value for sclae
4892 function Update(&$aImg,$aMin,$aMax) {
4893 $this->scale=array($aMin,$aMax);
4894 $this->world_size=$aMax-$aMin;
4895 $this->InitConstants($aImg);
4898 // Translate between world and screen
4899 function Translate($aCoord) {
4900 if( !is_numeric($aCoord) ) {
4901 if( $aCoord != '' && $aCoord != '-' && $aCoord != 'x' )
4902 JpGraphError::RaiseL(25070);//('Your data contains non-numeric values.');
4903 return 0;
4905 else {
4906 return $this->off + ($aCoord - $this->scale[0])*$this->scale_factor;
4910 // Relative translate (don't include offset) usefull when we just want
4911 // to know the relative position (in pixels) on the axis
4912 function RelTranslate($aCoord) {
4913 if( !is_numeric($aCoord) ) {
4914 if( $aCoord != '' && $aCoord != '-' && $aCoord != 'x' )
4915 JpGraphError::RaiseL(25070);//('Your data contains non-numeric values.');
4916 return 0;
4918 else {
4919 return ($aCoord - $this->scale[0]) * $this->scale_factor;
4923 // Restrict autoscaling to only use integers
4924 function SetIntScale($aIntScale=true) {
4925 $this->intscale=$aIntScale;
4928 // Calculate an integer autoscale
4929 function IntAutoScale(&$img,$min,$max,$maxsteps,$majend=true) {
4930 // Make sure limits are integers
4931 $min=floor($min);
4932 $max=ceil($max);
4933 if( abs($min-$max)==0 ) {
4934 --$min; ++$max;
4936 $maxsteps = floor($maxsteps);
4938 $gracetop=round(($this->gracetop/100.0)*abs($max-$min));
4939 $gracebottom=round(($this->gracebottom/100.0)*abs($max-$min));
4940 if( is_numeric($this->autoscale_min) ) {
4941 $min = ceil($this->autoscale_min);
4942 if( $min >= $max ) {
4943 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.');
4947 if( is_numeric($this->autoscale_max) ) {
4948 $max = ceil($this->autoscale_max);
4949 if( $min >= $max ) {
4950 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.');
4954 if( abs($min-$max ) == 0 ) {
4955 ++$max;
4956 --$min;
4959 $min -= $gracebottom;
4960 $max += $gracetop;
4962 // First get tickmarks as multiples of 1, 10, ...
4963 if( $majend ) {
4964 list($num1steps,$adj1min,$adj1max,$maj1step) =
4965 $this->IntCalcTicks($maxsteps,$min,$max,1);
4967 else {
4968 $adj1min = $min;
4969 $adj1max = $max;
4970 list($num1steps,$maj1step) =
4971 $this->IntCalcTicksFreeze($maxsteps,$min,$max,1);
4974 if( abs($min-$max) > 2 ) {
4975 // Then get tick marks as 2:s 2, 20, ...
4976 if( $majend ) {
4977 list($num2steps,$adj2min,$adj2max,$maj2step) =
4978 $this->IntCalcTicks($maxsteps,$min,$max,5);
4980 else {
4981 $adj2min = $min;
4982 $adj2max = $max;
4983 list($num2steps,$maj2step) =
4984 $this->IntCalcTicksFreeze($maxsteps,$min,$max,5);
4987 else {
4988 $num2steps = 10000; // Dummy high value so we don't choose this
4991 if( abs($min-$max) > 5 ) {
4992 // Then get tickmarks as 5:s 5, 50, 500, ...
4993 if( $majend ) {
4994 list($num5steps,$adj5min,$adj5max,$maj5step) =
4995 $this->IntCalcTicks($maxsteps,$min,$max,2);
4997 else {
4998 $adj5min = $min;
4999 $adj5max = $max;
5000 list($num5steps,$maj5step) =
5001 $this->IntCalcTicksFreeze($maxsteps,$min,$max,2);
5004 else {
5005 $num5steps = 10000; // Dummy high value so we don't choose this
5008 // Check to see whichof 1:s, 2:s or 5:s fit better with
5009 // the requested number of major ticks
5010 $match1=abs($num1steps-$maxsteps);
5011 $match2=abs($num2steps-$maxsteps);
5012 if( !empty($maj5step) && $maj5step > 1 )
5013 $match5=abs($num5steps-$maxsteps);
5014 else
5015 $match5=10000; // Dummy high value
5017 // Compare these three values and see which is the closest match
5018 // We use a 0.6 weight to gravitate towards multiple of 5:s
5019 if( $match1 < $match2 ) {
5020 if( $match1 < $match5 )
5021 $r=1;
5022 else
5023 $r=3;
5025 else {
5026 if( $match2 < $match5 )
5027 $r=2;
5028 else
5029 $r=3;
5031 // Minsteps are always the same as maxsteps for integer scale
5032 switch( $r ) {
5033 case 1:
5034 $this->ticks->Set($maj1step,$maj1step);
5035 $this->Update($img,$adj1min,$adj1max);
5036 break;
5037 case 2:
5038 $this->ticks->Set($maj2step,$maj2step);
5039 $this->Update($img,$adj2min,$adj2max);
5040 break;
5041 case 3:
5042 $this->ticks->Set($maj5step,$maj5step);
5043 $this->Update($img,$adj5min,$adj5max);
5044 break;
5045 default:
5046 JpGraphError::RaiseL(25073,$r);//('Internal error. Integer scale algorithm comparison out of bound (r=$r)');
5051 // Calculate autoscale. Used if user hasn't given a scale and ticks
5052 // $maxsteps is the maximum number of major tickmarks allowed.
5053 function AutoScale(&$img,$min,$max,$maxsteps,$majend=true) {
5054 if( $this->intscale ) {
5055 $this->IntAutoScale($img,$min,$max,$maxsteps,$majend);
5056 return;
5058 if( abs($min-$max) < 0.00001 ) {
5059 // We need some difference to be able to autoscale
5060 // make it 5% above and 5% below value
5061 if( $min==0 && $max==0 ) { // Special case
5062 $min=-1; $max=1;
5064 else {
5065 $delta = (abs($max)+abs($min))*0.005;
5066 $min -= $delta;
5067 $max += $delta;
5071 $gracetop=($this->gracetop/100.0)*abs($max-$min);
5072 $gracebottom=($this->gracebottom/100.0)*abs($max-$min);
5073 if( is_numeric($this->autoscale_min) ) {
5074 $min = $this->autoscale_min;
5075 if( $min >= $max ) {
5076 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.');
5078 if( abs($min-$max ) < 0.00001 )
5079 $max *= 1.2;
5082 if( is_numeric($this->autoscale_max) ) {
5083 $max = $this->autoscale_max;
5084 if( $min >= $max ) {
5085 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.');
5087 if( abs($min-$max ) < 0.00001 )
5088 $min *= 0.8;
5092 $min -= $gracebottom;
5093 $max += $gracetop;
5095 // First get tickmarks as multiples of 0.1, 1, 10, ...
5096 if( $majend ) {
5097 list($num1steps,$adj1min,$adj1max,$min1step,$maj1step) =
5098 $this->CalcTicks($maxsteps,$min,$max,1,2);
5100 else {
5101 $adj1min=$min;
5102 $adj1max=$max;
5103 list($num1steps,$min1step,$maj1step) =
5104 $this->CalcTicksFreeze($maxsteps,$min,$max,1,2,false);
5107 // Then get tick marks as 2:s 0.2, 2, 20, ...
5108 if( $majend ) {
5109 list($num2steps,$adj2min,$adj2max,$min2step,$maj2step) =
5110 $this->CalcTicks($maxsteps,$min,$max,5,2);
5112 else {
5113 $adj2min=$min;
5114 $adj2max=$max;
5115 list($num2steps,$min2step,$maj2step) =
5116 $this->CalcTicksFreeze($maxsteps,$min,$max,5,2,false);
5119 // Then get tickmarks as 5:s 0.05, 0.5, 5, 50, ...
5120 if( $majend ) {
5121 list($num5steps,$adj5min,$adj5max,$min5step,$maj5step) =
5122 $this->CalcTicks($maxsteps,$min,$max,2,5);
5124 else {
5125 $adj5min=$min;
5126 $adj5max=$max;
5127 list($num5steps,$min5step,$maj5step) =
5128 $this->CalcTicksFreeze($maxsteps,$min,$max,2,5,false);
5131 // Check to see whichof 1:s, 2:s or 5:s fit better with
5132 // the requested number of major ticks
5133 $match1=abs($num1steps-$maxsteps);
5134 $match2=abs($num2steps-$maxsteps);
5135 $match5=abs($num5steps-$maxsteps);
5136 // Compare these three values and see which is the closest match
5137 // We use a 0.8 weight to gravitate towards multiple of 5:s
5138 $r=$this->MatchMin3($match1,$match2,$match5,0.8);
5139 switch( $r ) {
5140 case 1:
5141 $this->Update($img,$adj1min,$adj1max);
5142 $this->ticks->Set($maj1step,$min1step);
5143 break;
5144 case 2:
5145 $this->Update($img,$adj2min,$adj2max);
5146 $this->ticks->Set($maj2step,$min2step);
5147 break;
5148 case 3:
5149 $this->Update($img,$adj5min,$adj5max);
5150 $this->ticks->Set($maj5step,$min5step);
5151 break;
5155 //---------------
5156 // PRIVATE METHODS
5158 // This method recalculates all constants that are depending on the
5159 // margins in the image. If the margins in the image are changed
5160 // this method should be called for every scale that is registred with
5161 // that image. Should really be installed as an observer of that image.
5162 function InitConstants(&$img) {
5163 if( $this->type=="x" ) {
5164 $this->world_abs_size=$img->width - $img->left_margin - $img->right_margin;
5165 $this->off=$img->left_margin;
5166 $this->scale_factor = 0;
5167 if( $this->world_size > 0 )
5168 $this->scale_factor=$this->world_abs_size/($this->world_size*1.0);
5170 else { // y scale
5171 $this->world_abs_size=$img->height - $img->top_margin - $img->bottom_margin;
5172 $this->off=$img->top_margin+$this->world_abs_size;
5173 $this->scale_factor = 0;
5174 if( $this->world_size > 0 ) {
5175 $this->scale_factor=-$this->world_abs_size/($this->world_size*1.0);
5178 $size = $this->world_size * $this->scale_factor;
5179 $this->scale_abs=array($this->off,$this->off + $size);
5182 // Initialize the conversion constants for this scale
5183 // This tries to pre-calculate as much as possible to speed up the
5184 // actual conversion (with Translate()) later on
5185 // $start =scale start in absolute pixels (for x-scale this is an y-position
5186 // and for an y-scale this is an x-position
5187 // $len =absolute length in pixels of scale
5188 function SetConstants($aStart,$aLen) {
5189 $this->world_abs_size=$aLen;
5190 $this->off=$aStart;
5192 if( $this->world_size<=0 ) {
5193 // This should never ever happen !!
5194 JpGraphError::RaiseL(25074);
5195 //("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.");
5199 // scale_factor = number of pixels per world unit
5200 $this->scale_factor=$this->world_abs_size/($this->world_size*1.0);
5202 // scale_abs = start and end points of scale in absolute pixels
5203 $this->scale_abs=array($this->off,$this->off+$this->world_size*$this->scale_factor);
5207 // Calculate number of ticks steps with a specific division
5208 // $a is the divisor of 10**x to generate the first maj tick intervall
5209 // $a=1, $b=2 give major ticks with multiple of 10, ...,0.1,1,10,...
5210 // $a=5, $b=2 give major ticks with multiple of 2:s ...,0.2,2,20,...
5211 // $a=2, $b=5 give major ticks with multiple of 5:s ...,0.5,5,50,...
5212 // We return a vector of
5213 // [$numsteps,$adjmin,$adjmax,$minstep,$majstep]
5214 // If $majend==true then the first and last marks on the axis will be major
5215 // labeled tick marks otherwise it will be adjusted to the closest min tick mark
5216 function CalcTicks($maxsteps,$min,$max,$a,$b,$majend=true) {
5217 $diff=$max-$min;
5218 if( $diff==0 )
5219 $ld=0;
5220 else
5221 $ld=floor(log10($diff));
5223 // Gravitate min towards zero if we are close
5224 if( $min>0 && $min < pow(10,$ld) ) $min=0;
5226 //$majstep=pow(10,$ld-1)/$a;
5227 $majstep=pow(10,$ld)/$a;
5228 $minstep=$majstep/$b;
5230 $adjmax=ceil($max/$minstep)*$minstep;
5231 $adjmin=floor($min/$minstep)*$minstep;
5232 $adjdiff = $adjmax-$adjmin;
5233 $numsteps=$adjdiff/$majstep;
5235 while( $numsteps>$maxsteps ) {
5236 $majstep=pow(10,$ld)/$a;
5237 $numsteps=$adjdiff/$majstep;
5238 ++$ld;
5241 $minstep=$majstep/$b;
5242 $adjmin=floor($min/$minstep)*$minstep;
5243 $adjdiff = $adjmax-$adjmin;
5244 if( $majend ) {
5245 $adjmin = floor($min/$majstep)*$majstep;
5246 $adjdiff = $adjmax-$adjmin;
5247 $adjmax = ceil($adjdiff/$majstep)*$majstep+$adjmin;
5249 else
5250 $adjmax=ceil($max/$minstep)*$minstep;
5252 return array($numsteps,$adjmin,$adjmax,$minstep,$majstep);
5255 function CalcTicksFreeze($maxsteps,$min,$max,$a,$b) {
5256 // Same as CalcTicks but don't adjust min/max values
5257 $diff=$max-$min;
5258 if( $diff==0 )
5259 $ld=0;
5260 else
5261 $ld=floor(log10($diff));
5263 //$majstep=pow(10,$ld-1)/$a;
5264 $majstep=pow(10,$ld)/$a;
5265 $minstep=$majstep/$b;
5266 $numsteps=floor($diff/$majstep);
5268 while( $numsteps > $maxsteps ) {
5269 $majstep=pow(10,$ld)/$a;
5270 $numsteps=floor($diff/$majstep);
5271 ++$ld;
5273 $minstep=$majstep/$b;
5274 return array($numsteps,$minstep,$majstep);
5278 function IntCalcTicks($maxsteps,$min,$max,$a,$majend=true) {
5279 $diff=$max-$min;
5280 if( $diff==0 )
5281 JpGraphError::RaiseL(25075);//('Can\'t automatically determine ticks since min==max.');
5282 else
5283 $ld=floor(log10($diff));
5285 // Gravitate min towards zero if we are close
5286 if( $min>0 && $min < pow(10,$ld) ) $min=0;
5288 if( $ld == 0 ) $ld=1;
5290 if( $a == 1 )
5291 $majstep = 1;
5292 else
5293 $majstep=pow(10,$ld)/$a;
5294 $adjmax=ceil($max/$majstep)*$majstep;
5296 $adjmin=floor($min/$majstep)*$majstep;
5297 $adjdiff = $adjmax-$adjmin;
5298 $numsteps=$adjdiff/$majstep;
5299 while( $numsteps>$maxsteps ) {
5300 $majstep=pow(10,$ld)/$a;
5301 $numsteps=$adjdiff/$majstep;
5302 ++$ld;
5305 $adjmin=floor($min/$majstep)*$majstep;
5306 $adjdiff = $adjmax-$adjmin;
5307 if( $majend ) {
5308 $adjmin = floor($min/$majstep)*$majstep;
5309 $adjdiff = $adjmax-$adjmin;
5310 $adjmax = ceil($adjdiff/$majstep)*$majstep+$adjmin;
5312 else
5313 $adjmax=ceil($max/$majstep)*$majstep;
5315 return array($numsteps,$adjmin,$adjmax,$majstep);
5319 function IntCalcTicksFreeze($maxsteps,$min,$max,$a) {
5320 // Same as IntCalcTick but don't change min/max values
5321 $diff=$max-$min;
5322 if( $diff==0 )
5323 JpGraphError::RaiseL(25075);//('Can\'t automatically determine ticks since min==max.');
5324 else
5325 $ld=floor(log10($diff));
5327 if( $ld == 0 ) $ld=1;
5329 if( $a == 1 )
5330 $majstep = 1;
5331 else
5332 $majstep=pow(10,$ld)/$a;
5334 $numsteps=floor($diff/$majstep);
5335 while( $numsteps > $maxsteps ) {
5336 $majstep=pow(10,$ld)/$a;
5337 $numsteps=floor($diff/$majstep);
5338 ++$ld;
5341 return array($numsteps,$majstep);
5346 // Determine the minimum of three values witha weight for last value
5347 function MatchMin3($a,$b,$c,$weight) {
5348 if( $a < $b ) {
5349 if( $a < ($c*$weight) )
5350 return 1; // $a smallest
5351 else
5352 return 3; // $c smallest
5354 elseif( $b < ($c*$weight) )
5355 return 2; // $b smallest
5356 return 3; // $c smallest
5358 } // Class
5360 //===================================================
5361 // CLASS RGB
5362 // Description: Color definitions as RGB triples
5363 //===================================================
5364 class RGB {
5365 var $rgb_table;
5366 var $img;
5367 function __construct(&$aImg) {
5368 $this->img = &$aImg;
5370 // Conversion array between color names and RGB
5371 $this->rgb_table = array(
5372 "aqua"=> array(0,255,255),
5373 "lime"=> array(0,255,0),
5374 "teal"=> array(0,128,128),
5375 "whitesmoke"=>array(245,245,245),
5376 "gainsboro"=>array(220,220,220),
5377 "oldlace"=>array(253,245,230),
5378 "linen"=>array(250,240,230),
5379 "antiquewhite"=>array(250,235,215),
5380 "papayawhip"=>array(255,239,213),
5381 "blanchedalmond"=>array(255,235,205),
5382 "bisque"=>array(255,228,196),
5383 "peachpuff"=>array(255,218,185),
5384 "navajowhite"=>array(255,222,173),
5385 "moccasin"=>array(255,228,181),
5386 "cornsilk"=>array(255,248,220),
5387 "ivory"=>array(255,255,240),
5388 "lemonchiffon"=>array(255,250,205),
5389 "seashell"=>array(255,245,238),
5390 "mintcream"=>array(245,255,250),
5391 "azure"=>array(240,255,255),
5392 "aliceblue"=>array(240,248,255),
5393 "lavender"=>array(230,230,250),
5394 "lavenderblush"=>array(255,240,245),
5395 "mistyrose"=>array(255,228,225),
5396 "white"=>array(255,255,255),
5397 "black"=>array(0,0,0),
5398 "darkslategray"=>array(47,79,79),
5399 "dimgray"=>array(105,105,105),
5400 "slategray"=>array(112,128,144),
5401 "lightslategray"=>array(119,136,153),
5402 "gray"=>array(190,190,190),
5403 "lightgray"=>array(211,211,211),
5404 "midnightblue"=>array(25,25,112),
5405 "navy"=>array(0,0,128),
5406 "cornflowerblue"=>array(100,149,237),
5407 "darkslateblue"=>array(72,61,139),
5408 "slateblue"=>array(106,90,205),
5409 "mediumslateblue"=>array(123,104,238),
5410 "lightslateblue"=>array(132,112,255),
5411 "mediumblue"=>array(0,0,205),
5412 "royalblue"=>array(65,105,225),
5413 "blue"=>array(0,0,255),
5414 "dodgerblue"=>array(30,144,255),
5415 "deepskyblue"=>array(0,191,255),
5416 "skyblue"=>array(135,206,235),
5417 "lightskyblue"=>array(135,206,250),
5418 "steelblue"=>array(70,130,180),
5419 "lightred"=>array(211,167,168),
5420 "lightsteelblue"=>array(176,196,222),
5421 "lightblue"=>array(173,216,230),
5422 "powderblue"=>array(176,224,230),
5423 "paleturquoise"=>array(175,238,238),
5424 "darkturquoise"=>array(0,206,209),
5425 "mediumturquoise"=>array(72,209,204),
5426 "turquoise"=>array(64,224,208),
5427 "cyan"=>array(0,255,255),
5428 "lightcyan"=>array(224,255,255),
5429 "cadetblue"=>array(95,158,160),
5430 "mediumaquamarine"=>array(102,205,170),
5431 "aquamarine"=>array(127,255,212),
5432 "darkgreen"=>array(0,100,0),
5433 "darkolivegreen"=>array(85,107,47),
5434 "darkseagreen"=>array(143,188,143),
5435 "seagreen"=>array(46,139,87),
5436 "mediumseagreen"=>array(60,179,113),
5437 "lightseagreen"=>array(32,178,170),
5438 "palegreen"=>array(152,251,152),
5439 "springgreen"=>array(0,255,127),
5440 "lawngreen"=>array(124,252,0),
5441 "green"=>array(0,255,0),
5442 "chartreuse"=>array(127,255,0),
5443 "mediumspringgreen"=>array(0,250,154),
5444 "greenyellow"=>array(173,255,47),
5445 "limegreen"=>array(50,205,50),
5446 "yellowgreen"=>array(154,205,50),
5447 "forestgreen"=>array(34,139,34),
5448 "olivedrab"=>array(107,142,35),
5449 "darkkhaki"=>array(189,183,107),
5450 "khaki"=>array(240,230,140),
5451 "palegoldenrod"=>array(238,232,170),
5452 "lightgoldenrodyellow"=>array(250,250,210),
5453 "lightyellow"=>array(255,255,200),
5454 "yellow"=>array(255,255,0),
5455 "gold"=>array(255,215,0),
5456 "lightgoldenrod"=>array(238,221,130),
5457 "goldenrod"=>array(218,165,32),
5458 "darkgoldenrod"=>array(184,134,11),
5459 "rosybrown"=>array(188,143,143),
5460 "indianred"=>array(205,92,92),
5461 "saddlebrown"=>array(139,69,19),
5462 "sienna"=>array(160,82,45),
5463 "peru"=>array(205,133,63),
5464 "burlywood"=>array(222,184,135),
5465 "beige"=>array(245,245,220),
5466 "wheat"=>array(245,222,179),
5467 "sandybrown"=>array(244,164,96),
5468 "tan"=>array(210,180,140),
5469 "chocolate"=>array(210,105,30),
5470 "firebrick"=>array(178,34,34),
5471 "brown"=>array(165,42,42),
5472 "darksalmon"=>array(233,150,122),
5473 "salmon"=>array(250,128,114),
5474 "lightsalmon"=>array(255,160,122),
5475 "orange"=>array(255,165,0),
5476 "darkorange"=>array(255,140,0),
5477 "coral"=>array(255,127,80),
5478 "lightcoral"=>array(240,128,128),
5479 "tomato"=>array(255,99,71),
5480 "orangered"=>array(255,69,0),
5481 "red"=>array(255,0,0),
5482 "hotpink"=>array(255,105,180),
5483 "deeppink"=>array(255,20,147),
5484 "pink"=>array(255,192,203),
5485 "lightpink"=>array(255,182,193),
5486 "palevioletred"=>array(219,112,147),
5487 "maroon"=>array(176,48,96),
5488 "mediumvioletred"=>array(199,21,133),
5489 "violetred"=>array(208,32,144),
5490 "magenta"=>array(255,0,255),
5491 "violet"=>array(238,130,238),
5492 "plum"=>array(221,160,221),
5493 "orchid"=>array(218,112,214),
5494 "mediumorchid"=>array(186,85,211),
5495 "darkorchid"=>array(153,50,204),
5496 "darkviolet"=>array(148,0,211),
5497 "blueviolet"=>array(138,43,226),
5498 "purple"=>array(160,32,240),
5499 "mediumpurple"=>array(147,112,219),
5500 "thistle"=>array(216,191,216),
5501 "snow1"=>array(255,250,250),
5502 "snow2"=>array(238,233,233),
5503 "snow3"=>array(205,201,201),
5504 "snow4"=>array(139,137,137),
5505 "seashell1"=>array(255,245,238),
5506 "seashell2"=>array(238,229,222),
5507 "seashell3"=>array(205,197,191),
5508 "seashell4"=>array(139,134,130),
5509 "AntiqueWhite1"=>array(255,239,219),
5510 "AntiqueWhite2"=>array(238,223,204),
5511 "AntiqueWhite3"=>array(205,192,176),
5512 "AntiqueWhite4"=>array(139,131,120),
5513 "bisque1"=>array(255,228,196),
5514 "bisque2"=>array(238,213,183),
5515 "bisque3"=>array(205,183,158),
5516 "bisque4"=>array(139,125,107),
5517 "peachPuff1"=>array(255,218,185),
5518 "peachpuff2"=>array(238,203,173),
5519 "peachpuff3"=>array(205,175,149),
5520 "peachpuff4"=>array(139,119,101),
5521 "navajowhite1"=>array(255,222,173),
5522 "navajowhite2"=>array(238,207,161),
5523 "navajowhite3"=>array(205,179,139),
5524 "navajowhite4"=>array(139,121,94),
5525 "lemonchiffon1"=>array(255,250,205),
5526 "lemonchiffon2"=>array(238,233,191),
5527 "lemonchiffon3"=>array(205,201,165),
5528 "lemonchiffon4"=>array(139,137,112),
5529 "ivory1"=>array(255,255,240),
5530 "ivory2"=>array(238,238,224),
5531 "ivory3"=>array(205,205,193),
5532 "ivory4"=>array(139,139,131),
5533 "honeydew"=>array(193,205,193),
5534 "lavenderblush1"=>array(255,240,245),
5535 "lavenderblush2"=>array(238,224,229),
5536 "lavenderblush3"=>array(205,193,197),
5537 "lavenderblush4"=>array(139,131,134),
5538 "mistyrose1"=>array(255,228,225),
5539 "mistyrose2"=>array(238,213,210),
5540 "mistyrose3"=>array(205,183,181),
5541 "mistyrose4"=>array(139,125,123),
5542 "azure1"=>array(240,255,255),
5543 "azure2"=>array(224,238,238),
5544 "azure3"=>array(193,205,205),
5545 "azure4"=>array(131,139,139),
5546 "slateblue1"=>array(131,111,255),
5547 "slateblue2"=>array(122,103,238),
5548 "slateblue3"=>array(105,89,205),
5549 "slateblue4"=>array(71,60,139),
5550 "royalblue1"=>array(72,118,255),
5551 "royalblue2"=>array(67,110,238),
5552 "royalblue3"=>array(58,95,205),
5553 "royalblue4"=>array(39,64,139),
5554 "dodgerblue1"=>array(30,144,255),
5555 "dodgerblue2"=>array(28,134,238),
5556 "dodgerblue3"=>array(24,116,205),
5557 "dodgerblue4"=>array(16,78,139),
5558 "steelblue1"=>array(99,184,255),
5559 "steelblue2"=>array(92,172,238),
5560 "steelblue3"=>array(79,148,205),
5561 "steelblue4"=>array(54,100,139),
5562 "deepskyblue1"=>array(0,191,255),
5563 "deepskyblue2"=>array(0,178,238),
5564 "deepskyblue3"=>array(0,154,205),
5565 "deepskyblue4"=>array(0,104,139),
5566 "skyblue1"=>array(135,206,255),
5567 "skyblue2"=>array(126,192,238),
5568 "skyblue3"=>array(108,166,205),
5569 "skyblue4"=>array(74,112,139),
5570 "lightskyblue1"=>array(176,226,255),
5571 "lightskyblue2"=>array(164,211,238),
5572 "lightskyblue3"=>array(141,182,205),
5573 "lightskyblue4"=>array(96,123,139),
5574 "slategray1"=>array(198,226,255),
5575 "slategray2"=>array(185,211,238),
5576 "slategray3"=>array(159,182,205),
5577 "slategray4"=>array(108,123,139),
5578 "lightsteelblue1"=>array(202,225,255),
5579 "lightsteelblue2"=>array(188,210,238),
5580 "lightsteelblue3"=>array(162,181,205),
5581 "lightsteelblue4"=>array(110,123,139),
5582 "lightblue1"=>array(191,239,255),
5583 "lightblue2"=>array(178,223,238),
5584 "lightblue3"=>array(154,192,205),
5585 "lightblue4"=>array(104,131,139),
5586 "lightcyan1"=>array(224,255,255),
5587 "lightcyan2"=>array(209,238,238),
5588 "lightcyan3"=>array(180,205,205),
5589 "lightcyan4"=>array(122,139,139),
5590 "paleturquoise1"=>array(187,255,255),
5591 "paleturquoise2"=>array(174,238,238),
5592 "paleturquoise3"=>array(150,205,205),
5593 "paleturquoise4"=>array(102,139,139),
5594 "cadetblue1"=>array(152,245,255),
5595 "cadetblue2"=>array(142,229,238),
5596 "cadetblue3"=>array(122,197,205),
5597 "cadetblue4"=>array(83,134,139),
5598 "turquoise1"=>array(0,245,255),
5599 "turquoise2"=>array(0,229,238),
5600 "turquoise3"=>array(0,197,205),
5601 "turquoise4"=>array(0,134,139),
5602 "cyan1"=>array(0,255,255),
5603 "cyan2"=>array(0,238,238),
5604 "cyan3"=>array(0,205,205),
5605 "cyan4"=>array(0,139,139),
5606 "darkslategray1"=>array(151,255,255),
5607 "darkslategray2"=>array(141,238,238),
5608 "darkslategray3"=>array(121,205,205),
5609 "darkslategray4"=>array(82,139,139),
5610 "aquamarine1"=>array(127,255,212),
5611 "aquamarine2"=>array(118,238,198),
5612 "aquamarine3"=>array(102,205,170),
5613 "aquamarine4"=>array(69,139,116),
5614 "darkseagreen1"=>array(193,255,193),
5615 "darkseagreen2"=>array(180,238,180),
5616 "darkseagreen3"=>array(155,205,155),
5617 "darkseagreen4"=>array(105,139,105),
5618 "seagreen1"=>array(84,255,159),
5619 "seagreen2"=>array(78,238,148),
5620 "seagreen3"=>array(67,205,128),
5621 "seagreen4"=>array(46,139,87),
5622 "palegreen1"=>array(154,255,154),
5623 "palegreen2"=>array(144,238,144),
5624 "palegreen3"=>array(124,205,124),
5625 "palegreen4"=>array(84,139,84),
5626 "springgreen1"=>array(0,255,127),
5627 "springgreen2"=>array(0,238,118),
5628 "springgreen3"=>array(0,205,102),
5629 "springgreen4"=>array(0,139,69),
5630 "chartreuse1"=>array(127,255,0),
5631 "chartreuse2"=>array(118,238,0),
5632 "chartreuse3"=>array(102,205,0),
5633 "chartreuse4"=>array(69,139,0),
5634 "olivedrab1"=>array(192,255,62),
5635 "olivedrab2"=>array(179,238,58),
5636 "olivedrab3"=>array(154,205,50),
5637 "olivedrab4"=>array(105,139,34),
5638 "darkolivegreen1"=>array(202,255,112),
5639 "darkolivegreen2"=>array(188,238,104),
5640 "darkolivegreen3"=>array(162,205,90),
5641 "darkolivegreen4"=>array(110,139,61),
5642 "khaki1"=>array(255,246,143),
5643 "khaki2"=>array(238,230,133),
5644 "khaki3"=>array(205,198,115),
5645 "khaki4"=>array(139,134,78),
5646 "lightgoldenrod1"=>array(255,236,139),
5647 "lightgoldenrod2"=>array(238,220,130),
5648 "lightgoldenrod3"=>array(205,190,112),
5649 "lightgoldenrod4"=>array(139,129,76),
5650 "yellow1"=>array(255,255,0),
5651 "yellow2"=>array(238,238,0),
5652 "yellow3"=>array(205,205,0),
5653 "yellow4"=>array(139,139,0),
5654 "gold1"=>array(255,215,0),
5655 "gold2"=>array(238,201,0),
5656 "gold3"=>array(205,173,0),
5657 "gold4"=>array(139,117,0),
5658 "goldenrod1"=>array(255,193,37),
5659 "goldenrod2"=>array(238,180,34),
5660 "goldenrod3"=>array(205,155,29),
5661 "goldenrod4"=>array(139,105,20),
5662 "darkgoldenrod1"=>array(255,185,15),
5663 "darkgoldenrod2"=>array(238,173,14),
5664 "darkgoldenrod3"=>array(205,149,12),
5665 "darkgoldenrod4"=>array(139,101,8),
5666 "rosybrown1"=>array(255,193,193),
5667 "rosybrown2"=>array(238,180,180),
5668 "rosybrown3"=>array(205,155,155),
5669 "rosybrown4"=>array(139,105,105),
5670 "indianred1"=>array(255,106,106),
5671 "indianred2"=>array(238,99,99),
5672 "indianred3"=>array(205,85,85),
5673 "indianred4"=>array(139,58,58),
5674 "sienna1"=>array(255,130,71),
5675 "sienna2"=>array(238,121,66),
5676 "sienna3"=>array(205,104,57),
5677 "sienna4"=>array(139,71,38),
5678 "burlywood1"=>array(255,211,155),
5679 "burlywood2"=>array(238,197,145),
5680 "burlywood3"=>array(205,170,125),
5681 "burlywood4"=>array(139,115,85),
5682 "wheat1"=>array(255,231,186),
5683 "wheat2"=>array(238,216,174),
5684 "wheat3"=>array(205,186,150),
5685 "wheat4"=>array(139,126,102),
5686 "tan1"=>array(255,165,79),
5687 "tan2"=>array(238,154,73),
5688 "tan3"=>array(205,133,63),
5689 "tan4"=>array(139,90,43),
5690 "chocolate1"=>array(255,127,36),
5691 "chocolate2"=>array(238,118,33),
5692 "chocolate3"=>array(205,102,29),
5693 "chocolate4"=>array(139,69,19),
5694 "firebrick1"=>array(255,48,48),
5695 "firebrick2"=>array(238,44,44),
5696 "firebrick3"=>array(205,38,38),
5697 "firebrick4"=>array(139,26,26),
5698 "brown1"=>array(255,64,64),
5699 "brown2"=>array(238,59,59),
5700 "brown3"=>array(205,51,51),
5701 "brown4"=>array(139,35,35),
5702 "salmon1"=>array(255,140,105),
5703 "salmon2"=>array(238,130,98),
5704 "salmon3"=>array(205,112,84),
5705 "salmon4"=>array(139,76,57),
5706 "lightsalmon1"=>array(255,160,122),
5707 "lightsalmon2"=>array(238,149,114),
5708 "lightsalmon3"=>array(205,129,98),
5709 "lightsalmon4"=>array(139,87,66),
5710 "orange1"=>array(255,165,0),
5711 "orange2"=>array(238,154,0),
5712 "orange3"=>array(205,133,0),
5713 "orange4"=>array(139,90,0),
5714 "darkorange1"=>array(255,127,0),
5715 "darkorange2"=>array(238,118,0),
5716 "darkorange3"=>array(205,102,0),
5717 "darkorange4"=>array(139,69,0),
5718 "coral1"=>array(255,114,86),
5719 "coral2"=>array(238,106,80),
5720 "coral3"=>array(205,91,69),
5721 "coral4"=>array(139,62,47),
5722 "tomato1"=>array(255,99,71),
5723 "tomato2"=>array(238,92,66),
5724 "tomato3"=>array(205,79,57),
5725 "tomato4"=>array(139,54,38),
5726 "orangered1"=>array(255,69,0),
5727 "orangered2"=>array(238,64,0),
5728 "orangered3"=>array(205,55,0),
5729 "orangered4"=>array(139,37,0),
5730 "deeppink1"=>array(255,20,147),
5731 "deeppink2"=>array(238,18,137),
5732 "deeppink3"=>array(205,16,118),
5733 "deeppink4"=>array(139,10,80),
5734 "hotpink1"=>array(255,110,180),
5735 "hotpink2"=>array(238,106,167),
5736 "hotpink3"=>array(205,96,144),
5737 "hotpink4"=>array(139,58,98),
5738 "pink1"=>array(255,181,197),
5739 "pink2"=>array(238,169,184),
5740 "pink3"=>array(205,145,158),
5741 "pink4"=>array(139,99,108),
5742 "lightpink1"=>array(255,174,185),
5743 "lightpink2"=>array(238,162,173),
5744 "lightpink3"=>array(205,140,149),
5745 "lightpink4"=>array(139,95,101),
5746 "palevioletred1"=>array(255,130,171),
5747 "palevioletred2"=>array(238,121,159),
5748 "palevioletred3"=>array(205,104,137),
5749 "palevioletred4"=>array(139,71,93),
5750 "maroon1"=>array(255,52,179),
5751 "maroon2"=>array(238,48,167),
5752 "maroon3"=>array(205,41,144),
5753 "maroon4"=>array(139,28,98),
5754 "violetred1"=>array(255,62,150),
5755 "violetred2"=>array(238,58,140),
5756 "violetred3"=>array(205,50,120),
5757 "violetred4"=>array(139,34,82),
5758 "magenta1"=>array(255,0,255),
5759 "magenta2"=>array(238,0,238),
5760 "magenta3"=>array(205,0,205),
5761 "magenta4"=>array(139,0,139),
5762 "mediumred"=>array(140,34,34),
5763 "orchid1"=>array(255,131,250),
5764 "orchid2"=>array(238,122,233),
5765 "orchid3"=>array(205,105,201),
5766 "orchid4"=>array(139,71,137),
5767 "plum1"=>array(255,187,255),
5768 "plum2"=>array(238,174,238),
5769 "plum3"=>array(205,150,205),
5770 "plum4"=>array(139,102,139),
5771 "mediumorchid1"=>array(224,102,255),
5772 "mediumorchid2"=>array(209,95,238),
5773 "mediumorchid3"=>array(180,82,205),
5774 "mediumorchid4"=>array(122,55,139),
5775 "darkorchid1"=>array(191,62,255),
5776 "darkorchid2"=>array(178,58,238),
5777 "darkorchid3"=>array(154,50,205),
5778 "darkorchid4"=>array(104,34,139),
5779 "purple1"=>array(155,48,255),
5780 "purple2"=>array(145,44,238),
5781 "purple3"=>array(125,38,205),
5782 "purple4"=>array(85,26,139),
5783 "mediumpurple1"=>array(171,130,255),
5784 "mediumpurple2"=>array(159,121,238),
5785 "mediumpurple3"=>array(137,104,205),
5786 "mediumpurple4"=>array(93,71,139),
5787 "thistle1"=>array(255,225,255),
5788 "thistle2"=>array(238,210,238),
5789 "thistle3"=>array(205,181,205),
5790 "thistle4"=>array(139,123,139),
5791 "gray1"=>array(10,10,10),
5792 "gray2"=>array(40,40,30),
5793 "gray3"=>array(70,70,70),
5794 "gray4"=>array(100,100,100),
5795 "gray5"=>array(130,130,130),
5796 "gray6"=>array(160,160,160),
5797 "gray7"=>array(190,190,190),
5798 "gray8"=>array(210,210,210),
5799 "gray9"=>array(240,240,240),
5800 "darkgray"=>array(100,100,100),
5801 "darkblue"=>array(0,0,139),
5802 "darkcyan"=>array(0,139,139),
5803 "darkmagenta"=>array(139,0,139),
5804 "darkred"=>array(139,0,0),
5805 "silver"=>array(192, 192, 192),
5806 "eggplant"=>array(144,176,168),
5807 "lightgreen"=>array(144,238,144));
5809 //----------------
5810 // PUBLIC METHODS
5811 // Colors can be specified as either
5812 // 1. #xxxxxx HTML style
5813 // 2. "colorname" as a named color
5814 // 3. array(r,g,b) RGB triple
5815 // This function translates this to a native RGB format and returns an
5816 // RGB triple.
5817 function Color($aColor) {
5818 if (is_string($aColor)) {
5819 // Strip of any alpha factor
5820 $pos = strpos($aColor,'@');
5821 if( $pos === false ) {
5822 $alpha = 0;
5824 else {
5825 $pos2 = strpos($aColor,':');
5826 if( $pos2===false )
5827 $pos2 = $pos-1; // Sentinel
5828 if( $pos > $pos2 ) {
5829 $alpha = substr($aColor,$pos+1);
5830 $aColor = substr($aColor,0,$pos);
5832 else {
5833 $alpha = substr($aColor,$pos+1,$pos2-$pos-1);
5834 $aColor = substr($aColor,0,$pos).substr($aColor,$pos2);
5838 // Extract potential adjustment figure at end of color
5839 // specification
5840 $pos = strpos($aColor,":");
5841 if( $pos === false ) {
5842 $adj = 1.0;
5844 else {
5845 $adj = 0.0 + substr($aColor,$pos+1);
5846 $aColor = substr($aColor,0,$pos);
5848 if( $adj < 0 )
5849 JpGraphError::RaiseL(25077);//('Adjustment factor for color must be > 0');
5851 if (substr($aColor, 0, 1) == "#") {
5852 $r = hexdec(substr($aColor, 1, 2));
5853 $g = hexdec(substr($aColor, 3, 2));
5854 $b = hexdec(substr($aColor, 5, 2));
5855 } else {
5856 if(!isset($this->rgb_table[$aColor]) )
5857 JpGraphError::RaiseL(25078,$aColor);//(" Unknown color: $aColor");
5858 $tmp=$this->rgb_table[$aColor];
5859 $r = $tmp[0];
5860 $g = $tmp[1];
5861 $b = $tmp[2];
5863 // Scale adj so that an adj=2 always
5864 // makes the color 100% white (i.e. 255,255,255.
5865 // and adj=1 neutral and adj=0 black.
5866 if( $adj > 1 ) {
5867 $m = ($adj-1.0)*(255-min(255,min($r,min($g,$b))));
5868 return array(min(255,$r+$m), min(255,$g+$m), min(255,$b+$m),$alpha);
5870 elseif( $adj < 1 ) {
5871 $m = ($adj-1.0)*max(255,max($r,max($g,$b)));
5872 return array(max(0,$r+$m), max(0,$g+$m), max(0,$b+$m),$alpha);
5874 else {
5875 return array($r,$g,$b,$alpha);
5878 } elseif( is_array($aColor) ) {
5879 if( count($aColor)==3 ) {
5880 $aColor[3]=0;
5881 return $aColor;
5883 else
5884 return $aColor;
5886 else
5887 JpGraphError::RaiseL(25079,$aColor,count($aColor));//(" Unknown color specification: $aColor , size=".count($aColor));
5890 // Compare two colors
5891 // return true if equal
5892 function Equal($aCol1,$aCol2) {
5893 $c1 = $this->Color($aCol1);
5894 $c2 = $this->Color($aCol2);
5895 if( $c1[0]==$c2[0] && $c1[1]==$c2[1] && $c1[2]==$c2[2] )
5896 return true;
5897 else
5898 return false;
5901 // Allocate a new color in the current image
5902 // Return new color index, -1 if no more colors could be allocated
5903 function Allocate($aColor,$aAlpha=0.0) {
5904 list ($r, $g, $b, $a) = $this->color($aColor);
5905 // If alpha is specified in the color string then this
5906 // takes precedence over the second argument
5907 if( $a > 0 )
5908 $aAlpha = $a;
5909 if( $GLOBALS['gd2'] ) {
5910 if( $aAlpha < 0 || $aAlpha > 1 ) {
5911 JpGraphError::RaiseL(25080);//('Alpha parameter for color must be between 0.0 and 1.0');
5913 return imagecolorresolvealpha($this->img, $r, $g, $b, round($aAlpha * 127));
5914 } else {
5915 $index = imagecolorexact($this->img, $r, $g, $b);
5916 if ($index == -1) {
5917 $index = imagecolorallocate($this->img, $r, $g, $b);
5918 if( USE_APPROX_COLORS && $index == -1 )
5919 $index = imagecolorresolve($this->img, $r, $g, $b);
5921 return $index;
5924 } // Class
5927 //===================================================
5928 // CLASS Image
5929 // Description: Wrapper class with some goodies to form the
5930 // Interface to low level image drawing routines.
5931 //===================================================
5932 class Image {
5933 var $img_format;
5934 var $expired=true;
5935 var $img=null;
5936 var $left_margin=30,$right_margin=20,$top_margin=20,$bottom_margin=30;
5937 var $plotwidth=0,$plotheight=0;
5938 var $rgb=null;
5939 var $current_color,$current_color_name;
5940 var $lastx=0, $lasty=0;
5941 var $width=0, $height=0;
5942 var $line_weight=1;
5943 var $line_style=1; // Default line style is solid
5944 var $obs_list=array();
5945 var $font_size=12,$font_family=FF_FONT1, $font_style=FS_NORMAL;
5946 var $font_file='';
5947 var $text_halign="left",$text_valign="bottom";
5948 var $ttf=null;
5949 var $use_anti_aliasing=false;
5950 var $quality=null;
5951 var $colorstack=array(),$colorstackidx=0;
5952 var $canvascolor = 'white' ;
5953 var $langconv = null ;
5955 //---------------
5956 // CONSTRUCTOR
5957 function __construct($aWidth,$aHeight,$aFormat=DEFAULT_GFORMAT) {
5958 $this->CreateImgCanvas($aWidth,$aHeight);
5959 $this->SetAutoMargin();
5961 if( !$this->SetImgFormat($aFormat) ) {
5962 JpGraphError::RaiseL(25081,$aFormat);//("JpGraph: Selected graphic format is either not supported or unknown [$aFormat]");
5964 $this->ttf = new TTF();
5965 $this->langconv = new LanguageConv();
5968 // Should we use anti-aliasing. Note: This really slows down graphics!
5969 function SetAntiAliasing() {
5970 $this->use_anti_aliasing=true;
5973 function CreateRawCanvas($aWidth=0,$aHeight=0) {
5974 if( $aWidth <= 1 || $aHeight <= 1 ) {
5975 JpGraphError::RaiseL(25082,$aWidth,$aHeight);//("Illegal sizes specified for width or height when creating an image, (width=$aWidth, height=$aHeight)");
5977 if( @$GLOBALS['gd2']==true && USE_TRUECOLOR ) {
5978 $this->img = @imagecreatetruecolor($aWidth, $aHeight);
5979 if( $this->img < 1 ) {
5980 JpGraphError::RaiseL(25126);
5981 //die("Can't create truecolor image. Check that you really have GD2 library installed.");
5983 $this->SetAlphaBlending();
5984 } else {
5985 $this->img = @imagecreate($aWidth, $aHeight);
5986 if( $this->img < 1 ) {
5987 JpGraphError::RaiseL(25126);
5988 //die("<b>JpGraph Error:</b> Can't create image. Check that you really have the GD library installed.");
5991 if( $this->rgb != null )
5992 $this->rgb->img = $this->img ;
5993 else
5994 $this->rgb = new RGB($this->img);
5997 function CloneCanvasH() {
5998 $oldimage = $this->img;
5999 $this->CreateRawCanvas($this->width,$this->height);
6000 imagecopy($this->img,$oldimage,0,0,0,0,$this->width,$this->height);
6001 return $oldimage;
6004 function CreateImgCanvas($aWidth=0,$aHeight=0) {
6006 $old = array($this->img,$this->width,$this->height);
6008 $aWidth = round($aWidth);
6009 $aHeight = round($aHeight);
6011 $this->width=$aWidth;
6012 $this->height=$aHeight;
6015 if( $aWidth==0 || $aHeight==0 ) {
6016 // We will set the final size later.
6017 // Note: The size must be specified before any other
6018 // img routines that stroke anything are called.
6019 $this->img = null;
6020 $this->rgb = null;
6021 return $old;
6024 $this->CreateRawCanvas($aWidth,$aHeight);
6026 // Set canvas color (will also be the background color for a
6027 // a pallett image
6028 $this->SetColor($this->canvascolor);
6029 $this->FilledRectangle(0,0,$aWidth,$aHeight);
6031 return $old ;
6034 function CopyCanvasH($aToHdl,$aFromHdl,$aToX,$aToY,$aFromX,$aFromY,$aWidth,$aHeight,$aw=-1,$ah=-1) {
6035 if( $aw === -1 ) {
6036 $aw = $aWidth;
6037 $ah = $aHeight;
6038 $f = 'imagecopyresized';
6040 else {
6041 $f = $GLOBALS['copyfunc'] ;
6043 $f($aToHdl,$aFromHdl,
6044 $aToX,$aToY,$aFromX,$aFromY, $aWidth,$aHeight,$aw,$ah);
6047 function Copy($fromImg,$toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$fromWidth=-1,$fromHeight=-1) {
6048 $this->CopyCanvasH($this->img,$fromImg,$toX,$toY,$fromX,$fromY,
6049 $toWidth,$toHeight,$fromWidth,$fromHeight);
6052 function CopyMerge($fromImg,$toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$fromWidth=-1,$fromHeight=-1,$aMix=100) {
6053 if( $aMix == 100 ) {
6054 $this->CopyCanvasH($this->img,$fromImg,
6055 $toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$fromWidth,$fromHeight);
6057 else {
6058 if( ($fromWidth != -1 && ($fromWidth != $toWidth)) ||
6059 ($fromHeight != -1 && ($fromHeight != $fromHeight)) ) {
6060 // Create a new canvas that will hold the re-scaled original from image
6061 if( $toWidth <= 1 || $toHeight <= 1 ) {
6062 JpGraphError::RaiseL(25083);//('Illegal image size when copying image. Size for copied to image is 1 pixel or less.');
6064 if( @$GLOBALS['gd2']==true && USE_TRUECOLOR ) {
6065 $tmpimg = @imagecreatetruecolor($toWidth, $toHeight);
6066 } else {
6067 $tmpimg = @imagecreate($toWidth, $toHeight);
6069 if( $tmpimg < 1 ) {
6070 JpGraphError::RaiseL(25084);//('Failed to create temporary GD canvas. Out of memory ?');
6072 $this->CopyCanvasH($tmpimg,$fromImg,0,0,0,0,
6073 $toWidth,$toHeight,$fromWidth,$fromHeight);
6074 $fromImg = $tmpimg;
6076 imagecopymerge($this->img,$fromImg,$toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$aMix);
6080 function GetWidth($aImg=null) {
6081 if( $aImg === null )
6082 $aImg = $this->img;
6083 return imagesx($aImg);
6086 function GetHeight($aImg=null) {
6087 if( $aImg === null )
6088 $aImg = $this->img;
6089 return imagesy($aImg);
6092 function CreateFromString($aStr) {
6093 $img = imagecreatefromstring($aStr);
6094 if( $img === false ) {
6095 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.');
6097 return $img;
6100 function SetCanvasH($aHdl) {
6101 $this->img = $aHdl;
6102 $this->rgb->img = $aHdl;
6105 function SetCanvasColor($aColor) {
6106 $this->canvascolor = $aColor ;
6109 function SetAlphaBlending($aFlg=true) {
6110 if( $GLOBALS['gd2'] )
6111 ImageAlphaBlending($this->img,$aFlg);
6112 else
6113 JpGraphError::RaiseL(25086);//('You only seem to have GD 1.x installed. To enable Alphablending requires GD 2.x or higher. Please install GD or make sure the constant USE_GD2 is specified correctly to reflect your installation. By default it tries to autodetect what version of GD you have installed. On some very rare occasions it may falsely detect GD2 where only GD1 is installed. You must then set USE_GD2 to false.');
6117 function SetAutoMargin() {
6118 GLOBAL $gJpgBrandTiming;
6119 $min_bm=10;
6121 if( $gJpgBrandTiming )
6122 $min_bm=15;
6124 $lm = min(40,$this->width/7);
6125 $rm = min(20,$this->width/10);
6126 $tm = max(20,$this->height/7);
6127 $bm = max($min_bm,$this->height/7);
6128 $this->SetMargin($lm,$rm,$tm,$bm);
6132 //---------------
6133 // PUBLIC METHODS
6135 function SetFont($family,$style=FS_NORMAL,$size=10) {
6136 $this->font_family=$family;
6137 $this->font_style=$style;
6138 $this->font_size=$size;
6139 $this->font_file='';
6140 if( ($this->font_family==FF_FONT1 || $this->font_family==FF_FONT2) && $this->font_style==FS_BOLD ){
6141 ++$this->font_family;
6143 if( $this->font_family > FF_FONT2+1 ) { // A TTF font so get the font file
6145 // Check that this PHP has support for TTF fonts
6146 if( !function_exists('imagettfbbox') ) {
6147 JpGraphError::RaiseL(25087);//('This PHP build has not been configured with TTF support. You need to recompile your PHP installation with FreeType support.');
6148 exit();
6150 $this->font_file = $this->ttf->File($this->font_family,$this->font_style);
6154 // Get the specific height for a text string
6155 function GetTextHeight($txt="",$angle=0) {
6156 $tmp = split("\n",$txt);
6157 $n = count($tmp);
6158 $m=0;
6159 for($i=0; $i< $n; ++$i)
6160 $m = max($m,strlen($tmp[$i]));
6162 if( $this->font_family <= FF_FONT2+1 ) {
6163 if( $angle==0 ) {
6164 $h = imagefontheight($this->font_family);
6165 if( $h === false ) {
6166 JpGraphError::RaiseL(25088);//('You have a misconfigured GD font support. The call to imagefontwidth() fails.');
6169 return $n*$h;
6171 else {
6172 $w = @imagefontwidth($this->font_family);
6173 if( $w === false ) {
6174 JpGraphError::RaiseL(25088);//('You have a misconfigured GD font support. The call to imagefontwidth() fails.');
6177 return $m*$w;
6180 else {
6181 $bbox = $this->GetTTFBBox($txt,$angle);
6182 return $bbox[1]-$bbox[5];
6186 // Estimate font height
6187 function GetFontHeight($angle=0) {
6188 $txt = "XOMg";
6189 return $this->GetTextHeight($txt,$angle);
6192 // Approximate font width with width of letter "O"
6193 function GetFontWidth($angle=0) {
6194 $txt = 'O';
6195 return $this->GetTextWidth($txt,$angle);
6198 // Get actual width of text in absolute pixels
6199 function GetTextWidth($txt,$angle=0) {
6201 $tmp = split("\n",$txt);
6202 $n = count($tmp);
6203 if( $this->font_family <= FF_FONT2+1 ) {
6205 $m=0;
6206 for($i=0; $i < $n; ++$i) {
6207 $l=strlen($tmp[$i]);
6208 if( $l > $m ) {
6209 $m = $l;
6213 if( $angle==0 ) {
6214 $w = @imagefontwidth($this->font_family);
6215 if( $w === false ) {
6216 JpGraphError::RaiseL(25088);//('You have a misconfigured GD font support. The call to imagefontwidth() fails.');
6218 return $m*$w;
6220 else {
6221 // 90 degrees internal so height becomes width
6222 $h = @imagefontheight($this->font_family);
6223 if( $h === false ) {
6224 JpGraphError::RaiseL(25089);//('You have a misconfigured GD font support. The call to imagefontheight() fails.');
6226 return $n*$h;
6229 else {
6230 // For TTF fonts we must walk through a lines and find the
6231 // widest one which we use as the width of the multi-line
6232 // paragraph
6233 $m=0;
6234 for( $i=0; $i < $n; ++$i ) {
6235 $bbox = $this->GetTTFBBox($tmp[$i],$angle);
6236 $mm = $bbox[2] - $bbox[0];
6237 if( $mm > $m )
6238 $m = $mm;
6240 return $m;
6244 // Draw text with a box around it
6245 function StrokeBoxedText($x,$y,$txt,$dir=0,$fcolor="white",$bcolor="black",
6246 $shadowcolor=false,$paragraph_align="left",
6247 $xmarg=6,$ymarg=4,$cornerradius=0,$dropwidth=3) {
6249 if( !is_numeric($dir) ) {
6250 if( $dir=="h" ) $dir=0;
6251 elseif( $dir=="v" ) $dir=90;
6252 else JpGraphError::RaiseL(25090,$dir);//(" Unknown direction specified in call to StrokeBoxedText() [$dir]");
6255 if( $this->font_family >= FF_FONT0 && $this->font_family <= FF_FONT2+1) {
6256 $width=$this->GetTextWidth($txt,$dir) ;
6257 $height=$this->GetTextHeight($txt,$dir) ;
6259 else {
6260 $width=$this->GetBBoxWidth($txt,$dir) ;
6261 $height=$this->GetBBoxHeight($txt,$dir) ;
6264 $height += 2*$ymarg;
6265 $width += 2*$xmarg;
6267 if( $this->text_halign=="right" ) $x -= $width;
6268 elseif( $this->text_halign=="center" ) $x -= $width/2;
6269 if( $this->text_valign=="bottom" ) $y -= $height;
6270 elseif( $this->text_valign=="center" ) $y -= $height/2;
6272 if( $shadowcolor ) {
6273 $this->PushColor($shadowcolor);
6274 $this->FilledRoundedRectangle($x-$xmarg+$dropwidth,$y-$ymarg+$dropwidth,
6275 $x+$width+$dropwidth,$y+$height-$ymarg+$dropwidth,
6276 $cornerradius);
6277 $this->PopColor();
6278 $this->PushColor($fcolor);
6279 $this->FilledRoundedRectangle($x-$xmarg,$y-$ymarg,
6280 $x+$width,$y+$height-$ymarg,
6281 $cornerradius);
6282 $this->PopColor();
6283 $this->PushColor($bcolor);
6284 $this->RoundedRectangle($x-$xmarg,$y-$ymarg,
6285 $x+$width,$y+$height-$ymarg,$cornerradius);
6286 $this->PopColor();
6288 else {
6289 if( $fcolor ) {
6290 $oc=$this->current_color;
6291 $this->SetColor($fcolor);
6292 $this->FilledRoundedRectangle($x-$xmarg,$y-$ymarg,$x+$width,$y+$height-$ymarg,$cornerradius);
6293 $this->current_color=$oc;
6295 if( $bcolor ) {
6296 $oc=$this->current_color;
6297 $this->SetColor($bcolor);
6298 $this->RoundedRectangle($x-$xmarg,$y-$ymarg,$x+$width,$y+$height-$ymarg,$cornerradius);
6299 $this->current_color=$oc;
6303 $h=$this->text_halign;
6304 $v=$this->text_valign;
6305 $this->SetTextAlign("left","top");
6306 $this->StrokeText($x, $y, $txt, $dir, $paragraph_align);
6307 $bb = array($x-$xmarg,$y+$height-$ymarg,$x+$width,$y+$height-$ymarg,
6308 $x+$width,$y-$ymarg,$x-$xmarg,$y-$ymarg);
6309 $this->SetTextAlign($h,$v);
6310 return $bb;
6313 // Set text alignment
6314 function SetTextAlign($halign,$valign="bottom") {
6315 $this->text_halign=$halign;
6316 $this->text_valign=$valign;
6320 function _StrokeBuiltinFont($x,$y,$txt,$dir=0,$paragraph_align="left",&$aBoundingBox,$aDebug=false) {
6322 if( is_numeric($dir) && $dir!=90 && $dir!=0)
6323 JpGraphError::RaiseL(25091);//(" Internal font does not support drawing text at arbitrary angle. Use TTF fonts instead.");
6325 $h=$this->GetTextHeight($txt);
6326 $fh=$this->GetFontHeight();
6327 $w=$this->GetTextWidth($txt);
6329 if( $this->text_halign=="right")
6330 $x -= $dir==0 ? $w : $h;
6331 elseif( $this->text_halign=="center" ) {
6332 // For center we subtract 1 pixel since this makes the middle
6333 // be prefectly in the middle
6334 $x -= $dir==0 ? $w/2-1 : $h/2;
6336 if( $this->text_valign=="top" )
6337 $y += $dir==0 ? $h : $w;
6338 elseif( $this->text_valign=="center" )
6339 $y += $dir==0 ? $h/2 : $w/2;
6341 if( $dir==90 ) {
6342 imagestringup($this->img,$this->font_family,$x,$y,$txt,$this->current_color);
6343 $aBoundingBox = array(round($x),round($y),round($x),round($y-$w),round($x+$h),round($y-$w),round($x+$h),round($y));
6344 if( $aDebug ) {
6345 // Draw bounding box
6346 $this->PushColor('green');
6347 $this->Polygon($aBoundingBox,true);
6348 $this->PopColor();
6351 else {
6352 if( ereg("\n",$txt) ) {
6353 $tmp = split("\n",$txt);
6354 for($i=0; $i < count($tmp); ++$i) {
6355 $w1 = $this->GetTextWidth($tmp[$i]);
6356 if( $paragraph_align=="left" ) {
6357 imagestring($this->img,$this->font_family,$x,$y-$h+1+$i*$fh,$tmp[$i],$this->current_color);
6359 elseif( $paragraph_align=="right" ) {
6360 imagestring($this->img,$this->font_family,$x+($w-$w1),
6361 $y-$h+1+$i*$fh,$tmp[$i],$this->current_color);
6363 else {
6364 imagestring($this->img,$this->font_family,$x+$w/2-$w1/2,
6365 $y-$h+1+$i*$fh,$tmp[$i],$this->current_color);
6369 else {
6370 //Put the text
6371 imagestring($this->img,$this->font_family,$x,$y-$h+1,$txt,$this->current_color);
6373 if( $aDebug ) {
6374 // Draw the bounding rectangle and the bounding box
6375 $p1 = array(round($x),round($y),round($x),round($y-$h),round($x+$w),round($y-$h),round($x+$w),round($y));
6377 // Draw bounding box
6378 $this->PushColor('green');
6379 $this->Polygon($p1,true);
6380 $this->PopColor();
6383 $aBoundingBox=array(round($x),round($y),round($x),round($y-$h),round($x+$w),round($y-$h),round($x+$w),round($y));
6387 function AddTxtCR($aTxt) {
6388 // If the user has just specified a '\n'
6389 // instead of '\n\t' we have to add '\r' since
6390 // the width will be too muchy otherwise since when
6391 // we print we stroke the individually lines by hand.
6392 $e = explode("\n",$aTxt);
6393 $n = count($e);
6394 for($i=0; $i<$n; ++$i) {
6395 $e[$i]=str_replace("\r","",$e[$i]);
6397 return implode("\n\r",$e);
6400 function GetTTFBBox($aTxt,$aAngle=0) {
6401 $bbox = @ImageTTFBBox($this->font_size,$aAngle,$this->font_file,$aTxt);
6402 if( $bbox === false ) {
6403 JpGraphError::RaiseL(25092,$this->font_file);
6404 //("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.");
6406 return $bbox;
6409 function GetBBoxTTF($aTxt,$aAngle=0) {
6410 // Normalize the bounding box to become a minimum
6411 // enscribing rectangle
6413 $aTxt = $this->AddTxtCR($aTxt);
6415 if( !is_readable($this->font_file) ) {
6416 JpGraphError::RaiseL(25093,$this->font_file);
6417 //('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.');
6419 $bbox = $this->GetTTFBBox($aTxt,$aAngle);
6421 if( $aAngle==0 )
6422 return $bbox;
6423 if( $aAngle >= 0 ) {
6424 if( $aAngle <= 90 ) { //<=0
6425 $bbox = array($bbox[6],$bbox[1],$bbox[2],$bbox[1],
6426 $bbox[2],$bbox[5],$bbox[6],$bbox[5]);
6428 elseif( $aAngle <= 180 ) { //<= 2
6429 $bbox = array($bbox[4],$bbox[7],$bbox[0],$bbox[7],
6430 $bbox[0],$bbox[3],$bbox[4],$bbox[3]);
6432 elseif( $aAngle <= 270 ) { //<= 3
6433 $bbox = array($bbox[2],$bbox[5],$bbox[6],$bbox[5],
6434 $bbox[6],$bbox[1],$bbox[2],$bbox[1]);
6436 else {
6437 $bbox = array($bbox[0],$bbox[3],$bbox[4],$bbox[3],
6438 $bbox[4],$bbox[7],$bbox[0],$bbox[7]);
6441 elseif( $aAngle < 0 ) {
6442 if( $aAngle <= -270 ) { // <= -3
6443 $bbox = array($bbox[6],$bbox[1],$bbox[2],$bbox[1],
6444 $bbox[2],$bbox[5],$bbox[6],$bbox[5]);
6446 elseif( $aAngle <= -180 ) { // <= -2
6447 $bbox = array($bbox[0],$bbox[3],$bbox[4],$bbox[3],
6448 $bbox[4],$bbox[7],$bbox[0],$bbox[7]);
6450 elseif( $aAngle <= -90 ) { // <= -1
6451 $bbox = array($bbox[2],$bbox[5],$bbox[6],$bbox[5],
6452 $bbox[6],$bbox[1],$bbox[2],$bbox[1]);
6454 else {
6455 $bbox = array($bbox[0],$bbox[3],$bbox[4],$bbox[3],
6456 $bbox[4],$bbox[7],$bbox[0],$bbox[7]);
6459 return $bbox;
6462 function GetBBoxHeight($aTxt,$aAngle=0) {
6463 $box = $this->GetBBoxTTF($aTxt,$aAngle);
6464 return $box[1]-$box[7]+1;
6467 function GetBBoxWidth($aTxt,$aAngle=0) {
6468 $box = $this->GetBBoxTTF($aTxt,$aAngle);
6469 return $box[2]-$box[0]+1;
6472 function _StrokeTTF($x,$y,$txt,$dir=0,$paragraph_align="left",&$aBoundingBox,$debug=false) {
6474 // Setupo default inter line margin for paragraphs to
6475 // 25% of the font height.
6476 $ConstLineSpacing = 0.25 ;
6478 // Remember the anchor point before adjustment
6479 if( $debug ) {
6480 $ox=$x;
6481 $oy=$y;
6484 if( !ereg("\n",$txt) || ($dir>0 && ereg("\n",$txt)) ) {
6485 // Format a single line
6487 $txt = $this->AddTxtCR($txt);
6489 $bbox=$this->GetBBoxTTF($txt,$dir);
6491 // Align x,y ot lower left corner of bbox
6492 $x -= $bbox[0];
6493 $y -= $bbox[1];
6495 // Note to self: "topanchor" is deprecated after we changed the
6496 // bopunding box stuff.
6497 if( $this->text_halign=="right" || $this->text_halign=="topanchor" )
6498 $x -= $bbox[2]-$bbox[0];
6499 elseif( $this->text_halign=="center" ) $x -= ($bbox[2]-$bbox[0])/2;
6501 if( $this->text_valign=="top" ) $y += abs($bbox[5])+$bbox[1];
6502 elseif( $this->text_valign=="center" ) $y -= ($bbox[5]-$bbox[1])/2;
6504 ImageTTFText ($this->img, $this->font_size, $dir, $x, $y,
6505 $this->current_color,$this->font_file,$txt);
6507 // Calculate and return the co-ordinates for the bounding box
6508 $box=@ImageTTFBBox($this->font_size,$dir,$this->font_file,$txt);
6509 $p1 = array();
6512 for($i=0; $i < 4; ++$i) {
6513 $p1[] = round($box[$i*2]+$x);
6514 $p1[] = round($box[$i*2+1]+$y);
6516 $aBoundingBox = $p1;
6518 // Debugging code to highlight the bonding box and bounding rectangle
6519 // For text at 0 degrees the bounding box and bounding rectangle are the
6520 // same
6521 if( $debug ) {
6522 // Draw the bounding rectangle and the bounding box
6523 $box=@ImageTTFBBox($this->font_size,$dir,$this->font_file,$txt);
6524 $p = array();
6525 $p1 = array();
6526 for($i=0; $i < 4; ++$i) {
6527 $p[] = $bbox[$i*2]+$x;
6528 $p[] = $bbox[$i*2+1]+$y;
6529 $p1[] = $box[$i*2]+$x;
6530 $p1[] = $box[$i*2+1]+$y;
6533 // Draw bounding box
6534 $this->PushColor('green');
6535 $this->Polygon($p1,true);
6536 $this->PopColor();
6538 // Draw bounding rectangle
6539 $this->PushColor('darkgreen');
6540 $this->Polygon($p,true);
6541 $this->PopColor();
6543 // Draw a cross at the anchor point
6544 $this->PushColor('red');
6545 $this->Line($ox-15,$oy,$ox+15,$oy);
6546 $this->Line($ox,$oy-15,$ox,$oy+15);
6547 $this->PopColor();
6550 else {
6551 // Format a text paragraph
6552 $fh=$this->GetFontHeight();
6554 // Line margin is 25% of font height
6555 $linemargin=round($fh*$ConstLineSpacing);
6556 $fh += $linemargin;
6557 $w=$this->GetTextWidth($txt);
6559 $y -= $linemargin/2;
6560 $tmp = split("\n",$txt);
6561 $nl = count($tmp);
6562 $h = $nl * $fh;
6564 if( $this->text_halign=="right")
6565 $x -= $dir==0 ? $w : $h;
6566 elseif( $this->text_halign=="center" ) {
6567 $x -= $dir==0 ? $w/2 : $h/2;
6570 if( $this->text_valign=="top" )
6571 $y += $dir==0 ? $h : $w;
6572 elseif( $this->text_valign=="center" )
6573 $y += $dir==0 ? $h/2 : $w/2;
6575 // Here comes a tricky bit.
6576 // Since we have to give the position for the string at the
6577 // baseline this means thaht text will move slightly up
6578 // and down depending on any of it's character descend below
6579 // the baseline, for example a 'g'. To adjust the Y-position
6580 // we therefore adjust the text with the baseline Y-offset
6581 // as used for the current font and size. This will keep the
6582 // baseline at a fixed positoned disregarding the actual
6583 // characters in the string.
6584 $standardbox = $this->GetTTFBBox('Gg',$dir);
6585 $yadj = $standardbox[1];
6586 $xadj = $standardbox[0];
6587 $aBoundingBox = array();
6588 for($i=0; $i < $nl; ++$i) {
6589 $wl = $this->GetTextWidth($tmp[$i]);
6590 $bbox = $this->GetTTFBBox($tmp[$i],$dir);
6591 if( $paragraph_align=="left" ) {
6592 $xl = $x;
6594 elseif( $paragraph_align=="right" ) {
6595 $xl = $x + ($w-$wl);
6597 else {
6598 // Center
6599 $xl = $x + $w/2 - $wl/2 ;
6602 $xl -= $bbox[0];
6603 $yl = $y - $yadj;
6604 $xl = $xl - $xadj;
6605 ImageTTFText ($this->img, $this->font_size, $dir,
6606 $xl, $yl-($h-$fh)+$fh*$i,
6607 $this->current_color,$this->font_file,$tmp[$i]);
6609 if( $debug ) {
6610 // Draw the bounding rectangle around each line
6611 $box=@ImageTTFBBox($this->font_size,$dir,$this->font_file,$tmp[$i]);
6612 $p = array();
6613 for($j=0; $j < 4; ++$j) {
6614 $p[] = $bbox[$j*2]+$xl;
6615 $p[] = $bbox[$j*2+1]+$yl-($h-$fh)+$fh*$i;
6618 // Draw bounding rectangle
6619 $this->PushColor('darkgreen');
6620 $this->Polygon($p,true);
6621 $this->PopColor();
6625 // Get the bounding box
6626 $bbox = $this->GetBBoxTTF($txt,$dir);
6627 for($j=0; $j < 4; ++$j) {
6628 $bbox[$j*2]+= round($x);
6629 $bbox[$j*2+1]+= round($y - ($h-$fh) - $yadj);
6631 $aBoundingBox = $bbox;
6633 if( $debug ) {
6634 // Draw a cross at the anchor point
6635 $this->PushColor('red');
6636 $this->Line($ox-25,$oy,$ox+25,$oy);
6637 $this->Line($ox,$oy-25,$ox,$oy+25);
6638 $this->PopColor();
6644 function StrokeText($x,$y,$txt,$dir=0,$paragraph_align="left",$debug=false) {
6646 $x = round($x);
6647 $y = round($y);
6649 // Do special language encoding
6650 $txt = $this->langconv->Convert($txt,$this->font_family);
6652 if( !is_numeric($dir) )
6653 JpGraphError::RaiseL(25094);//(" Direction for text most be given as an angle between 0 and 90.");
6655 if( $this->font_family >= FF_FONT0 && $this->font_family <= FF_FONT2+1) {
6656 $this->_StrokeBuiltinFont($x,$y,$txt,$dir,$paragraph_align,$boundingbox,$debug);
6658 elseif($this->font_family >= _FF_FIRST && $this->font_family <= _FF_LAST) {
6659 $this->_StrokeTTF($x,$y,$txt,$dir,$paragraph_align,$boundingbox,$debug);
6661 else
6662 JpGraphError::RaiseL(25095);//(" Unknown font font family specification. ");
6663 return $boundingbox;
6666 function SetMargin($lm,$rm,$tm,$bm) {
6667 $this->left_margin=$lm;
6668 $this->right_margin=$rm;
6669 $this->top_margin=$tm;
6670 $this->bottom_margin=$bm;
6671 $this->plotwidth=$this->width - $this->left_margin-$this->right_margin ;
6672 $this->plotheight=$this->height - $this->top_margin-$this->bottom_margin ;
6673 if( $this->width > 0 && $this->height > 0 ) {
6674 if( $this->plotwidth < 0 || $this->plotheight < 0 )
6675 JpGraphError::raise("Too 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.");
6679 function SetTransparent($color) {
6680 imagecolortransparent ($this->img,$this->rgb->allocate($color));
6683 function SetColor($color,$aAlpha=0) {
6684 $this->current_color_name = $color;
6685 $this->current_color=$this->rgb->allocate($color,$aAlpha);
6686 if( $this->current_color == -1 ) {
6687 $tc=imagecolorstotal($this->img);
6688 JpGraphError::RaiseL(25096);
6689 //("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.");
6691 return $this->current_color;
6694 function PushColor($color) {
6695 if( $color != "" ) {
6696 $this->colorstack[$this->colorstackidx]=$this->current_color_name;
6697 $this->colorstack[$this->colorstackidx+1]=$this->current_color;
6698 $this->colorstackidx+=2;
6699 $this->SetColor($color);
6701 else {
6702 JpGraphError::RaiseL(25097);//("Color specified as empty string in PushColor().");
6706 function PopColor() {
6707 if($this->colorstackidx<1)
6708 JpGraphError::RaiseL(25098);//(" Negative Color stack index. Unmatched call to PopColor()");
6709 $this->current_color=$this->colorstack[--$this->colorstackidx];
6710 $this->current_color_name=$this->colorstack[--$this->colorstackidx];
6714 // Why this duplication? Because this way we can call this method
6715 // for any image and not only the current objsct
6716 function AdjSat($sat) {
6717 if( $GLOBALS['gd2'] && USE_TRUECOLOR )
6718 return;
6719 $this->_AdjSat($this->img,$sat);
6722 function _AdjSat($img,$sat) {
6723 $nbr = imagecolorstotal ($img);
6724 for( $i=0; $i<$nbr; ++$i ) {
6725 $colarr = imagecolorsforindex ($img,$i);
6726 $rgb[0]=$colarr["red"];
6727 $rgb[1]=$colarr["green"];
6728 $rgb[2]=$colarr["blue"];
6729 $rgb = $this->AdjRGBSat($rgb,$sat);
6730 imagecolorset ($img, $i, $rgb[0], $rgb[1], $rgb[2]);
6734 function AdjBrightContrast($bright,$contr=0) {
6735 if( $GLOBALS['gd2'] && USE_TRUECOLOR )
6736 return;
6737 $this->_AdjBrightContrast($this->img,$bright,$contr);
6740 function _AdjBrightContrast($img,$bright,$contr=0) {
6741 if( $bright < -1 || $bright > 1 || $contr < -1 || $contr > 1 )
6742 JpGraphError::RaiseL(25099);//(" Parameters for brightness and Contrast out of range [-1,1]");
6743 $nbr = imagecolorstotal ($img);
6744 for( $i=0; $i<$nbr; ++$i ) {
6745 $colarr = imagecolorsforindex ($img,$i);
6746 $r = $this->AdjRGBBrightContrast($colarr["red"],$bright,$contr);
6747 $g = $this->AdjRGBBrightContrast($colarr["green"],$bright,$contr);
6748 $b = $this->AdjRGBBrightContrast($colarr["blue"],$bright,$contr);
6749 imagecolorset ($img, $i, $r, $g, $b);
6753 // Private helper function for adj sat
6754 // Adjust saturation for RGB array $u. $sat is a value between -1 and 1
6755 // Note: Due to GD inability to handle true color the RGB values are only between
6756 // 8 bit. This makes saturation quite sensitive for small increases in parameter sat.
6758 // Tip: To get a grayscale picture set sat=-100, values <-100 changes the colors
6759 // to it's complement.
6761 // Implementation note: The saturation is implemented directly in the RGB space
6762 // by adjusting the perpendicular distance between the RGB point and the "grey"
6763 // line (1,1,1). Setting $sat>0 moves the point away from the line along the perp.
6764 // distance and a negative value moves the point closer to the line.
6765 // The values are truncated when the color point hits the bounding box along the
6766 // RGB axis.
6767 // DISCLAIMER: I'm not 100% sure this is he correct way to implement a color
6768 // saturation function in RGB space. However, it looks ok and has the expected effect.
6769 function AdjRGBSat($rgb,$sat) {
6770 // TODO: Should be moved to the RGB class
6771 // Grey vector
6772 $v=array(1,1,1);
6774 // Dot product
6775 $dot = $rgb[0]*$v[0]+$rgb[1]*$v[1]+$rgb[2]*$v[2];
6777 // Normalize dot product
6778 $normdot = $dot/3; // dot/|v|^2
6780 // Direction vector between $u and its projection onto $v
6781 for($i=0; $i<3; ++$i)
6782 $r[$i] = $rgb[$i] - $normdot*$v[$i];
6784 // Adjustment factor so that sat==1 sets the highest RGB value to 255
6785 if( $sat > 0 ) {
6786 $m=0;
6787 for( $i=0; $i<3; ++$i) {
6788 if( sign($r[$i]) == 1 && $r[$i]>0)
6789 $m=max($m,(255-$rgb[$i])/$r[$i]);
6791 $tadj=$m;
6793 else
6794 $tadj=1;
6796 $tadj = $tadj*$sat;
6797 for($i=0; $i<3; ++$i) {
6798 $un[$i] = round($rgb[$i] + $tadj*$r[$i]);
6799 if( $un[$i]<0 ) $un[$i]=0; // Truncate color when they reach 0
6800 if( $un[$i]>255 ) $un[$i]=255;// Avoid potential rounding error
6802 return $un;
6805 // Private helper function for AdjBrightContrast
6806 function AdjRGBBrightContrast($rgb,$bright,$contr) {
6807 // TODO: Should be moved to the RGB class
6808 // First handle contrast, i.e change the dynamic range around grey
6809 if( $contr <= 0 ) {
6810 // Decrease contrast
6811 $adj = abs($rgb-128) * (-$contr);
6812 if( $rgb < 128 ) $rgb += $adj;
6813 else $rgb -= $adj;
6815 else { // $contr > 0
6816 // Increase contrast
6817 if( $rgb < 128 ) $rgb = $rgb - ($rgb * $contr);
6818 else $rgb = $rgb + ((255-$rgb) * $contr);
6821 // Add (or remove) various amount of white
6822 $rgb += $bright*255;
6823 $rgb=min($rgb,255);
6824 $rgb=max($rgb,0);
6825 return $rgb;
6828 function SetLineWeight($weight) {
6829 $this->line_weight = $weight;
6832 function SetStartPoint($x,$y) {
6833 $this->lastx=round($x);
6834 $this->lasty=round($y);
6837 function Arc($cx,$cy,$w,$h,$s,$e) {
6838 // GD Arc doesn't like negative angles
6839 while( $s < 0) $s += 360;
6840 while( $e < 0) $e += 360;
6842 imagearc($this->img,round($cx),round($cy),round($w),round($h),
6843 $s,$e,$this->current_color);
6846 function FilledArc($xc,$yc,$w,$h,$s,$e,$style="") {
6848 if( $GLOBALS['gd2'] ) {
6849 while( $s < 0 ) $s += 360;
6850 while( $e < 0 ) $e += 360;
6851 if( $style=="" )
6852 $style=IMG_ARC_PIE;
6853 imagefilledarc($this->img,round($xc),round($yc),round($w),round($h),
6854 round($s),round($e),$this->current_color,$style);
6855 return;
6859 // In GD 1.x we have to do it ourself interesting enough there is surprisingly
6860 // little difference in time between doing it PHP and using the optimised GD
6861 // library (roughly ~20%) I had expected it to be at least 100% slower doing it
6862 // manually with a polygon approximation in PHP.....
6863 $fillcolor = $this->current_color_name;
6865 $w /= 2; // We use radius in our calculations instead
6866 $h /= 2;
6868 // Setup the angles so we have the same conventions as the builtin
6869 // FilledArc() which is a little bit strange if you ask me....
6871 $s = 360-$s;
6872 $e = 360-$e;
6874 if( $e > $s ) {
6875 $e = $e - 360;
6876 $da = $s - $e;
6878 $da = $s-$e;
6880 // We use radians
6881 $s *= M_PI/180;
6882 $e *= M_PI/180;
6883 $da *= M_PI/180;
6885 // Calculate a polygon approximation
6886 $p[0] = $xc;
6887 $p[1] = $yc;
6889 // Heuristic on how many polygons we need to make the
6890 // arc look good
6891 $numsteps = round(8 * abs($da) * ($w+$h)*($w+$h)/1500);
6893 if( $numsteps == 0 ) return;
6894 if( $numsteps < 7 ) $numsteps=7;
6895 $delta = abs($da)/$numsteps;
6897 $pa=array();
6898 $a = $s;
6899 for($i=1; $i<=$numsteps; ++$i ) {
6900 $p[2*$i] = round($xc + $w*cos($a));
6901 $p[2*$i+1] = round($yc - $h*sin($a));
6902 //$a = $s + $i*$delta;
6903 $a -= $delta;
6904 $pa[2*($i-1)] = $p[2*$i];
6905 $pa[2*($i-1)+1] = $p[2*$i+1];
6908 // Get the last point at the exact ending angle to avoid
6909 // any rounding errors.
6910 $p[2*$i] = round($xc + $w*cos($e));
6911 $p[2*$i+1] = round($yc - $h*sin($e));
6912 $pa[2*($i-1)] = $p[2*$i];
6913 $pa[2*($i-1)+1] = $p[2*$i+1];
6914 $i++;
6916 $p[2*$i] = $xc;
6917 $p[2*$i+1] = $yc;
6918 if( $fillcolor != "" ) {
6919 $this->PushColor($fillcolor);
6920 imagefilledpolygon($this->img,$p,count($p)/2,$this->current_color);
6921 $this->PopColor();
6925 function FilledCakeSlice($cx,$cy,$w,$h,$s,$e) {
6926 $this->CakeSlice($cx,$cy,$w,$h,$s,$e,$this->current_color_name);
6929 function CakeSlice($xc,$yc,$w,$h,$s,$e,$fillcolor="",$arccolor="") {
6930 $s = round($s); $e = round($e);
6931 $w = round($w); $h = round($h);
6932 $xc = round($xc); $yc = round($yc);
6933 $this->PushColor($fillcolor);
6934 $this->FilledArc($xc,$yc,2*$w,2*$h,$s,$e);
6935 $this->PopColor();
6936 if( $arccolor != "" ) {
6937 $this->PushColor($arccolor);
6938 // We add 2 pixels to make the Arc() better aligned with the filled arc.
6939 if( $GLOBALS['gd2'] ) {
6940 imagefilledarc($this->img,$xc,$yc,2*$w,2*$h,$s,$e,$this->current_color,IMG_ARC_NOFILL | IMG_ARC_EDGED ) ;
6942 else {
6943 $this->Arc($xc,$yc,2*$w+2,2*$h+2,$s,$e);
6944 $xx = $w * cos(2*M_PI - $s*M_PI/180) + $xc;
6945 $yy = $yc - $h * sin(2*M_PI - $s*M_PI/180);
6946 $this->Line($xc,$yc,$xx,$yy);
6947 $xx = $w * cos(2*M_PI - $e*M_PI/180) + $xc;
6948 $yy = $yc - $h * sin(2*M_PI - $e*M_PI/180);
6949 $this->Line($xc,$yc,$xx,$yy);
6951 $this->PopColor();
6955 function Ellipse($xc,$yc,$w,$h) {
6956 $this->Arc($xc,$yc,$w,$h,0,360);
6959 // Breseham circle gives visually better result then using GD
6960 // built in arc(). It takes some more time but gives better
6961 // accuracy.
6962 function BresenhamCircle($xc,$yc,$r) {
6963 $d = 3-2*$r;
6964 $x = 0;
6965 $y = $r;
6966 while($x<=$y) {
6967 $this->Point($xc+$x,$yc+$y);
6968 $this->Point($xc+$x,$yc-$y);
6969 $this->Point($xc-$x,$yc+$y);
6970 $this->Point($xc-$x,$yc-$y);
6972 $this->Point($xc+$y,$yc+$x);
6973 $this->Point($xc+$y,$yc-$x);
6974 $this->Point($xc-$y,$yc+$x);
6975 $this->Point($xc-$y,$yc-$x);
6977 if( $d<0 ) $d += 4*$x+6;
6978 else {
6979 $d += 4*($x-$y)+10;
6980 --$y;
6982 ++$x;
6986 function Circle($xc,$yc,$r) {
6987 if( USE_BRESENHAM )
6988 $this->BresenhamCircle($xc,$yc,$r);
6989 else {
6992 // Some experimental code snippet to see if we can get a decent
6993 // result doing a trig-circle
6994 // Create an approximated circle with 0.05 rad resolution
6995 $end = 2*M_PI;
6996 $l = $r/10;
6997 if( $l < 3 ) $l=3;
6998 $step_size = 2*M_PI/(2*$r*M_PI/$l);
6999 $pts = array();
7000 $pts[] = $r + $xc;
7001 $pts[] = $yc;
7002 for( $a=$step_size; $a <= $end; $a += $step_size ) {
7003 $pts[] = round($xc + $r*cos($a));
7004 $pts[] = round($yc - $r*sin($a));
7006 imagepolygon($this->img,$pts,count($pts)/2,$this->current_color);
7009 $this->Arc($xc,$yc,$r*2,$r*2,0,360);
7011 // For some reason imageellipse() isn't in GD 2.0.1, PHP 4.1.1
7012 //imageellipse($this->img,$xc,$yc,$r,$r,$this->current_color);
7016 function FilledCircle($xc,$yc,$r) {
7017 if( $GLOBALS['gd2'] ) {
7018 imagefilledellipse($this->img,round($xc),round($yc),
7019 2*$r,2*$r,$this->current_color);
7021 else {
7022 for( $i=1; $i < 2*$r; $i += 2 ) {
7023 // To avoid moire patterns we have to draw some
7024 // 1 extra "skewed" filled circles
7025 $this->Arc($xc,$yc,$i,$i,0,360);
7026 $this->Arc($xc,$yc,$i+1,$i,0,360);
7027 $this->Arc($xc,$yc,$i+1,$i+1,0,360);
7032 // Linear Color InterPolation
7033 function lip($f,$t,$p) {
7034 $p = round($p,1);
7035 $r = $f[0] + ($t[0]-$f[0])*$p;
7036 $g = $f[1] + ($t[1]-$f[1])*$p;
7037 $b = $f[2] + ($t[2]-$f[2])*$p;
7038 return array($r,$g,$b);
7041 // Anti-aliased line.
7042 // Note that this is roughly 8 times slower then a normal line!
7043 function WuLine($x1,$y1,$x2,$y2) {
7044 // Get foreground line color
7045 $lc = imagecolorsforindex($this->img,$this->current_color);
7046 $lc = array($lc["red"],$lc["green"],$lc["blue"]);
7048 $dx = $x2-$x1;
7049 $dy = $y2-$y1;
7051 if( abs($dx) > abs($dy) ) {
7052 if( $dx<0 ) {
7053 $dx = -$dx;$dy = -$dy;
7054 $tmp=$x2;$x2=$x1;$x1=$tmp;
7055 $tmp=$y2;$y2=$y1;$y1=$tmp;
7057 $x=$x1<<16; $y=$y1<<16;
7058 $yinc = ($dy*65535)/$dx;
7059 while( ($x >> 16) < $x2 ) {
7061 $bc = @imagecolorsforindex($this->img,imagecolorat($this->img,$x>>16,$y>>16));
7062 if( $bc <= 0 ) {
7063 JpGraphError::RaiseL(25100);//('Problem with color palette and your GD setup. Please disable anti-aliasing or use GD2 with true-color. If you have GD2 library installed please make sure that you have set the USE_GD2 constant to true and that truecolor is enabled.');
7065 $bc=array($bc["red"],$bc["green"],$bc["blue"]);
7067 $this->SetColor($this->lip($lc,$bc,($y & 0xFFFF)/65535));
7068 imagesetpixel($this->img,$x>>16,$y>>16,$this->current_color);
7069 $this->SetColor($this->lip($lc,$bc,(~$y & 0xFFFF)/65535));
7070 imagesetpixel($this->img,$x>>16,($y>>16)+1,$this->current_color);
7071 $x += 65536; $y += $yinc;
7074 else {
7075 if( $dy<0 ) {
7076 $dx = -$dx;$dy = -$dy;
7077 $tmp=$x2;$x2=$x1;$x1=$tmp;
7078 $tmp=$y2;$y2=$y1;$y1=$tmp;
7080 $x=$x1<<16; $y=$y1<<16;
7081 $xinc = ($dx*65535)/$dy;
7082 while( ($y >> 16) < $y2 ) {
7084 $bc = @imagecolorsforindex($this->img,imagecolorat($this->img,$x>>16,$y>>16));
7085 if( $bc <= 0 ) {
7086 JpGraphError::RaiseL(25100);//('Problem with color palette and your GD setup. Please disable anti-aliasing or use GD2 with true-color. If you have GD2 library installed please make sure that you have set the USE_GD2 constant to true and truecolor is enabled.');
7090 $bc=array($bc["red"],$bc["green"],$bc["blue"]);
7092 $this->SetColor($this->lip($lc,$bc,($x & 0xFFFF)/65535));
7093 imagesetpixel($this->img,$x>>16,$y>>16,$this->current_color);
7094 $this->SetColor($this->lip($lc,$bc,(~$x & 0xFFFF)/65535));
7095 imagesetpixel($this->img,($x>>16)+1,$y>>16,$this->current_color);
7096 $y += 65536; $x += $xinc;
7099 $this->SetColor($lc);
7100 imagesetpixel($this->img,$x2,$y2,$this->current_color);
7101 imagesetpixel($this->img,$x1,$y1,$this->current_color);
7104 // Set line style dashed, dotted etc
7105 function SetLineStyle($s) {
7106 if( is_numeric($s) ) {
7107 if( $s<1 || $s>4 )
7108 JpGraphError::RaiseL(25101,$s);//(" Illegal numeric argument to SetLineStyle(): ($s)");
7110 elseif( is_string($s) ) {
7111 if( $s == "solid" ) $s=1;
7112 elseif( $s == "dotted" ) $s=2;
7113 elseif( $s == "dashed" ) $s=3;
7114 elseif( $s == "longdashed" ) $s=4;
7115 else JpGraphError::RaiseL(25102,$s);//(" Illegal string argument to SetLineStyle(): $s");
7117 else JpGraphError::RaiseL(25103,$s);//(" Illegal argument to SetLineStyle $s");
7118 $this->line_style=$s;
7121 // Same as Line but take the line_style into account
7122 function StyleLine($x1,$y1,$x2,$y2) {
7123 switch( $this->line_style ) {
7124 case 1:// Solid
7125 $this->Line($x1,$y1,$x2,$y2);
7126 break;
7127 case 2: // Dotted
7128 $this->DashedLine($x1,$y1,$x2,$y2,1,6);
7129 break;
7130 case 3: // Dashed
7131 $this->DashedLine($x1,$y1,$x2,$y2,2,4);
7132 break;
7133 case 4: // Longdashes
7134 $this->DashedLine($x1,$y1,$x2,$y2,8,6);
7135 break;
7136 default:
7137 JpGraphError::RaiseL(25104,$this->line_style);//(" Unknown line style: $this->line_style ");
7138 break;
7142 function Line($x1,$y1,$x2,$y2) {
7144 $x1 = round($x1);
7145 $x2 = round($x2);
7146 $y1 = round($y1);
7147 $y2 = round($y2);
7149 if( $this->line_weight==0 ) return;
7150 if( $this->use_anti_aliasing ) {
7151 $dx = $x2-$x1;
7152 $dy = $y2-$y1;
7153 // Vertical, Horizontal or 45 lines don't need anti-aliasing
7154 if( $dx!=0 && $dy!=0 && $dx!=$dy ) {
7155 $this->WuLine($x1,$y1,$x2,$y2);
7156 return;
7159 if( $this->line_weight==1 ) {
7160 imageline($this->img,$x1,$y1,$x2,$y2,$this->current_color);
7162 elseif( $x1==$x2 ) { // Special case for vertical lines
7163 imageline($this->img,$x1,$y1,$x2,$y2,$this->current_color);
7164 $w1=floor($this->line_weight/2);
7165 $w2=floor(($this->line_weight-1)/2);
7166 for($i=1; $i<=$w1; ++$i)
7167 imageline($this->img,$x1+$i,$y1,$x2+$i,$y2,$this->current_color);
7168 for($i=1; $i<=$w2; ++$i)
7169 imageline($this->img,$x1-$i,$y1,$x2-$i,$y2,$this->current_color);
7171 elseif( $y1==$y2 ) { // Special case for horizontal lines
7172 imageline($this->img,$x1,$y1,$x2,$y2,$this->current_color);
7173 $w1=floor($this->line_weight/2);
7174 $w2=floor(($this->line_weight-1)/2);
7175 for($i=1; $i<=$w1; ++$i)
7176 imageline($this->img,$x1,$y1+$i,$x2,$y2+$i,$this->current_color);
7177 for($i=1; $i<=$w2; ++$i)
7178 imageline($this->img,$x1,$y1-$i,$x2,$y2-$i,$this->current_color);
7180 else { // General case with a line at an angle
7181 $a = atan2($y1-$y2,$x2-$x1);
7182 // Now establish some offsets from the center. This gets a little
7183 // bit involved since we are dealing with integer functions and we
7184 // want the apperance to be as smooth as possible and never be thicker
7185 // then the specified width.
7187 // We do the trig stuff to make sure that the endpoints of the line
7188 // are perpendicular to the line itself.
7189 $dx=(sin($a)*$this->line_weight/2);
7190 $dy=(cos($a)*$this->line_weight/2);
7192 $pnts = array($x2+$dx,$y2+$dy,$x2-$dx,$y2-$dy,$x1-$dx,$y1-$dy,$x1+$dx,$y1+$dy);
7193 imagefilledpolygon($this->img,$pnts,count($pnts)/2,$this->current_color);
7195 $this->lastx=$x2; $this->lasty=$y2;
7198 function Polygon($p,$closed=FALSE,$fast=FALSE) {
7199 if( $this->line_weight==0 ) return;
7200 $n=count($p);
7201 $oldx = $p[0];
7202 $oldy = $p[1];
7203 if( $fast ) {
7204 for( $i=2; $i < $n; $i+=2 ) {
7205 imageline($this->img,$oldx,$oldy,$p[$i],$p[$i+1],$this->current_color);
7206 $oldx = $p[$i];
7207 $oldy = $p[$i+1];
7209 if( $closed ) {
7210 imageline($this->img,$p[$n*2-2],$p[$n*2-1],$p[0],$p[1],$this->current_color);
7213 else {
7214 for( $i=2; $i < $n; $i+=2 ) {
7215 $this->StyleLine($oldx,$oldy,$p[$i],$p[$i+1]);
7216 $oldx = $p[$i];
7217 $oldy = $p[$i+1];
7220 if( $closed )
7221 $this->Line($oldx,$oldy,$p[0],$p[1]);
7224 function FilledPolygon($pts) {
7225 $n=count($pts);
7226 if( $n == 0 ) {
7227 JpGraphError::RaiseL(25105);//('NULL data specified for a filled polygon. Check that your data is not NULL.');
7229 for($i=0; $i < $n; ++$i)
7230 $pts[$i] = round($pts[$i]);
7231 imagefilledpolygon($this->img,$pts,count($pts)/2,$this->current_color);
7234 function Rectangle($xl,$yu,$xr,$yl) {
7235 $this->Polygon(array($xl,$yu,$xr,$yu,$xr,$yl,$xl,$yl,$xl,$yu));
7238 function FilledRectangle($xl,$yu,$xr,$yl) {
7239 $this->FilledPolygon(array($xl,$yu,$xr,$yu,$xr,$yl,$xl,$yl));
7242 function FilledRectangle2($xl,$yu,$xr,$yl,$color1,$color2,$style=1) {
7243 // Fill a rectangle with lines of two colors
7244 if( $style===1 ) {
7245 // Horizontal stripe
7246 if( $yl < $yu ) {
7247 $t = $yl; $yl=$yu; $yu=$t;
7249 for( $y=$yu; $y <= $yl; ++$y) {
7250 $this->SetColor($color1);
7251 $this->Line($xl,$y,$xr,$y);
7252 ++$y;
7253 $this->SetColor($color2);
7254 $this->Line($xl,$y,$xr,$y);
7257 else {
7258 if( $xl < $xl ) {
7259 $t = $xl; $xl=$xr; $xr=$t;
7261 for( $x=$xl; $x <= $xr; ++$x) {
7262 $this->SetColor($color1);
7263 $this->Line($x,$yu,$x,$yl);
7264 ++$x;
7265 $this->SetColor($color2);
7266 $this->Line($x,$yu,$x,$yl);
7271 function ShadowRectangle($xl,$yu,$xr,$yl,$fcolor=false,$shadow_width=3,$shadow_color=array(102,102,102)) {
7272 // This is complicated by the fact that we must also handle the case where
7273 // the reactangle has no fill color
7274 $this->PushColor($shadow_color);
7275 $this->FilledRectangle($xr-$shadow_width,$yu+$shadow_width,$xr,$yl-$shadow_width-1);
7276 $this->FilledRectangle($xl+$shadow_width,$yl-$shadow_width,$xr,$yl);
7277 //$this->FilledRectangle($xl+$shadow_width,$yu+$shadow_width,$xr,$yl);
7278 $this->PopColor();
7279 if( $fcolor==false )
7280 $this->Rectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1);
7281 else {
7282 $this->PushColor($fcolor);
7283 $this->FilledRectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1);
7284 $this->PopColor();
7285 $this->Rectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1);
7289 function FilledRoundedRectangle($xt,$yt,$xr,$yl,$r=5) {
7290 if( $r==0 ) {
7291 $this->FilledRectangle($xt,$yt,$xr,$yl);
7292 return;
7295 // To avoid overlapping fillings (which will look strange
7296 // when alphablending is enabled) we have no choice but
7297 // to fill the five distinct areas one by one.
7299 // Center square
7300 $this->FilledRectangle($xt+$r,$yt+$r,$xr-$r,$yl-$r);
7301 // Top band
7302 $this->FilledRectangle($xt+$r,$yt,$xr-$r,$yt+$r-1);
7303 // Bottom band
7304 $this->FilledRectangle($xt+$r,$yl-$r+1,$xr-$r,$yl);
7305 // Left band
7306 $this->FilledRectangle($xt,$yt+$r+1,$xt+$r-1,$yl-$r);
7307 // Right band
7308 $this->FilledRectangle($xr-$r+1,$yt+$r,$xr,$yl-$r);
7310 // Topleft & Topright arc
7311 $this->FilledArc($xt+$r,$yt+$r,$r*2,$r*2,180,270);
7312 $this->FilledArc($xr-$r,$yt+$r,$r*2,$r*2,270,360);
7314 // Bottomleft & Bottom right arc
7315 $this->FilledArc($xt+$r,$yl-$r,$r*2,$r*2,90,180);
7316 $this->FilledArc($xr-$r,$yl-$r,$r*2,$r*2,0,90);
7320 function RoundedRectangle($xt,$yt,$xr,$yl,$r=5) {
7322 if( $r==0 ) {
7323 $this->Rectangle($xt,$yt,$xr,$yl);
7324 return;
7327 // Top & Bottom line
7328 $this->Line($xt+$r,$yt,$xr-$r,$yt);
7329 $this->Line($xt+$r,$yl,$xr-$r,$yl);
7331 // Left & Right line
7332 $this->Line($xt,$yt+$r,$xt,$yl-$r);
7333 $this->Line($xr,$yt+$r,$xr,$yl-$r);
7335 // Topleft & Topright arc
7336 $this->Arc($xt+$r,$yt+$r,$r*2,$r*2,180,270);
7337 $this->Arc($xr-$r,$yt+$r,$r*2,$r*2,270,360);
7339 // Bottomleft & Bottomright arc
7340 $this->Arc($xt+$r,$yl-$r,$r*2,$r*2,90,180);
7341 $this->Arc($xr-$r,$yl-$r,$r*2,$r*2,0,90);
7344 function FilledBevel($x1,$y1,$x2,$y2,$depth=2,$color1='white@0.4',$color2='darkgray@0.4') {
7345 $this->FilledRectangle($x1,$y1,$x2,$y2);
7346 $this->Bevel($x1,$y1,$x2,$y2,$depth,$color1,$color2);
7349 function Bevel($x1,$y1,$x2,$y2,$depth=2,$color1='white@0.4',$color2='black@0.5') {
7350 $this->PushColor($color1);
7351 for( $i=0; $i < $depth; ++$i ) {
7352 $this->Line($x1+$i,$y1+$i,$x1+$i,$y2-$i);
7353 $this->Line($x1+$i,$y1+$i,$x2-$i,$y1+$i);
7355 $this->PopColor();
7357 $this->PushColor($color2);
7358 for( $i=0; $i < $depth; ++$i ) {
7359 $this->Line($x1+$i,$y2-$i,$x2-$i,$y2-$i);
7360 $this->Line($x2-$i,$y1+$i,$x2-$i,$y2-$i-1);
7362 $this->PopColor();
7365 function StyleLineTo($x,$y) {
7366 $this->StyleLine($this->lastx,$this->lasty,$x,$y);
7367 $this->lastx=$x;
7368 $this->lasty=$y;
7371 function LineTo($x,$y) {
7372 $this->Line($this->lastx,$this->lasty,$x,$y);
7373 $this->lastx=$x;
7374 $this->lasty=$y;
7377 function Point($x,$y) {
7378 imagesetpixel($this->img,round($x),round($y),$this->current_color);
7381 function Fill($x,$y) {
7382 imagefill($this->img,round($x),round($y),$this->current_color);
7385 function FillToBorder($x,$y,$aBordColor) {
7386 $bc = $this->rgb->allocate($aBordColor);
7387 if( $bc == -1 ) {
7388 JpGraphError::RaiseL(25106);//('Image::FillToBorder : Can not allocate more colors');
7389 exit();
7391 imagefilltoborder($this->img,round($x),round($y),$bc,$this->current_color);
7394 function DashedLine($x1,$y1,$x2,$y2,$dash_length=1,$dash_space=4) {
7396 $x1 = round($x1);
7397 $x2 = round($x2);
7398 $y1 = round($y1);
7399 $y2 = round($y2);
7401 // Code based on, but not identical to, work by Ariel Garza and James Pine
7402 $line_length = ceil (sqrt(pow(($x2 - $x1),2) + pow(($y2 - $y1),2)) );
7403 $dx = ($line_length) ? ($x2 - $x1) / $line_length : 0;
7404 $dy = ($line_length) ? ($y2 - $y1) / $line_length : 0;
7405 $lastx = $x1; $lasty = $y1;
7406 $xmax = max($x1,$x2);
7407 $xmin = min($x1,$x2);
7408 $ymax = max($y1,$y2);
7409 $ymin = min($y1,$y2);
7410 for ($i = 0; $i < $line_length; $i += ($dash_length + $dash_space)) {
7411 $x = ($dash_length * $dx) + $lastx;
7412 $y = ($dash_length * $dy) + $lasty;
7414 // The last section might overshoot so we must take a computational hit
7415 // and check this.
7416 if( $x>$xmax ) $x=$xmax;
7417 if( $y>$ymax ) $y=$ymax;
7419 if( $x<$xmin ) $x=$xmin;
7420 if( $y<$ymin ) $y=$ymin;
7422 $this->Line($lastx,$lasty,$x,$y);
7423 $lastx = $x + ($dash_space * $dx);
7424 $lasty = $y + ($dash_space * $dy);
7428 function SetExpired($aFlg=true) {
7429 $this->expired = $aFlg;
7432 // Generate image header
7433 function Headers() {
7435 // In case we are running from the command line with the client version of
7436 // PHP we can't send any headers.
7437 $sapi = php_sapi_name();
7438 if( $sapi == 'cli' )
7439 return;
7441 if( headers_sent($file,$lineno) ) {
7442 $file=basename($file);
7443 $t = new ErrMsgText();
7444 $msg = $t->Get(10,$file,$lineno);
7445 die($msg);
7448 if ($this->expired) {
7449 header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
7450 header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT");
7451 header("Cache-Control: no-cache, must-revalidate");
7452 header("Pragma: no-cache");
7454 header("Content-type: image/$this->img_format");
7457 // Adjust image quality for formats that allow this
7458 function SetQuality($q) {
7459 $this->quality = $q;
7462 // Stream image to browser or to file
7463 function Stream($aFile="") {
7464 $func="image".$this->img_format;
7465 if( $this->img_format=="jpeg" && $this->quality != null ) {
7466 $res = @$func($this->img,$aFile,$this->quality);
7468 else {
7469 if( $aFile != "" ) {
7470 $res = @$func($this->img,$aFile);
7471 if( !$res )
7472 JpGraphError::RaiseL(25107,$aFile);//("Can't write to file '$aFile'. Check that the process running PHP has enough permission.");
7474 else {
7475 $res = @$func($this->img);
7476 if( !$res )
7477 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.");
7483 // Clear resource tide up by image
7484 function Destroy() {
7485 imagedestroy($this->img);
7488 // Specify image format. Note depending on your installation
7489 // of PHP not all formats may be supported.
7490 function SetImgFormat($aFormat,$aQuality=75) {
7491 $this->quality = $aQuality;
7492 $aFormat = strtolower($aFormat);
7493 $tst = true;
7494 $supported = imagetypes();
7495 if( $aFormat=="auto" ) {
7496 if( $supported & IMG_PNG )
7497 $this->img_format="png";
7498 elseif( $supported & IMG_JPG )
7499 $this->img_format="jpeg";
7500 elseif( $supported & IMG_GIF )
7501 $this->img_format="gif";
7502 else
7503 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.");
7505 return true;
7507 else {
7508 if( $aFormat=="jpeg" || $aFormat=="png" || $aFormat=="gif" ) {
7509 if( $aFormat=="jpeg" && !($supported & IMG_JPG) )
7510 $tst=false;
7511 elseif( $aFormat=="png" && !($supported & IMG_PNG) )
7512 $tst=false;
7513 elseif( $aFormat=="gif" && !($supported & IMG_GIF) )
7514 $tst=false;
7515 else {
7516 $this->img_format=$aFormat;
7517 return true;
7520 else
7521 $tst=false;
7522 if( !$tst )
7523 JpGraphError::RaiseL(25110,$aFormat);//(" Your PHP installation does not support the chosen graphic format: $aFormat");
7526 } // CLASS
7528 //===================================================
7529 // CLASS RotImage
7530 // Description: Exactly as Image but draws the image at
7531 // a specified angle around a specified rotation point.
7532 //===================================================
7533 class RotImage extends Image {
7534 var $m=array();
7535 var $a=0;
7536 var $dx=0,$dy=0,$transx=0,$transy=0;
7538 function __construct($aWidth,$aHeight,$a=0,$aFormat=DEFAULT_GFORMAT) {
7539 parent::__construct($aWidth,$aHeight,$aFormat);
7540 $this->dx=$this->left_margin+$this->plotwidth/2;
7541 $this->dy=$this->top_margin+$this->plotheight/2;
7542 $this->SetAngle($a);
7545 function SetCenter($dx,$dy) {
7546 $old_dx = $this->dx;
7547 $old_dy = $this->dy;
7548 $this->dx=$dx;
7549 $this->dy=$dy;
7550 $this->SetAngle($this->a);
7551 return array($old_dx,$old_dy);
7554 function SetTranslation($dx,$dy) {
7555 $old = array($this->transx,$this->transy);
7556 $this->transx = $dx;
7557 $this->transy = $dy;
7558 return $old;
7561 function UpdateRotMatrice() {
7562 $a = $this->a;
7563 $a *= M_PI/180;
7564 $sa=sin($a); $ca=cos($a);
7565 // Create the rotation matrix
7566 $this->m[0][0] = $ca;
7567 $this->m[0][1] = -$sa;
7568 $this->m[0][2] = $this->dx*(1-$ca) + $sa*$this->dy ;
7569 $this->m[1][0] = $sa;
7570 $this->m[1][1] = $ca;
7571 $this->m[1][2] = $this->dy*(1-$ca) - $sa*$this->dx ;
7574 function SetAngle($a) {
7575 $tmp = $this->a;
7576 $this->a = $a;
7577 $this->UpdateRotMatrice();
7578 return $tmp;
7581 function Circle($xc,$yc,$r) {
7582 // Circle get's rotated through the Arc() call
7583 // made in the parent class
7584 parent::Circle($xc,$yc,$r);
7587 function FilledCircle($xc,$yc,$r) {
7588 // If we use GD1 then Image::FilledCircle will use a
7589 // call to Arc so it will get rotated through the Arc
7590 // call.
7591 if( $GLOBALS['gd2'] ) {
7592 list($xc,$yc) = $this->Rotate($xc,$yc);
7594 parent::FilledCircle($xc,$yc,$r);
7597 function Arc($xc,$yc,$w,$h,$s,$e) {
7598 list($xc,$yc) = $this->Rotate($xc,$yc);
7599 $s += $this->a;
7600 $e += $this->a;
7601 parent::Arc($xc,$yc,$w,$h,$s,$e);
7604 function FilledArc($xc,$yc,$w,$h,$s,$e, $style='') {
7605 list($xc,$yc) = $this->Rotate($xc,$yc);
7606 $s += $this->a;
7607 $e += $this->a;
7608 parent::FilledArc($xc,$yc,$w,$h,$s,$e);
7611 function SetMargin($lm,$rm,$tm,$bm) {
7612 parent::SetMargin($lm,$rm,$tm,$bm);
7613 $this->dx=$this->left_margin+$this->plotwidth/2;
7614 $this->dy=$this->top_margin+$this->plotheight/2;
7615 $this->UpdateRotMatrice();
7618 function Rotate($x,$y) {
7619 // Optimization. Ignore rotation if Angle==0 || ANgle==360
7620 if( $this->a == 0 || $this->a == 360 ) {
7621 return array($x + $this->transx, $y + $this->transy );
7623 else {
7624 $x1=round($this->m[0][0]*$x + $this->m[0][1]*$y,1) + $this->m[0][2] + $this->transx;
7625 $y1=round($this->m[1][0]*$x + $this->m[1][1]*$y,1) + $this->m[1][2] + $this->transy;
7626 return array($x1,$y1);
7630 function CopyMerge($fromImg,$toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$fromWidth=-1,$fromHeight=-1,$aMix=100) {
7631 list($toX,$toY) = $this->Rotate($toX,$toY);
7632 parent::CopyMerge($fromImg,$toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$fromWidth,$fromHeight,$aMix);
7636 function ArrRotate($pnts) {
7637 $n = count($pnts)-1;
7638 for($i=0; $i < $n; $i+=2) {
7639 list ($x,$y) = $this->Rotate($pnts[$i],$pnts[$i+1]);
7640 $pnts[$i] = $x; $pnts[$i+1] = $y;
7642 return $pnts;
7645 function Line($x1,$y1,$x2,$y2) {
7646 list($x1,$y1) = $this->Rotate($x1,$y1);
7647 list($x2,$y2) = $this->Rotate($x2,$y2);
7648 parent::Line($x1,$y1,$x2,$y2);
7651 function Rectangle($x1,$y1,$x2,$y2) {
7652 // Rectangle uses Line() so it will be rotated through that call
7653 parent::Rectangle($x1,$y1,$x2,$y2);
7656 function FilledRectangle($x1,$y1,$x2,$y2) {
7657 if( $y1==$y2 || $x1==$x2 )
7658 $this->Line($x1,$y1,$x2,$y2);
7659 else
7660 $this->FilledPolygon(array($x1,$y1,$x2,$y1,$x2,$y2,$x1,$y2));
7663 function Polygon($pnts,$closed=FALSE,$fast=false) {
7664 //Polygon uses Line() so it will be rotated through that call
7665 parent::Polygon($pnts,$closed,$fast);
7668 function FilledPolygon($pnts) {
7669 parent::FilledPolygon($this->ArrRotate($pnts));
7672 function Point($x,$y) {
7673 list($xp,$yp) = $this->Rotate($x,$y);
7674 parent::Point($xp,$yp);
7677 function StrokeText($x,$y,$txt,$dir=0,$paragraph_align="left",$debug=false) {
7678 list($xp,$yp) = $this->Rotate($x,$y);
7679 return parent::StrokeText($xp,$yp,$txt,$dir,$paragraph_align,$debug);
7683 //===================================================
7684 // CLASS ImgStreamCache
7685 // Description: Handle caching of graphs to files
7686 //===================================================
7687 class ImgStreamCache {
7688 var $cache_dir;
7689 var $img=null;
7690 var $timeout=0; // Infinite timeout
7691 //---------------
7692 // CONSTRUCTOR
7693 function __construct(&$aImg, $aCacheDir=CACHE_DIR) {
7694 $this->img = &$aImg;
7695 $this->cache_dir = $aCacheDir;
7698 //---------------
7699 // PUBLIC METHODS
7701 // Specify a timeout (in minutes) for the file. If the file is older then the
7702 // timeout value it will be overwritten with a newer version.
7703 // If timeout is set to 0 this is the same as infinite large timeout and if
7704 // timeout is set to -1 this is the same as infinite small timeout
7705 function SetTimeout($aTimeout) {
7706 $this->timeout=$aTimeout;
7709 // Output image to browser and also write it to the cache
7710 function PutAndStream(&$aImage,$aCacheFileName,$aInline,$aStrokeFileName) {
7711 // Some debugging code to brand the image with numbe of colors
7712 // used
7713 GLOBAL $gJpgBrandTiming;
7715 if( $gJpgBrandTiming ) {
7716 global $tim;
7717 $t=$tim->Pop()/1000.0;
7718 $c=$aImage->SetColor("black");
7719 $t=sprintf(BRAND_TIME_FORMAT,round($t,3));
7720 imagestring($this->img->img,2,5,$this->img->height-20,$t,$c);
7723 // Check if we should stroke the image to an arbitrary file
7724 if( _FORCE_IMGTOFILE ) {
7725 $aStrokeFileName = _FORCE_IMGDIR.GenImgName();
7728 if( $aStrokeFileName!="" ) {
7729 if( $aStrokeFileName == "auto" )
7730 $aStrokeFileName = GenImgName();
7731 if( file_exists($aStrokeFileName) ) {
7732 // Delete the old file
7733 if( !@unlink($aStrokeFileName) )
7734 JpGraphError::RaiseL(25111,$aStrokeFileName);//(" Can't delete cached image $aStrokeFileName. Permission problem?");
7736 $aImage->Stream($aStrokeFileName);
7737 return;
7740 if( $aCacheFileName != "" && USE_CACHE) {
7742 $aCacheFileName = $this->cache_dir . $aCacheFileName;
7743 if( file_exists($aCacheFileName) ) {
7744 if( !$aInline ) {
7745 // If we are generating image off-line (just writing to the cache)
7746 // and the file exists and is still valid (no timeout)
7747 // then do nothing, just return.
7748 $diff=time()-filemtime($aCacheFileName);
7749 if( $diff < 0 )
7750 JpGraphError::RaiseL(25112,$aCacheFileName);//(" Cached imagefile ($aCacheFileName) has file date in the future!!");
7751 if( $this->timeout>0 && ($diff <= $this->timeout*60) )
7752 return;
7754 if( !@unlink($aCacheFileName) )
7755 JpGraphError::RaiseL(25113,$aStrokeFileName);//(" Can't delete cached image $aStrokeFileName. Permission problem?");
7756 $aImage->Stream($aCacheFileName);
7758 else {
7759 $this->MakeDirs(dirname($aCacheFileName));
7760 if( !is_writeable(dirname($aCacheFileName)) ) {
7761 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.');
7763 $aImage->Stream($aCacheFileName);
7766 $res=true;
7767 // Set group to specified
7768 if( CACHE_FILE_GROUP != "" )
7769 $res = @chgrp($aCacheFileName,CACHE_FILE_GROUP);
7770 if( CACHE_FILE_MOD != "" )
7771 $res = @chmod($aCacheFileName,CACHE_FILE_MOD);
7772 if( !$res )
7773 JpGraphError::RaiseL(25115,$aStrokeFileName);//(" Can't set permission for cached image $aStrokeFileName. Permission problem?");
7775 $aImage->Destroy();
7776 if( $aInline ) {
7777 if ($fh = @fopen($aCacheFileName, "rb") ) {
7778 $this->img->Headers();
7779 fpassthru($fh);
7780 return;
7782 else
7783 JpGraphError::RaiseL(25116,$aFile);//(" Cant open file from cache [$aFile]");
7786 elseif( $aInline ) {
7787 $this->img->Headers();
7788 $aImage->Stream();
7789 return;
7793 // Check if a given image is in cache and in that case
7794 // pass it directly on to web browser. Return false if the
7795 // image file doesn't exist or exists but is to old
7796 function GetAndStream($aCacheFileName) {
7797 $aCacheFileName = $this->cache_dir.$aCacheFileName;
7798 if ( USE_CACHE && file_exists($aCacheFileName) && $this->timeout>=0 ) {
7799 $diff=time()-filemtime($aCacheFileName);
7800 if( $this->timeout>0 && ($diff > $this->timeout*60) ) {
7801 return false;
7803 else {
7804 if ($fh = @fopen($aCacheFileName, "rb")) {
7805 $this->img->Headers();
7806 fpassthru($fh);
7807 return true;
7809 else
7810 JpGraphError::RaiseL(25117,$aCacheFileName);//(" Can't open cached image \"$aCacheFileName\" for reading.");
7813 return false;
7816 //---------------
7817 // PRIVATE METHODS
7818 // Create all necessary directories in a path
7819 function MakeDirs($aFile) {
7820 $dirs = array();
7821 while ( !(file_exists($aFile)) ) {
7822 $dirs[] = $aFile;
7823 $aFile = dirname($aFile);
7825 for ($i = sizeof($dirs)-1; $i>=0; $i--) {
7826 if(! @mkdir($dirs[$i],0777) )
7827 JpGraphError::RaiseL(25118,$aFile);//(" Can't create directory $aFile. Make sure PHP has write permission to this directory.");
7828 // We also specify mode here after we have changed group.
7829 // This is necessary if Apache user doesn't belong the
7830 // default group and hence can't specify group permission
7831 // in the previous mkdir() call
7832 if( CACHE_FILE_GROUP != "" ) {
7833 $res=true;
7834 $res =@chgrp($dirs[$i],CACHE_FILE_GROUP);
7835 $res &= @chmod($dirs[$i],0777);
7836 if( !$res )
7837 JpGraphError::RaiseL(25119,$aFile);//(" Can't set permissions for $aFile. Permission problems?");
7840 return true;
7842 } // CLASS Cache
7844 //===================================================
7845 // CLASS Legend
7846 // Description: Responsible for drawing the box containing
7847 // all the legend text for the graph
7848 //===================================================
7849 DEFINE('_DEFAULT_LPM_SIZE',8);
7850 class Legend {
7851 var $color=array(0,0,0); // Default fram color
7852 var $fill_color=array(235,235,235); // Default fill color
7853 var $shadow=true; // Shadow around legend "box"
7854 var $shadow_color='gray';
7855 var $txtcol=array();
7856 var $mark_abs_hsize=_DEFAULT_LPM_SIZE, $mark_abs_vsize=_DEFAULT_LPM_SIZE;
7857 var $xmargin=5,$ymargin=3,$shadow_width=2;
7858 var $xlmargin=2, $ylmargin='';
7859 var $xpos=0.05, $ypos=0.15, $xabspos=-1, $yabspos=-1;
7860 var $halign="right", $valign="top";
7861 var $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12;
7862 var $font_color='black';
7863 var $hide=false,$layout_n=1;
7864 var $weight=1,$frameweight=1;
7865 var $csimareas='';
7866 var $reverse = false ;
7867 //---------------
7868 // CONSTRUCTOR
7869 function __construct() {
7870 // Empty
7872 //---------------
7873 // PUBLIC METHODS
7874 function Hide($aHide=true) {
7875 $this->hide=$aHide;
7878 function SetHColMargin($aXMarg) {
7879 $this->xmargin = $aXMarg;
7882 function SetVColMargin($aSpacing) {
7883 $this->ymargin = $aSpacing ;
7886 function SetLeftMargin($aXMarg) {
7887 $this->xlmargin = $aXMarg;
7890 // Synonym
7891 function SetLineSpacing($aSpacing) {
7892 $this->ymargin = $aSpacing ;
7895 function SetShadow($aShow='gray',$aWidth=2) {
7896 if( is_string($aShow) ) {
7897 $this->shadow_color = $aShow;
7898 $this->shadow=true;
7900 else
7901 $this->shadow=$aShow;
7902 $this->shadow_width=$aWidth;
7905 function SetMarkAbsSize($aSize) {
7906 $this->mark_abs_vsize = $aSize ;
7907 $this->mark_abs_hsize = $aSize ;
7910 function SetMarkAbsVSize($aSize) {
7911 $this->mark_abs_vsize = $aSize ;
7914 function SetMarkAbsHSize($aSize) {
7915 $this->mark_abs_hsize = $aSize ;
7918 function SetLineWeight($aWeight) {
7919 $this->weight = $aWeight;
7922 function SetFrameWeight($aWeight) {
7923 $this->frameweight = $aWeight;
7926 function SetLayout($aDirection=LEGEND_VERT) {
7927 $this->layout_n = $aDirection==LEGEND_VERT ? 1 : 99 ;
7930 function SetColumns($aCols) {
7931 $this->layout_n = $aCols ;
7934 function SetReverse($f=true) {
7935 $this->reverse = $f ;
7938 // Set color on frame around box
7939 function SetColor($aFontColor,$aColor='black') {
7940 $this->font_color=$aFontColor;
7941 $this->color=$aColor;
7944 function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
7945 $this->font_family = $aFamily;
7946 $this->font_style = $aStyle;
7947 $this->font_size = $aSize;
7950 function SetPos($aX,$aY,$aHAlign="right",$aVAlign="top") {
7951 $this->Pos($aX,$aY,$aHAlign,$aVAlign);
7954 function SetAbsPos($aX,$aY,$aHAlign="right",$aVAlign="top") {
7955 $this->xabspos=$aX;
7956 $this->yabspos=$aY;
7957 $this->halign=$aHAlign;
7958 $this->valign=$aVAlign;
7962 function Pos($aX,$aY,$aHAlign="right",$aVAlign="top") {
7963 if( !($aX<1 && $aY<1) )
7964 JpGraphError::RaiseL(25120);//(" Position for legend must be given as percentage in range 0-1");
7965 $this->xpos=$aX;
7966 $this->ypos=$aY;
7967 $this->halign=$aHAlign;
7968 $this->valign=$aVAlign;
7971 function SetFillColor($aColor) {
7972 $this->fill_color=$aColor;
7975 function Add($aTxt,$aColor,$aPlotmark="",$aLinestyle=0,$csimtarget="",$csimalt="") {
7976 $this->txtcol[]=array($aTxt,$aColor,$aPlotmark,$aLinestyle,$csimtarget,$csimalt);
7979 function GetCSIMAreas() {
7980 return $this->csimareas;
7983 function Stroke(&$aImg) {
7984 // Constant
7985 $fillBoxFrameWeight=1;
7987 if( $this->hide ) return;
7989 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
7991 if( $this->reverse ) {
7992 $this->txtcol = array_reverse($this->txtcol);
7995 $n=count($this->txtcol);
7996 if( $n == 0 ) return;
7998 // Find out the max width and height of each column to be able
7999 // to size the legend box.
8000 $numcolumns = ($n > $this->layout_n ? $this->layout_n : $n);
8001 for( $i=0; $i < $numcolumns; ++$i ) {
8002 $colwidth[$i] = $aImg->GetTextWidth($this->txtcol[$i][0]) +
8003 2*$this->xmargin + 2*$this->mark_abs_hsize;
8004 $colheight[$i] = 0;
8007 // Find our maximum height in each row
8008 $rows = 0 ; $rowheight[0] = 0;
8009 for( $i=0; $i < $n; ++$i ) {
8010 $h = max($this->mark_abs_vsize,$aImg->GetTextHeight($this->txtcol[$i][0]))+$this->ymargin;
8011 if( $i % $numcolumns == 0 ) {
8012 $rows++;
8013 $rowheight[$rows-1] = 0;
8015 $rowheight[$rows-1] = max($rowheight[$rows-1],$h);
8018 $abs_height = 0;
8019 for( $i=0; $i < $rows; ++$i ) {
8020 $abs_height += $rowheight[$i] ;
8023 // Make sure that the height is at least as high as mark size + ymargin
8024 $abs_height = max($abs_height,$this->mark_abs_vsize);
8026 // We add 3 extra pixels height to compensate for the difficult in
8027 // calculating font height
8028 $abs_height += $this->ymargin+3;
8030 // Find out the maximum width in each column
8031 for( $i=$numcolumns; $i < $n; ++$i ) {
8032 $colwidth[$i % $numcolumns] = max(
8033 $aImg->GetTextWidth($this->txtcol[$i][0])+2*$this->xmargin+2*$this->mark_abs_hsize,$colwidth[$i % $numcolumns]);
8036 // Get the total width
8037 $mtw = 0;
8038 for( $i=0; $i < $numcolumns; ++$i ) {
8039 $mtw += $colwidth[$i] ;
8042 // Find out maximum width we need for legend box
8043 $abs_width = $mtw+$this->xlmargin;
8045 if( $this->xabspos === -1 && $this->yabspos === -1 ) {
8046 $this->xabspos = $this->xpos*$aImg->width ;
8047 $this->yabspos = $this->ypos*$aImg->height ;
8050 // Positioning of the legend box
8051 if( $this->halign=="left" )
8052 $xp = $this->xabspos;
8053 elseif( $this->halign=="center" )
8054 $xp = $this->xabspos - $abs_width/2;
8055 else
8056 $xp = $aImg->width - $this->xabspos - $abs_width;
8058 $yp=$this->yabspos;
8059 if( $this->valign=="center" )
8060 $yp-=$abs_height/2;
8061 elseif( $this->valign=="bottom" )
8062 $yp-=$abs_height;
8064 // Stroke legend box
8065 $aImg->SetColor($this->color);
8066 $aImg->SetLineWeight($this->frameweight);
8067 $aImg->SetLineStyle('solid');
8069 if( $this->shadow )
8070 $aImg->ShadowRectangle($xp,$yp,$xp+$abs_width+$this->shadow_width,
8071 $yp+$abs_height+$this->shadow_width,
8072 $this->fill_color,$this->shadow_width,$this->shadow_color);
8073 else {
8074 $aImg->SetColor($this->fill_color);
8075 $aImg->FilledRectangle($xp,$yp,$xp+$abs_width,$yp+$abs_height);
8076 $aImg->SetColor($this->color);
8077 $aImg->Rectangle($xp,$yp,$xp+$abs_width,$yp+$abs_height);
8080 // x1,y1 is the position for the legend mark
8081 $x1=$xp+$this->mark_abs_hsize+$this->xlmargin;
8082 $y1=$yp + $this->ymargin;
8084 $f2 = round($aImg->GetTextHeight('X')/2);
8086 $grad = new Gradient($aImg);
8087 $patternFactory = null;
8089 // Now stroke each legend in turn
8090 // Each plot has added the following information to the legend
8091 // p[0] = Legend text
8092 // p[1] = Color,
8093 // p[2] = For markers a reference to the PlotMark object
8094 // p[3] = For lines the line style, for gradient the negative gradient style
8095 // p[4] = CSIM target
8096 // p[5] = CSIM Alt text
8097 $i = 1 ; $row = 0;
8098 foreach($this->txtcol as $p) {
8100 // STROKE DEBUG BOX
8101 if( _JPG_DEBUG ) {
8102 $aImg->SetLineWeight(1);
8103 $aImg->SetColor('red');
8104 $aImg->SetLineStyle('solid');
8105 $aImg->Rectangle($xp,$y1,$xp+$abs_width,$y1+$rowheight[$row]);
8108 $aImg->SetLineWeight($this->weight);
8109 $x1 = round($x1); $y1=round($y1);
8110 if ( $p[2] != "" && $p[2]->GetType() > -1 ) {
8111 // Make a plot mark legend
8112 $aImg->SetColor($p[1]);
8113 if( is_string($p[3]) || $p[3]>0 ) {
8114 $aImg->SetLineStyle($p[3]);
8115 $aImg->StyleLine($x1-$this->mark_abs_hsize,$y1+$f2,$x1+$this->mark_abs_hsize,$y1+$f2);
8117 // Stroke a mark with the standard size
8118 // (As long as it is not an image mark )
8119 if( $p[2]->GetType() != MARK_IMG ) {
8120 $p[2]->iFormatCallback = '';
8122 // Since size for circles is specified as the radius
8123 // this means that we must half the size to make the total
8124 // width behave as the other marks
8125 if( $p[2]->GetType() == MARK_FILLEDCIRCLE || $p[2]->GetType() == MARK_CIRCLE ) {
8126 $p[2]->SetSize(min($this->mark_abs_vsize,$this->mark_abs_hsize)/2);
8127 $p[2]->Stroke($aImg,$x1,$y1+$f2);
8129 else {
8130 $p[2]->SetSize(min($this->mark_abs_vsize,$this->mark_abs_hsize));
8131 $p[2]->Stroke($aImg,$x1,$y1+$f2);
8135 elseif ( $p[2] != "" && (is_string($p[3]) || $p[3]>0 ) ) {
8136 // Draw a styled line
8137 $aImg->SetColor($p[1]);
8138 $aImg->SetLineStyle($p[3]);
8139 $aImg->StyleLine($x1-1,$y1+$f2,$x1+$this->mark_abs_hsize,$y1+$f2);
8140 $aImg->StyleLine($x1-1,$y1+$f2+1,$x1+$this->mark_abs_hsize,$y1+$f2+1);
8142 else {
8143 // Draw a colored box
8144 $color = $p[1] ;
8145 // We make boxes slightly larger to better show
8146 $boxsize = min($this->mark_abs_vsize,$this->mark_abs_hsize) + 2 ;
8147 $ym = round($y1 + $f2 - $boxsize/2);
8148 // We either need to plot a gradient or a
8149 // pattern. To differentiate we use a kludge.
8150 // Patterns have a p[3] value of < -100
8151 if( $p[3] < -100 ) {
8152 // p[1][0] == iPattern, p[1][1] == iPatternColor, p[1][2] == iPatternDensity
8153 if( $patternFactory == null ) {
8154 $patternFactory = new RectPatternFactory();
8156 $prect = $patternFactory->Create($p[1][0],$p[1][1],1);
8157 $prect->SetBackground($p[1][3]);
8158 $prect->SetDensity($p[1][2]+1);
8159 $prect->SetPos(new Rectangle($x1,$ym,$boxsize,$boxsize));
8160 $prect->Stroke($aImg);
8161 $prect=null;
8163 else {
8164 if( is_array($color) && count($color)==2 ) {
8165 // The client want a gradient color
8166 $grad->FilledRectangle($x1,$ym,
8167 $x1+$boxsize,$ym+$boxsize,
8168 $color[0],$color[1],-$p[3]);
8170 else {
8171 $aImg->SetColor($p[1]);
8172 $aImg->FilledRectangle($x1,$ym,$x1+$boxsize,$ym+$boxsize);
8174 $aImg->SetColor($this->color);
8175 $aImg->SetLineWeight($fillBoxFrameWeight);
8176 $aImg->Rectangle($x1,$ym,$x1+$boxsize,$ym+$boxsize);
8179 $aImg->SetColor($this->font_color);
8180 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
8181 $aImg->SetTextAlign("left","top");
8182 $aImg->StrokeText(round($x1+$this->mark_abs_hsize+$this->xmargin),$y1,$p[0]);
8184 // Add CSIM for Legend if defined
8185 if( $p[4] != "" ) {
8186 $xe = $x1 + $this->xmargin+$this->mark_abs_hsize+$aImg->GetTextWidth($p[0]);
8187 $ye = $y1 + max($this->mark_abs_vsize,$aImg->GetTextHeight($p[0]));
8188 $coords = "$x1,$y1,$xe,$y1,$xe,$ye,$x1,$ye";
8189 if( ! empty($p[4]) ) {
8190 $this->csimareas .= "<area shape=\"poly\" coords=\"$coords\" href=\"".$p[4]."\"";
8191 if( !empty($p[5]) ) {
8192 $tmp=sprintf($p[5],$p[0]);
8193 $this->csimareas .= " title=\"$tmp\"";
8195 $this->csimareas .= " alt=\"\" />\n";
8198 if( $i >= $this->layout_n ) {
8199 $x1 = $xp+$this->mark_abs_hsize+$this->xlmargin;
8200 $y1 += $rowheight[$row++];
8201 $i = 1;
8203 else {
8204 $x1 += $colwidth[($i-1) % $numcolumns] ;
8205 ++$i;
8209 } // Class
8212 //===================================================
8213 // CLASS DisplayValue
8214 // Description: Used to print data values at data points
8215 //===================================================
8216 class DisplayValue {
8217 var $show=false,$format="%.1f",$negformat="";
8218 var $iFormCallback='';
8219 var $angle=0;
8220 var $ff=FF_FONT1,$fs=FS_NORMAL,$fsize=10;
8221 var $color="navy",$negcolor="";
8222 var $margin=5,$valign="",$halign="center";
8223 var $iHideZero=false;
8225 function Show($aFlag=true) {
8226 $this->show=$aFlag;
8229 function SetColor($aColor,$aNegcolor="") {
8230 $this->color = $aColor;
8231 $this->negcolor = $aNegcolor;
8234 function SetFont($aFontFamily,$aFontStyle=FS_NORMAL,$aFontSize=10) {
8235 $this->ff=$aFontFamily;
8236 $this->fs=$aFontStyle;
8237 $this->fsize=$aFontSize;
8240 function SetMargin($aMargin) {
8241 $this->margin = $aMargin;
8244 function SetAngle($aAngle) {
8245 $this->angle = $aAngle;
8248 function SetAlign($aHAlign,$aVAlign='') {
8249 $this->halign = $aHAlign;
8250 $this->valign = $aVAlign;
8253 function SetFormat($aFormat,$aNegFormat="") {
8254 $this->format= $aFormat;
8255 $this->negformat= $aNegFormat;
8258 function SetFormatCallback($aFunc) {
8259 $this->iFormCallback = $aFunc;
8262 function HideZero($aFlag=true) {
8263 $this->iHideZero=$aFlag;
8266 function Stroke($img,$aVal,$x,$y) {
8268 if( $this->show )
8270 if( $this->negformat=="" ) $this->negformat=$this->format;
8271 if( $this->negcolor=="" ) $this->negcolor=$this->color;
8273 if( $aVal===NULL || (is_string($aVal) && ($aVal=="" || $aVal=="-" || $aVal=="x" ) ) )
8274 return;
8276 if( is_numeric($aVal) && $aVal==0 && $this->iHideZero ) {
8277 return;
8280 // Since the value is used in different cirumstances we need to check what
8281 // kind of formatting we shall use. For example, to display values in a line
8282 // graph we simply display the formatted value, but in the case where the user
8283 // has already specified a text string we don't fo anything.
8284 if( $this->iFormCallback != '' ) {
8285 $f = $this->iFormCallback;
8286 $sval = call_user_func($f,$aVal);
8288 elseif( is_numeric($aVal) ) {
8289 if( $aVal >= 0 )
8290 $sval=sprintf($this->format,$aVal);
8291 else
8292 $sval=sprintf($this->negformat,$aVal);
8294 else
8295 $sval=$aVal;
8297 $y = $y-sign($aVal)*$this->margin;
8299 $txt = new Text($sval,$x,$y);
8300 $txt->SetFont($this->ff,$this->fs,$this->fsize);
8301 if( $this->valign == "" ) {
8302 if( $aVal >= 0 )
8303 $valign = "bottom";
8304 else
8305 $valign = "top";
8307 else
8308 $valign = $this->valign;
8309 $txt->Align($this->halign,$valign);
8311 $txt->SetOrientation($this->angle);
8312 if( $aVal > 0 )
8313 $txt->SetColor($this->color);
8314 else
8315 $txt->SetColor($this->negcolor);
8316 $txt->Stroke($img);
8321 //===================================================
8322 // CLASS Plot
8323 // Description: Abstract base class for all concrete plot classes
8324 //===================================================
8325 class Plot {
8326 var $line_weight=1;
8327 var $coords=array();
8328 var $legend='',$hidelegend=false;
8329 var $csimtargets=array(); // Array of targets for CSIM
8330 var $csimareas=""; // Resultant CSIM area tags
8331 var $csimalts=null; // ALT:s for corresponding target
8332 var $color="black";
8333 var $numpoints=0;
8334 var $weight=1;
8335 var $value;
8336 var $center=false;
8337 var $legendcsimtarget='';
8338 var $legendcsimalt='';
8339 //---------------
8340 // CONSTRUCTOR
8341 function __construct(&$aDatay,$aDatax=false) {
8342 $this->numpoints = count($aDatay);
8343 if( $this->numpoints==0 )
8344 JpGraphError::RaiseL(25121);//("Empty input data array specified for plot. Must have at least one data point.");
8345 $this->coords[0]=$aDatay;
8346 if( is_array($aDatax) )
8347 $this->coords[1]=$aDatax;
8348 $this->value = new DisplayValue();
8351 //---------------
8352 // PUBLIC METHODS
8354 // Stroke the plot
8355 // "virtual" function which must be implemented by
8356 // the subclasses
8357 function Stroke(&$aImg,&$aXScale,&$aYScale) {
8358 JpGraphError::RaiseL(25122);//("JpGraph: Stroke() must be implemented by concrete subclass to class Plot");
8361 function HideLegend($f=true) {
8362 $this->hidelegend = $f;
8365 function DoLegend(&$graph) {
8366 if( !$this->hidelegend )
8367 $this->Legend($graph);
8370 function StrokeDataValue($img,$aVal,$x,$y) {
8371 $this->value->Stroke($img,$aVal,$x,$y);
8374 // Set href targets for CSIM
8375 function SetCSIMTargets($aTargets,$aAlts=null) {
8376 $this->csimtargets=$aTargets;
8377 $this->csimalts=$aAlts;
8380 // Get all created areas
8381 function GetCSIMareas() {
8382 return $this->csimareas;
8385 // "Virtual" function which gets called before any scale
8386 // or axis are stroked used to do any plot specific adjustment
8387 function PreStrokeAdjust(&$aGraph) {
8388 if( substr($aGraph->axtype,0,4) == "text" && (isset($this->coords[1])) )
8389 JpGraphError::RaiseL(25123);//("JpGraph: You can't use a text X-scale with specified X-coords. Use a \"int\" or \"lin\" scale instead.");
8390 return true;
8393 // Get minimum values in plot
8394 function Min() {
8395 if( isset($this->coords[1]) )
8396 $x=$this->coords[1];
8397 else
8398 $x="";
8399 if( $x != "" && count($x) > 0 )
8400 $xm=min($x);
8401 else
8402 $xm=0;
8403 $y=$this->coords[0];
8404 $cnt = count($y);
8405 if( $cnt > 0 ) {
8407 if( ! isset($y[0]) ) {
8408 JpGraphError('The input data array must have consecutive values from position 0 and forward. The given y-array starts with empty values (NULL)');
8411 //$ym = $y[0];
8412 $i=0;
8413 while( $i<$cnt && !is_numeric($ym=$y[$i]) )
8414 $i++;
8415 while( $i < $cnt) {
8416 if( is_numeric($y[$i]) )
8417 $ym=min($ym,$y[$i]);
8418 ++$i;
8421 else
8422 $ym="";
8423 return array($xm,$ym);
8426 // Get maximum value in plot
8427 function Max() {
8428 if( isset($this->coords[1]) )
8429 $x=$this->coords[1];
8430 else
8431 $x="";
8433 if( $x!="" && count($x) > 0 )
8434 $xm=max($x);
8435 else {
8436 $xm = $this->numpoints-1;
8438 $y=$this->coords[0];
8439 if( count($y) > 0 ) {
8441 if( !isset($y[0]) ) {
8442 JpGraphError::Raise('The input data array must have consecutive values from position 0 and forward. The given y-array starts with empty values (NULL)');
8443 //$y[0] = 0;
8444 // Change in 1.5.1 Don't treat this as an error any more. Just silently convert to 0
8445 // Change in 1.17 Treat his as an error again !! This is the right way to do !!
8448 $cnt = count($y);
8449 $i=0;
8450 while( $i<$cnt && !is_numeric($ym=$y[$i]) )
8451 $i++;
8452 while( $i < $cnt ) {
8453 if( is_numeric($y[$i]) )
8454 $ym=max($ym,$y[$i]);
8455 ++$i;
8459 else
8460 $ym="";
8461 return array($xm,$ym);
8464 function SetColor($aColor) {
8465 $this->color=$aColor;
8468 function SetLegend($aLegend,$aCSIM="",$aCSIMAlt="") {
8469 $this->legend = $aLegend;
8470 $this->legendcsimtarget = $aCSIM;
8471 $this->legendcsimalt = $aCSIMAlt;
8474 function SetWeight($aWeight) {
8475 $this->weight=$aWeight;
8478 function SetLineWeight($aWeight=1) {
8479 $this->line_weight=$aWeight;
8482 function SetCenter($aCenter=true) {
8483 $this->center = $aCenter;
8486 // This method gets called by Graph class to plot anything that should go
8487 // into the margin after the margin color has been set.
8488 function StrokeMargin(&$aImg) {
8489 return true;
8492 // Framework function the chance for each plot class to set a legend
8493 function Legend(&$aGraph) {
8494 if( $this->legend != "" )
8495 $aGraph->legend->Add($this->legend,$this->color,"",0,$this->legendcsimtarget,$this->legendcsimalt);
8498 } // Class
8501 //===================================================
8502 // CLASS PlotLine
8503 // Description:
8504 // Data container class to hold properties for a static
8505 // line that is drawn directly in the plot area.
8506 // Usefull to add static borders inside a plot to show
8507 // for example set-values
8508 //===================================================
8509 class PlotLine {
8510 var $weight=1;
8511 var $color="black";
8512 var $direction=-1;
8513 var $scaleposition;
8515 //---------------
8516 // CONSTRUCTOR
8517 function __construct($aDir=HORIZONTAL,$aPos=0,$aColor="black",$aWeight=1) {
8518 $this->direction = $aDir;
8519 $this->color=$aColor;
8520 $this->weight=$aWeight;
8521 $this->scaleposition=$aPos;
8524 //---------------
8525 // PUBLIC METHODS
8526 function SetPosition($aScalePosition) {
8527 $this->scaleposition=$aScalePosition;
8530 function SetDirection($aDir) {
8531 $this->direction = $aDir;
8534 function SetColor($aColor) {
8535 $this->color=$aColor;
8538 function SetWeight($aWeight) {
8539 $this->weight=$aWeight;
8542 function PreStrokeAdjust($aGraph) {
8543 // Nothing to do
8546 function Stroke(&$aImg,&$aXScale,&$aYScale) {
8547 $aImg->SetColor($this->color);
8548 $aImg->SetLineWeight($this->weight);
8549 if( $this->direction == VERTICAL ) {
8550 $ymin_abs=$aYScale->Translate($aYScale->GetMinVal());
8551 $ymax_abs=$aYScale->Translate($aYScale->GetMaxVal());
8552 $xpos_abs=$aXScale->Translate($this->scaleposition);
8553 $aImg->Line($xpos_abs, $ymin_abs, $xpos_abs, $ymax_abs);
8555 elseif( $this->direction == HORIZONTAL ) {
8556 $xmin_abs=$aXScale->Translate($aXScale->GetMinVal());
8557 $xmax_abs=$aXScale->Translate($aXScale->GetMaxVal());
8558 $ypos_abs=$aYScale->Translate($this->scaleposition);
8559 $aImg->Line($xmin_abs, $ypos_abs, $xmax_abs, $ypos_abs);
8561 else
8562 JpGraphError::RaiseL(25125);//(" Illegal direction for static line");
8566 // <EOF>