2 //=======================================================================
4 // Description: PHP4 Graph Plotting library. Base module.
6 // Author: Johan Persson (johanp@aditus.nu)
7 // Ver: $Id: jpgraph.php,v 1.242.2.21 2003/08/22 23:56:11 aditus Exp $
9 // License: This code is released under QPL 1.0
10 // Copyright (C) 2001,2002,2003 Johan Persson
11 //========================================================================
13 //------------------------------------------------------------------------
14 // Directories for cache and font directory.
15 // Leave them undefined to use default values.
17 // Default values used if these defines are left commented out are:
20 // CACHE_DIR = /tmp/jpgraph_cache/
21 // TTF_DIR = /usr/X11R6/lib/X11/fonts/truetype/
24 // CACHE_DIR = $SERVER_TEMP/jpgraph_cache/
25 // TTF_DIR = $SERVER_SYSTEMROOT/fonts/
28 //------------------------------------------------------------------------
30 // The full absolute name of the directory to be used to store the
31 // cached image files. This directory will not be used if the USE_CACHE
32 // define (further down) is false. If you enable the cache please note that
33 // this directory MUST be readable and writable for the process running PHP.
35 // DEFINE("CACHE_DIR","/tmp/jpgraph_cache/");
37 // Directory for jpGraph TTF fonts. Must end with '/'
38 // DEFINE("TTF_DIR","/usr/X11R6/lib/X11/fonts/truetype/");
41 //-------------------------------------------------------------------------
42 // Cache directory specification for use with CSIM graphs that are
44 // The directory must be the filesysystem name as seen by PHP
45 // and the 'http' version must be the same directory but as
46 // seen by the HTTP server relative to the 'htdocs' ddirectory.
47 // If a relative path is specified it is taken to be relative from where
48 // the image script is executed.
49 // Note: The default setting is to create a subdirectory in the
50 // directory from where the image script is executed and store all files
51 // there. As ususal this directory must be writeable by the PHP process.
52 DEFINE("CSIMCACHE_DIR","csimcache/");
53 DEFINE("CSIMCACHE_HTTP_DIR","csimcache/");
55 //------------------------------------------------------------------------
56 // Various JpGraph Settings. Adjust accordingly to your
57 // preferences. Note that cache functionality is turned off by
58 // default (Enable by setting USE_CACHE to true)
59 //------------------------------------------------------------------------
61 // Deafult graphic format set to "auto" which will automatically
62 // choose the best available format in the order png,gif,jpg
63 // (The supported format depends on what your PHP installation supports)
64 DEFINE("DEFAULT_GFORMAT","auto");
66 // Should the image be a truecolor image?
67 // Note 1: Has only effect with GD 2.0.1 and above.
68 // Note 2: GD 2.0.1 + PHP 4.0.6 on Win32 crashes when trying to use
69 // trucolor. Truecolor support is to be considered alpha since GD 2.x
70 // is still not considered stable (especially on Win32).
71 // Note 3: MUST be enabled to get background images working with GD2
72 // Note 4: If enabled then truetype fonts will look very ugly with GD 2.0.1
73 // => You can't have both background images and truetype fonts in the same
74 // image until these bugs has been fixed in GD 2.01. There is a patch
75 // available for GD 2.0.1 though. See the README file.
76 DEFINE('USE_TRUECOLOR',true);
78 // Specify what version of the GD library is installed.
79 // If this is set to 'auto' the version will be automatically
81 // However since determining the library takes ~1ms you can also
82 // manually specify the version if you know what version you have.
83 // This means that you should
84 // set this define to true if you have GD 2.x installed to save 1ms.
85 DEFINE("USE_LIBRARY_GD2",'auto');
87 // Should the cache be used at all? By setting this to false no
88 // files will be generated in the cache directory.
89 // The difference from READ_CACHE being that setting READ_CACHE to
90 // false will still create the image in the cache directory
91 // just not use it. By setting USE_CACHE=false no files will even
92 // be generated in the cache directory.
93 DEFINE("USE_CACHE",false);
95 // Should we try to find an image in the cache before generating it?
96 // Set this define to false to bypass the reading of the cache and always
97 // regenerate the image. Note that even if reading the cache is
98 // disabled the cached will still be updated with the newly generated
99 // image. Set also "USE_CACHE" below.
100 DEFINE("READ_CACHE",true);
102 // Determine if the error handler should be image based or purely
103 // text based. Image based makes it easier since the script will
104 // always return an image even in case of errors.
105 DEFINE("USE_IMAGE_ERROR_HANDLER",true);
107 // Determine if the library should also setup the default PHP
108 // error handler to generate a graphic error mesage. This is useful
109 // during development to be able to see the error message as an image
110 // instead as a "red-cross" in a page where an image is expected.
111 DEFINE("INSTALL_PHP_ERR_HANDLER",false);
113 // If the color palette is full should JpGraph try to allocate
114 // the closest match? If you plan on using background images or
115 // gradient fills it might be a good idea to enable this.
116 // If not you will otherwise get an error saying that the color palette is
117 // exhausted. The drawback of using approximations is that the colors
118 // might not be exactly what you specified.
119 // Note1: This does only apply to paletted images, not truecolor
120 // images since they don't have the limitations of maximum number
122 DEFINE("USE_APPROX_COLORS",true);
124 // Special unicode cyrillic language support
125 DEFINE("LANGUAGE_CYRILLIC",false);
127 // If you are setting this config to true the conversion
128 // will assume that the input text is windows 1251, if
129 // false it will assume koi8-r
130 DEFINE("CYRILLIC_FROM_WINDOWS",false);
132 // Should usage of deprecated functions and parameters give a fatal error?
133 // (Useful to check if code is future proof.)
134 DEFINE("ERR_DEPRECATED",true);
136 // Should the time taken to generate each picture be branded to the lower
137 // left in corner in each generated image? Useful for performace measurements
139 DEFINE("BRAND_TIMING",false);
141 // What format should be used for the timing string?
142 DEFINE("BRAND_TIME_FORMAT","(%01.3fs)");
144 //------------------------------------------------------------------------
145 // The following constants should rarely have to be changed !
146 //------------------------------------------------------------------------
148 // What group should the cached file belong to
149 // (Set to "" will give the default group for the "PHP-user")
150 // Please note that the Apache user must be a member of the
151 // specified group since otherwise it is impossible for Apache
152 // to set the specified group.
153 DEFINE("CACHE_FILE_GROUP","wwwadmin");
155 // What permissions should the cached file have
156 // (Set to "" will give the default persmissions for the "PHP-user")
157 DEFINE("CACHE_FILE_MOD",0664);
159 // Decide if we should use the bresenham circle algorithm or the
160 // built in Arc(). Bresenham gives better visual apperance of circles
161 // but is more CPU intensive and slower then the built in Arc() function
162 // in GD. Turned off by default for speed
163 DEFINE("USE_BRESENHAM",false);
165 // Special file name to indicate that we only want to calc
166 // the image map in the call to Graph::Stroke() used
167 // internally from the GetHTMLCSIM() method.
168 DEFINE("_CSIM_SPECIALFILE","_csim_special_");
170 // HTTP GET argument that is used with image map
171 // to indicate to the script to just generate the image
172 // and not the full CSIM HTML page.
173 DEFINE("_CSIM_DISPLAY","_jpg_csimd");
175 // Special filename for Graph::Stroke(). If this filename is given
176 // then the image will NOT be streamed to browser of file. Instead the
177 // Stroke call will return the handler for the created GD image.
178 DEFINE("_IMG_HANDLER","__handle");
180 // DON'T SET THIS FLAG YORSELF THIS IS ONLY FOR INTERNAL TESTING
181 // PURPOSES. ENABLING THIS FLAG WILL MAKE SOME OF YOUR SCRIPT
183 // Enable some extra debug information for CSIM etc to be shown.
184 DEFINE("JPG_DEBUG",false);
187 DEFINE('JPG_VERSION','1.12');
189 //------------------------------------------------------------------------
190 // Automatic settings of path for cache and font directory
191 // if they have not been previously specified
192 //------------------------------------------------------------------------
193 if (!defined('CACHE_DIR')) {
194 if ( strstr( PHP_OS, 'WIN') ) {
195 if( empty($_SERVER['TEMP']) ) {
196 die('JpGraph Error: No path specified for CACHE_DIR. Please specify a path for that DEFINE in jpgraph.php');
199 DEFINE('CACHE_DIR', $_SERVER['TEMP'] . '/');
202 DEFINE('CACHE_DIR','/tmp/jpgraph_cache/');
206 if (!defined('TTF_DIR')) {
207 if (strstr( PHP_OS, 'WIN') ) {
208 if( empty($_SERVER['SystemRoot']) ) {
209 die('JpGraph Error: No path specified for TTF_DIR. Please specify a path for that DEFINE in jpgraph.php');
212 DEFINE('TTF_DIR', $_SERVER['SystemRoot'] . '/fonts/');
215 DEFINE('TTF_DIR','/usr/X11R6/lib/X11/fonts/truetype/');
219 //------------------------------------------------------------------
220 // Constants which are used as parameters for the method calls
221 //------------------------------------------------------------------
224 DEFINE("FF_COURIER",10);
225 DEFINE("FF_VERDANA",11);
226 DEFINE("FF_TIMES",12);
227 DEFINE("FF_COMIC",14);
228 DEFINE("FF_ARIAL",15);
229 DEFINE("FF_GEORGIA",16);
230 DEFINE("FF_TREBUCHE",17);
233 DEFINE("FF_SIMSUN",18);
236 // Available from http://www.gnome.org/fonts/
237 DEFINE("FF_VERA",19);
238 DEFINE("FF_VERAMONO",20);
239 DEFINE("FF_VERASERIF",21);
242 // Older deprecated fonts
243 DEFINE("FF_BOOK",91); // Deprecated fonts from 1.9
244 DEFINE("FF_HANDWRT",92); // Deprecated fonts from 1.9
247 DEFINE("FS_NORMAL",9001);
248 DEFINE("FS_BOLD",9002);
249 DEFINE("FS_ITALIC",9003);
250 DEFINE("FS_BOLDIT",9004);
251 DEFINE("FS_BOLDITALIC",9004);
253 //Definitions for internal font, new style
254 DEFINE("FF_FONT0",1);
255 DEFINE("FF_FONT1",2);
256 DEFINE("FF_FONT2",4);
258 //Definitions for internal font, old style
259 // (Only defined here to be able to generate an error mesage
261 DEFINE("FONT0",99); // Deprecated from 1.2
262 DEFINE("FONT1",98); // Deprecated from 1.2
263 DEFINE("FONT1_BOLD",97); // Deprecated from 1.2
264 DEFINE("FONT2",96); // Deprecated from 1.2
265 DEFINE("FONT2_BOLD",95); // Deprecated from 1.2
268 DEFINE("TICKD_DENSE",1);
269 DEFINE("TICKD_NORMAL",2);
270 DEFINE("TICKD_SPARSE",3);
271 DEFINE("TICKD_VERYSPARSE",4);
273 // Side for ticks and labels.
274 DEFINE("SIDE_LEFT",-1);
275 DEFINE("SIDE_RIGHT",1);
276 DEFINE("SIDE_DOWN",-1);
277 DEFINE("SIDE_BOTTOM",-1);
279 DEFINE("SIDE_TOP",1);
281 // Legend type stacked vertical or horizontal
282 DEFINE("LEGEND_VERT",0);
283 DEFINE("LEGEND_HOR",1);
285 // Mark types for plot marks
286 DEFINE("MARK_SQUARE",1);
287 DEFINE("MARK_UTRIANGLE",2);
288 DEFINE("MARK_DTRIANGLE",3);
289 DEFINE("MARK_DIAMOND",4);
290 DEFINE("MARK_CIRCLE",5);
291 DEFINE("MARK_FILLEDCIRCLE",6);
292 DEFINE("MARK_CROSS",7);
293 DEFINE("MARK_STAR",8);
295 DEFINE("MARK_LEFTTRIANGLE",10);
296 DEFINE("MARK_RIGHTTRIANGLE",11);
297 DEFINE("MARK_FLASH",12);
298 DEFINE("MARK_IMG",13);
299 DEFINE("MARK_FLAG1",14);
300 DEFINE("MARK_FLAG2",15);
301 DEFINE("MARK_FLAG3",16);
302 DEFINE("MARK_FLAG4",17);
305 DEFINE("MARK_IMG_PUSHPIN",50);
306 DEFINE("MARK_IMG_SPUSHPIN",50);
307 DEFINE("MARK_IMG_LPUSHPIN",51);
308 DEFINE("MARK_IMG_DIAMOND",52);
309 DEFINE("MARK_IMG_SQUARE",53);
310 DEFINE("MARK_IMG_STAR",54);
311 DEFINE("MARK_IMG_BALL",55);
312 DEFINE("MARK_IMG_SBALL",55);
313 DEFINE("MARK_IMG_MBALL",56);
314 DEFINE("MARK_IMG_LBALL",57);
315 DEFINE("MARK_IMG_BEVEL",58);
317 // Styles for gradient color fill
318 DEFINE("GRAD_VER",1);
319 DEFINE("GRAD_VERT",1);
320 DEFINE("GRAD_HOR",2);
321 DEFINE("GRAD_MIDHOR",3);
322 DEFINE("GRAD_MIDVER",4);
323 DEFINE("GRAD_CENTER",5);
324 DEFINE("GRAD_WIDE_MIDVER",6);
325 DEFINE("GRAD_WIDE_MIDHOR",7);
326 DEFINE("GRAD_LEFT_REFLECTION",8);
327 DEFINE("GRAD_RIGHT_REFLECTION",9);
330 DEFINE("INLINE_YES",1);
331 DEFINE("INLINE_NO",0);
333 // Format for background images
334 DEFINE("BGIMG_FILLPLOT",1);
335 DEFINE("BGIMG_FILLFRAME",2);
336 DEFINE("BGIMG_COPY",3);
337 DEFINE("BGIMG_CENTER",4);
340 DEFINE("DEPTH_BACK",0);
341 DEFINE("DEPTH_FRONT",1);
344 DEFINE("VERTICAL",1);
345 DEFINE("HORIZONTAL",0);
347 // Constants for types of static bands in plot area
348 DEFINE("BAND_RDIAG",1); // Right diagonal lines
349 DEFINE("BAND_LDIAG",2); // Left diagonal lines
350 DEFINE("BAND_SOLID",3); // Solid one color
351 DEFINE("BAND_VLINE",4); // Vertical lines
352 DEFINE("BAND_HLINE",5); // Horizontal lines
353 DEFINE("BAND_3DPLANE",6); // "3D" Plane
354 DEFINE("BAND_HVCROSS",7); // Vertical/Hor crosses
355 DEFINE("BAND_DIAGCROSS",8); // Diagonal crosses
357 // Axis styles for scientific style axis
358 DEFINE('AXSTYLE_SIMPLE',1);
359 DEFINE('AXSTYLE_BOXIN',2);
360 DEFINE('AXSTYLE_BOXOUT',3);
361 DEFINE('AXSTYLE_YBOXIN',4);
362 DEFINE('AXSTYLE_YBOXOUT',5);
364 // Style for title backgrounds
365 DEFINE('TITLEBKG_STYLE1',1);
366 DEFINE('TITLEBKG_STYLE2',2);
367 DEFINE('TITLEBKG_STYLE3',3);
368 DEFINE('TITLEBKG_FRAME_NONE',0);
369 DEFINE('TITLEBKG_FRAME_FULL',1);
370 DEFINE('TITLEBKG_FRAME_BOTTOM',2);
371 DEFINE('TITLEBKG_FRAME_BEVEL',3);
372 DEFINE('TITLEBKG_FILLSTYLE_HSTRIPED',1);
373 DEFINE('TITLEBKG_FILLSTYLE_VSTRIPED',2);
374 DEFINE('TITLEBKG_FILLSTYLE_SOLID',3);
376 // Width of tab titles
377 DEFINE('TABTITLE_WIDTHFIT',0);
378 DEFINE('TABTITLE_WIDTHFULL',-1);
381 // Get hold of gradient class (In Version 2.x)
382 // A client of the library has to manually include this
384 include "jpgraph_gradient.php";
387 // First of all set up a default error handler
390 //=============================================================
391 // The default trivial text error handler.
392 //=============================================================
393 class JpGraphErrObject {
394 function JpGraphErrObject() {
395 // Empty. Reserved for future use
398 // If aHalt is true then execution can't continue. Typical used for
400 function Raise($aMsg,$aHalt=true) {
401 $aMsg = "<b>JpGraph Error:</b> ".$aMsg;
409 //==============================================================
410 // An image based error handler
411 //==============================================================
412 class JpGraphErrObjectImg {
414 function Raise($aMsg,$aHalt=true) {
416 'iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAMAAAC7IEhfAAAAaV'.
417 'BMVEX//////2Xy8mLl5V/Z2VvMzFi/v1WyslKlpU+ZmUyMjEh/'.
418 'f0VyckJlZT9YWDxMTDjAwMDy8sLl5bnY2K/MzKW/v5yyspKlpY'.
419 'iYmH+MjHY/PzV/f2xycmJlZVlZWU9MTEXY2Ms/PzwyMjLFTjea'.
420 'AAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACx'.
421 'IAAAsSAdLdfvwAAAAHdElNRQfTBgISOCqusfs5AAABLUlEQVR4'.
422 '2tWV3XKCMBBGWfkranCIVClKLd/7P2Q3QsgCxjDTq+6FE2cPH+'.
423 'xJ0Ogn2lQbsT+Wrs+buAZAV4W5T6Bs0YXBBwpKgEuIu+JERAX6'.
424 'wM2rHjmDdEITmsQEEmWADgZm6rAjhXsoMGY9B/NZBwJzBvn+e3'.
425 'wHntCAJdGu9SviwIwoZVDxPB9+Rc0TSEbQr0j3SA1gwdSn6Db0'.
426 '6Tm1KfV6yzWGQO7zdpvyKLKBDmRFjzeB3LYgK7r6A/noDAfjtS'.
427 'IXaIzbJSv6WgUebTMV4EoRB8a2mQiQjgtF91HdKDKZ1gtFtQjk'.
428 'YcWaR5OKOhkYt+ZsTFdJRfPAApOpQYJTNHvCRSJR6SJngQadfc'.
429 'vd69OLMddVOPCGVnmrFD8bVYd3JXfxXPtLR/+mtv59/ALWiiMx'.
430 'qL72fwAAAABJRU5ErkJggg==' ;
432 if( headers_sent() ) {
433 // Special case for headers already sent error. Dont
434 // return an image since it can't be displayed
435 die("<b>JpGraph Error:</b> ".$aMsg);
438 // Create the error icon GD
439 $erricon = Image::CreateFromString(base64_decode($img_iconerror));
441 // Create an image that contains the error text.
443 $img = new Image($w,$h);
447 $img->SetColor("gray");
448 $img->FilledRectangle(5,5,$w-1,$h-1,10);
449 $img->SetColor("gray:0.7");
450 $img->FilledRectangle(5,5,$w-3,$h-3,10);
453 $img->SetColor("lightblue");
454 $img->FilledRectangle(1,1,$w-5,$h-5);
455 $img->CopyCanvasH($img->img,$erricon,5,30,0,0,40,40);
458 $img->SetColor("black");
459 $img->Rectangle(1,1,$w-5,$h-5);
460 $img->Rectangle(0,0,$w-4,$h-4);
463 $img->SetColor("darkred");
464 for($y=3; $y < 18; $y += 2 )
465 $img->Line(1,$y,$w-25,$y);
466 $img->Line(1,17,$w-5,17);
467 $img->Line($w-25,1,$w-25,16);
470 $img->Line($w-18,6,$w-12,12);
471 $img->Line($w-18,7,$w-12,13);
472 $img->Line($w-18,12,$w-12,6);
473 $img->Line($w-18,13,$w-12,7);
476 $img->SetColor("white");
478 $img->Line($w-24,3,$w-24,15);
479 $img->Line($w-24,2,$w-7,2);
482 $img->Line(2,2,2,$h-5);
483 $img->Line(2,2,$w-27,2);
485 // "Gray button shadow"
486 $img->SetColor("darkgray");
487 $img->Line($w-6,3,$w-6,16);
488 $img->Line($w-24,16,$w-7,16);
490 // Gray window shadow
491 $img->Line(2,$h-6,$w-5,$h-6);
492 $img->Line(3,$h-7,$w-5,$h-7);
497 $img->SetColor("lightgray:1.3");
498 $img->FilledRectangle($m-$l,2,$m+$l,16);
501 $img->SetColor("darkred");
502 $img->SetFont(FF_FONT2,FS_BOLD);
503 $img->StrokeText($m-50,15,"JpGraph Error");
504 $img->SetColor("black");
505 $img->SetFont(FF_FONT1,FS_NORMAL);
506 $txt = new Text(wordwrap($aMsg,52),52,25);
507 $txt->Align("left","top");
516 // A wrapper class that is used to access the specified error object
517 // (to hide the global error parameter and avoid having a GLOBAL directive
521 function Install($aErrObject) {
523 $__jpg_err = $aErrObject;
525 function Raise($aMsg,$aHalt=true){
527 $tmp = new $__jpg_err;
528 $tmp->Raise($aMsg,$aHalt);
533 // ... and install the default error handler
535 if( USE_IMAGE_ERROR_HANDLER ) {
536 JpGraphError::Install("JpGraphErrObjectImg");
539 JpGraphError::Install("JpGraphErrObject");
543 // Setup PHP error handler
546 function _phpErrorHandler($errno,$errmsg,$filename, $linenum, $vars) {
547 JpGraphError::Raise('In '.basename($filename).'#'.$linenum."\n".$errmsg);
550 if( INSTALL_PHP_ERR_HANDLER ) {
551 set_error_handler("_phpErrorHandler");
556 //Check if there were any warnings, perhaps some wrong includes by the
559 if( isset($GLOBALS['php_errormsg']) ) {
560 JpGraphError::Raise("<b>General PHP error:</b><br>".$GLOBALS['php_errormsg']);
565 // Routine to determine if GD1 or GD2 is installed
567 function CheckGDVersion() {
569 phpinfo(8); // Just get the modules loaded
570 $a = ob_get_contents();
572 if( preg_match('/.*GD Version.*(1\.).*/',$a,$m) ) {
575 elseif( preg_match('/.*GD Version.*(2\.).*/',$a,$m) ) {
585 // Check what version of the GD library is installed.
587 if( USE_LIBRARY_GD2 === 'auto' ) {
588 $gdversion = CheckGDVersion();
589 if( $gdversion == 2 ) {
590 $GLOBALS['gd2'] = true;
591 $GLOBALS['copyfunc'] = 'imagecopyresampled';
593 elseif( $gdversion == 1 ) {
594 $GLOBALS['gd2'] = false;
595 $GLOBALS['copyfunc'] = 'imagecopyresized';
598 JpGraphError::Raise(" Your PHP installation does not seem to
599 have the required GD library.
600 Please see the PHP documentation on how to install and enable the GD library.");
604 $GLOBALS['gd2'] = USE_LIBRARY_GD2;
605 $GLOBALS['copyfunc'] = USE_LIBRARY_GD2 ? 'imagecopyresampled' : 'imagecopyresized';
608 // Usefull mathematical function
609 function sign($a) {return $a >= 0 ? 1 : -1;}
611 // Utility function to generate an image name based on the filename we
612 // are running from and assuming we use auto detection of graphic format
613 // (top level), i.e it is safe to call this function
614 // from a script that uses JpGraph
615 function GenImgName() {
616 global $HTTP_SERVER_VARS;
617 $supported = imagetypes();
618 if( $supported & IMG_PNG )
620 elseif( $supported & IMG_GIF )
622 elseif( $supported & IMG_JPG )
624 if( !isset($HTTP_SERVER_VARS['PHP_SELF']) )
625 JpGraphError::Raise(" Can't access PHP_SELF, PHP global variable. You can't run PHP from command line
626 if you want to use the 'auto' naming of cache or image files.");
627 $fname=basename($HTTP_SERVER_VARS['PHP_SELF']);
628 // Replace the ".php" extension with the image format extension
629 return substr($fname,0,strlen($fname)-4).".".$img_format;
635 function Convert($aTxt,$aFF) {
636 if( LANGUAGE_CYRILLIC ) {
637 if( CYRILLIC_FROM_WINDOWS ) {
638 $aTxt = convert_cyr_string($aTxt, "w", "k");
640 $isostring = convert_cyr_string($aTxt, "k", "i");
641 $unistring = LanguageConv::iso2uni($isostring);
644 elseif( $aFF === FF_SIMSUN ) {
645 // Do Chinese conversion
646 if( $this->g2312 == null ) {
647 include_once 'jpgraph_gb2312.php' ;
648 $this->g2312 = new GB2312toUTF8();
650 return $this->g2312->gb2utf8($aTxt);
656 // Translate iso encoding to unicode
657 function iso2uni ($isoline){
658 for ($i=0; $i < strlen($isoline); $i++){
659 $thischar=substr($isoline,$i,1);
660 $charcode=ord($thischar);
661 $uniline.=($charcode>175) ? "&#" . (1040+($charcode-176)). ";" : $thischar;
667 //===================================================
669 // Description: General timing utility class to handle
670 // timne measurement of generating graphs. Multiple
671 // timers can be started by pushing new on a stack.
672 //===================================================
678 function JpgTimer() {
685 // Push a new timer start on stack
687 list($ms,$s)=explode(" ",microtime());
688 $this->start[$this->idx++]=floor($ms*1000) + 1000*$s;
691 // Pop the latest timer start and return the diff with the
694 assert($this->idx>0);
695 list($ms,$s)=explode(" ",microtime());
696 $etime=floor($ms*1000) + (1000*$s);
698 return $etime-$this->start[$this->idx];
702 $gJpgBrandTiming = BRAND_TIMING;
703 //===================================================
705 // Description: Hold localized text used in dates
706 // ToDOo: Rewrite this to use the real local locale
708 //===================================================
711 var $iLocale = 'C'; // environmental locale be used by default
714 var $iShortDay = null;
715 var $iShortMonth = null;
716 var $iMonthName = null;
720 function DateLocale() {
721 settype($this->iDayAbb, 'array');
722 settype($this->iShortDay, 'array');
723 settype($this->iShortMonth, 'array');
724 settype($this->iMonthName, 'array');
732 function Set($aLocale) {
733 if ( in_array($aLocale, array_keys($this->iDayAbb)) ){
734 $this->iLocale = $aLocale;
735 return TRUE; // already cached nothing else to do!
738 $pLocale = setlocale(LC_TIME, 0); // get current locale for LC_TIME
739 $res = setlocale(LC_TIME, $aLocale);
741 JpGraphError::Raise("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.");
745 $this->iLocale = $aLocale;
747 for ( $i = 0, $ofs = 0 - strftime('%w'); $i < 7; $i++, $ofs++ ){
748 $day = strftime('%a', strtotime("$ofs day"));
749 $day{0} = strtoupper($day{0});
750 $this->iDayAbb[$aLocale][]= $day{0};
751 $this->iShortDay[$aLocale][]= $day;
754 for($i=1; $i<=12; ++$i) {
755 list($short ,$full) = explode('|', strftime("%b|%B",strtotime("2001-$i-01")));
756 $this->iShortMonth[$aLocale][] = ucfirst($short);
757 $this->iMonthName [$aLocale][] = ucfirst($full);
761 setlocale(LC_TIME, $pLocale);
767 function GetDayAbb() {
768 return $this->iDayAbb[$this->iLocale];
771 function GetShortDay() {
772 return $this->iShortDay[$this->iLocale];
775 function GetShortMonth() {
776 return $this->iShortMonth[$this->iLocale];
779 function GetShortMonthName($aNbr) {
780 return $this->iShortMonth[$this->iLocale][$aNbr];
783 function GetLongMonthName($aNbr) {
784 return $this->iMonthName[$this->iLocale][$aNbr];
787 function GetMonth() {
788 return $this->iMonthName[$this->iLocale];
792 $gDateLocale = new DateLocale();
793 $gJpgDateLocale = new DateLocale();
796 //===================================================
797 // CLASS FuncGenerator
798 // Description: Utility class to help generate data for function plots.
799 // The class supports both parametric and regular functions.
800 //===================================================
801 class FuncGenerator {
802 var $iFunc='',$iXFunc='',$iMin,$iMax,$iStepSize;
804 function FuncGenerator($aFunc,$aXFunc='') {
805 $this->iFunc = $aFunc;
806 $this->iXFunc = $aXFunc;
809 function E($aXMin,$aXMax,$aSteps=50) {
810 $this->iMin = $aXMin;
811 $this->iMax = $aXMax;
812 $this->iStepSize = ($aXMax-$aXMin)/$aSteps;
814 if( $this->iXFunc != '' )
815 $t = 'for($i='.$aXMin.'; $i<='.$aXMax.'; $i += '.$this->iStepSize.') {$ya[]='.$this->iFunc.';$xa[]='.$this->iXFunc.';}';
816 elseif( $this->iFunc != '' )
817 $t = 'for($x='.$aXMin.'; $x<='.$aXMax.'; $x += '.$this->iStepSize.') {$ya[]='.$this->iFunc.';$xa[]=$x;} $x='.$aXMax.';$ya[]='.$this->iFunc.';$xa[]=$x;';
819 JpGraphError::Raise('FuncGenerator : No function specified. ');
823 // If there is an error in the function specifcation this is the only
824 // way we can discover that.
825 if( empty($xa) || empty($ya) )
826 JpGraphError::Raise('FuncGenerator : Syntax error in function specification ');
828 return array($xa,$ya);
833 //=======================================================
835 // Description: Encapsulates the footer line in the Graph
837 //=======================================================
839 var $left,$center,$right;
840 var $iLeftMargin = 3;
841 var $iRightMargin = 3;
842 var $iBottomMargin = 3;
845 $this->left = new Text();
846 $this->left->ParagraphAlign('left');
847 $this->center = new Text();
848 $this->center->ParagraphAlign('center');
849 $this->right = new Text();
850 $this->right->ParagraphAlign('right');
853 function Stroke($aImg) {
854 $y = $aImg->height - $this->iBottomMargin;
855 $x = $this->iLeftMargin;
856 $this->left->Align('left','bottom');
857 $this->left->Stroke($aImg,$x,$y);
859 $x = ($aImg->width - $this->iLeftMargin - $this->iRightMargin)/2;
860 $this->center->Align('center','bottom');
861 $this->center->Stroke($aImg,$x,$y);
863 $x = $aImg->width - $this->iRightMargin;
864 $this->right->Align('right','bottom');
865 $this->right->Stroke($aImg,$x,$y);
869 DEFINE('BGRAD_FRAME',1);
870 DEFINE('BGRAD_MARGIN',2);
871 DEFINE('BGRAD_PLOT',3);
875 //===================================================
877 // Description: Main class to handle graphs
878 //===================================================
880 var $cache=null; // Cache object (singleton)
881 var $img=null; // Img object (singleton)
882 var $plots=array(); // Array of all plot object in the graph (for Y 1 axis)
883 var $y2plots=array();// Array of all plot object in the graph (for Y 2 axis)
884 var $xscale=null; // X Scale object (could be instance of LinearScale or LogScale
885 var $yscale=null,$y2scale=null;
886 var $cache_name; // File name to be used for the current graph in the cache directory
887 var $xgrid=null; // X Grid object (linear or logarithmic)
888 var $ygrid=null,$y2grid=null; //dito for Y
889 var $doframe=true,$frame_color=array(0,0,0), $frame_weight=1; // Frame around graph
890 var $boxed=false, $box_color=array(0,0,0), $box_weight=1; // Box around plot area
891 var $doshadow=false,$shadow_width=4,$shadow_color=array(102,102,102); // Shadow for graph
892 var $xaxis=null; // X-axis (instane of Axis class)
893 var $yaxis=null, $y2axis=null; // Y axis (instance of Axis class)
894 var $margin_color=array(200,200,200); // Margin color of graph
895 var $plotarea_color=array(255,255,255); // Plot area color
896 var $title,$subtitle,$subsubtitle; // Title and subtitle(s) text object
897 var $axtype="linlin"; // Type of axis
898 var $xtick_factor; // Factot to determine the maximum number of ticks depending on the plot with
899 var $texts=null, $y2texts=null; // Text object to ge shown in the graph
900 var $lines=null, $y2lines=null;
901 var $bands=null, $y2bands=null;
902 var $text_scale_off=0; // Text scale offset in world coordinates
903 var $background_image="",$background_image_type=-1,$background_image_format="png";
904 var $background_image_bright=0,$background_image_contr=0,$background_image_sat=0;
905 var $image_bright=0, $image_contr=0, $image_sat=0;
907 var $showcsim=0,$csimcolor="red"; //debug stuff, draw the csim boundaris on the image if <>0
908 var $grid_depth=DEPTH_BACK; // Draw grid under all plots as default
909 var $iAxisStyle = AXSTYLE_SIMPLE;
910 var $iCSIMdisplay=false,$iHasStroked = false;
912 var $csimcachename = '', $csimcachetimeout = 0;
913 var $iDoClipping = false;
914 var $y2orderback=true;
916 var $bkg_gradtype=-1,$bkg_gradstyle=BGRAD_MARGIN;
917 var $bkg_gradfrom='navy', $bkg_gradto='silver';
918 var $titlebackground = false;
919 var $titlebackground_color = 'lightblue',
920 $titlebackground_style = 1,
921 $titlebackground_framecolor = 'blue',
922 $titlebackground_framestyle = 2,
923 $titlebackground_frameweight = 1,
924 $titlebackground_bevelheight = 3 ;
925 var $titlebkg_fillstyle=TITLEBKG_FILLSTYLE_SOLID;
926 var $titlebkg_scolor1='black',$titlebkg_scolor2='white';
927 var $framebevel = false, $framebeveldepth = 2 ;
928 var $framebevelborder = false, $framebevelbordercolor='black';
929 var $framebevelcolor1='white@0.4', $framebevelcolor2='black@0.4';
934 // aWIdth Width in pixels of image
935 // aHeight Height in pixels of image
936 // aCachedName Name for image file in cache directory
937 // aTimeOut Timeout in minutes for image in cache
938 // aInline If true the image is streamed back in the call to Stroke()
939 // If false the image is just created in the cache
940 function Graph($aWidth=300,$aHeight=200,$aCachedName="",$aTimeOut=0,$aInline=true) {
941 GLOBAL $gJpgBrandTiming;
942 // If timing is used create a new timing object
943 if( $gJpgBrandTiming ) {
945 $tim = new JpgTimer();
949 // Automatically generate the image file name based on the name of the script that
950 // generates the graph
951 if( $aCachedName=="auto" )
952 $aCachedName=GenImgName();
954 // Should the image be streamed back to the browser or only to the cache?
955 $this->inline=$aInline;
957 $this->img = new RotImage($aWidth,$aHeight);
959 $this->cache = new ImgStreamCache($this->img);
960 $this->cache->SetTimeOut($aTimeOut);
962 $this->title = new Text();
963 $this->title->ParagraphAlign('center');
964 $this->title->SetFont(FF_FONT2,FS_BOLD);
965 $this->title->SetMargin(3);
967 $this->subtitle = new Text();
968 $this->subtitle->ParagraphAlign('center');
970 $this->subsubtitle = new Text();
971 $this->subsubtitle->ParagraphAlign('center');
973 $this->legend = new Legend();
974 $this->footer = new Footer();
976 // If the cached version exist just read it directly from the
977 // cache, stream it back to browser and exit
978 if( $aCachedName!="" && READ_CACHE && $aInline )
979 if( $this->cache->GetAndStream($aCachedName) ) {
983 $this->cache_name = $aCachedName;
984 $this->SetTickDensity(); // Normal density
986 $this->tabtitle = new GraphTabTitle();
991 // Should the grid be in front or back of the plot?
992 function SetGridDepth($aDepth) {
993 $this->grid_depth=$aDepth;
996 // Specify graph angle 0-360 degrees.
997 function SetAngle($aAngle) {
998 $this->img->SetAngle($aAngle);
1001 function SetAlphaBlending($aFlg=true) {
1002 $this->img->SetAlphaBlending($aFlg);
1005 // Shortcut to image margin
1006 function SetMargin($lm,$rm,$tm,$bm) {
1007 $this->img->SetMargin($lm,$rm,$tm,$bm);
1010 function SetY2OrderBack($aBack=true) {
1011 $this->y2orderback = $aBack;
1014 // Rotate the graph 90 degrees and set the margin
1015 // when we have done a 90 degree rotation
1016 function Set90AndMargin($lm=0,$rm=0,$tm=0,$bm=0) {
1017 $lm = $lm ==0 ? floor(0.2 * $this->img->width) : $lm ;
1018 $rm = $rm ==0 ? floor(0.1 * $this->img->width) : $rm ;
1019 $tm = $tm ==0 ? floor(0.2 * $this->img->height) : $tm ;
1020 $bm = $bm ==0 ? floor(0.1 * $this->img->height) : $bm ;
1022 $adj = ($this->img->height - $this->img->width)/2;
1023 $this->img->SetMargin($tm-$adj,$bm-$adj,$rm+$adj,$lm+$adj);
1024 $this->img->SetCenter(floor($this->img->width/2),floor($this->img->height/2));
1025 $this->SetAngle(90);
1026 $this->xaxis->SetLabelAlign('right','center');
1027 $this->yaxis->SetLabelAlign('center','bottom');
1030 function SetClipping($aFlg=true) {
1031 $this->iDoClipping = $aFlg ;
1034 // Add a plot object to the graph
1035 function Add(&$aPlot) {
1036 if( $aPlot == null )
1037 JpGraphError::Raise("<b></b> Graph::Add() You tried to add a null plot to the graph.");
1038 if( is_array($aPlot) && count($aPlot) > 0 )
1039 $cl = get_class($aPlot[0]);
1041 $cl = get_class($aPlot);
1044 $this->AddText($aPlot);
1045 elseif( $cl == 'plotline' )
1046 $this->AddLine($aPlot);
1047 elseif( $cl == 'plotband' )
1048 $this->AddBand($aPlot);
1050 $this->plots[] = &$aPlot;
1053 // Add plot to second Y-scale
1054 function AddY2(&$aPlot) {
1055 if( $aPlot == null )
1056 JpGraphError::Raise("<b></b> Graph::AddY2() You tried to add a null plot to the graph.");
1058 if( is_array($aPlot) && count($aPlot) > 0 )
1059 $cl = get_class($aPlot[0]);
1061 $cl = get_class($aPlot);
1064 $this->AddText($aPlot,true);
1065 elseif( $cl == 'plotline' )
1066 $this->AddLine($aPlot,true);
1067 elseif( $cl == 'plotband' )
1068 $this->AddBand($aPlot,true);
1070 $this->y2plots[] = &$aPlot;
1073 // Add text object to the graph
1074 function AddText(&$aTxt,$aToY2=false) {
1076 JpGraphError::Raise("<b></b> Graph::AddText() You tried to add a null text to the graph.");
1078 if( is_array($aTxt) ) {
1079 for($i=0; $i < count($aTxt); ++$i )
1080 $this->y2texts[]=&$aTxt[$i];
1083 $this->y2texts[] = &$aTxt;
1086 if( is_array($aTxt) ) {
1087 for($i=0; $i < count($aTxt); ++$i )
1088 $this->texts[]=&$aTxt[$i];
1091 $this->texts[] = &$aTxt;
1095 // Add a line object (class PlotLine) to the graph
1096 function AddLine(&$aLine,$aToY2=false) {
1097 if( $aLine == null )
1098 JpGraphError::Raise("<b></b> Graph::AddLine() You tried to add a null line to the graph.");
1101 if( is_array($aLine) ) {
1102 for($i=0; $i < count($aLine); ++$i )
1103 $this->y2lines[]=&$aLine[$i];
1106 $this->y2lines[] = &$aLine;
1109 if( is_array($aLine) ) {
1110 for($i=0; $i<count($aLine); ++$i )
1111 $this->lines[]=&$aLine[$i];
1114 $this->lines[] = &$aLine;
1118 // Add vertical or horizontal band
1119 function AddBand(&$aBand,$aToY2=false) {
1120 if( $aBand == null )
1121 JpGraphError::Raise(" Graph::AddBand() You tried to add a null band to the graph.");
1124 if( is_array($aBand) ) {
1125 for($i=0; $i < count($aBand); ++$i )
1126 $this->y2bands[] = &$aBand[$i];
1129 $this->y2bands[] = &$aBand;
1132 if( is_array($aBand) ) {
1133 for($i=0; $i < count($aBand); ++$i )
1134 $this->bands[] = &$aBand[$i];
1137 $this->bands[] = &$aBand;
1141 function SetBackgroundGradient($aFrom='navy',$aTo='silver',$aGradType=GRAD_HOR,$aStyle=BGRAD_FRAME) {
1142 $this->bkg_gradtype=$aGradType;
1143 $this->bkg_gradstyle=$aStyle;
1144 $this->bkg_gradfrom = $aFrom;
1145 $this->bkg_gradto = $aTo;
1148 // Specify a background image
1149 function SetBackgroundImage($aFileName,$aBgType=BGIMG_FILLPLOT,$aImgFormat="auto") {
1151 if( $GLOBALS['gd2'] && !USE_TRUECOLOR ) {
1152 JpGraphError::Raise("You are using GD 2.x and are trying to use a background images on a non truecolor image. To use background images with GD 2.x you <b>must</b> enable truecolor by setting the USE_TRUECOLOR constant to TRUE. Due to a bug in GD 2.0.1 using any truetype fonts with truecolor images will result in very poor quality fonts.");
1155 // Get extension to determine image type
1156 if( $aImgFormat == "auto" ) {
1157 $e = explode('.',$aFileName);
1159 JpGraphError::Raise('Incorrect file name for Graph::SetBackgroundImage() : '.$aFileName.' Must have a valid image extension (jpg,gif,png) when using autodetection of image type');
1162 $valid_formats = array('png', 'jpg', 'gif');
1163 $aImgFormat = strtolower($e[count($e)-1]);
1164 if ($aImgFormat == 'jpeg') {
1165 $aImgFormat = 'jpg';
1167 elseif (!in_array($aImgFormat, $valid_formats) ) {
1168 JpGraphError::Raise('Unknown file extension ($aImgFormat) in Graph::SetBackgroundImage() for filename: '.$aFileName);
1172 $this->background_image = $aFileName;
1173 $this->background_image_type=$aBgType;
1174 $this->background_image_format=$aImgFormat;
1177 // Adjust brightness and constrast for background image
1178 function AdjBackgroundImage($aBright,$aContr=0,$aSat=0) {
1179 $this->background_image_bright=$aBright;
1180 $this->background_image_contr=$aContr;
1181 $this->background_image_sat=$aSat;
1184 // Adjust brightness and constrast for image
1185 function AdjImage($aBright,$aContr=0,$aSat=0) {
1186 $this->image_bright=$aBright;
1187 $this->image_contr=$aContr;
1188 $this->image_sat=$aSat;
1191 // Specify axis style (boxed or single)
1192 function SetAxisStyle($aStyle) {
1193 $this->iAxisStyle = $aStyle ;
1196 // Set a frame around the plot area
1197 function SetBox($aDrawPlotFrame=true,$aPlotFrameColor=array(0,0,0),$aPlotFrameWeight=1) {
1198 $this->boxed = $aDrawPlotFrame;
1199 $this->box_weight = $aPlotFrameWeight;
1200 $this->box_color = $aPlotFrameColor;
1203 // Specify color for the plotarea (not the margins)
1204 function SetColor($aColor) {
1205 $this->plotarea_color=$aColor;
1208 // Specify color for the margins (all areas outside the plotarea)
1209 function SetMarginColor($aColor) {
1210 $this->margin_color=$aColor;
1213 // Set a frame around the entire image
1214 function SetFrame($aDrawImgFrame=true,$aImgFrameColor=array(0,0,0),$aImgFrameWeight=1) {
1215 $this->doframe = $aDrawImgFrame;
1216 $this->frame_color = $aImgFrameColor;
1217 $this->frame_weight = $aImgFrameWeight;
1220 function SetFrameBevel($aDepth=3,$aBorder=false,$aBorderColor='black',$aColor1='white@0.4',$aColor2='darkgray@0.4',$aFlg=true) {
1221 $this->framebevel = $aFlg ;
1222 $this->framebeveldepth = $aDepth ;
1223 $this->framebevelborder = $aBorder ;
1224 $this->framebevelbordercolor = $aBorderColor ;
1225 $this->framebevelcolor1 = $aColor1 ;
1226 $this->framebevelcolor2 = $aColor2 ;
1228 $this->doshadow = false ;
1231 // Set the shadow around the whole image
1232 function SetShadow($aShowShadow=true,$aShadowWidth=5,$aShadowColor=array(102,102,102)) {
1233 $this->doshadow = $aShowShadow;
1234 $this->shadow_color = $aShadowColor;
1235 $this->shadow_width = $aShadowWidth;
1236 $this->footer->iBottomMargin += $aShadowWidth;
1237 $this->footer->iRightMargin += $aShadowWidth;
1240 // Specify x,y scale. Note that if you manually specify the scale
1241 // you must also specify the tick distance with a call to Ticks::Set()
1242 function SetScale($aAxisType,$aYMin=1,$aYMax=1,$aXMin=1,$aXMax=1) {
1243 $this->axtype = $aAxisType;
1245 if( $aYMax < $aYMin || $aXMax < $aXMin )
1246 JpGraphError::Raise('Graph::SetScale(): Specified Max value must be larger than the specified Min value.');
1248 $yt=substr($aAxisType,-3,3);
1250 $this->yscale = new LinearScale($aYMin,$aYMax);
1251 elseif( $yt == "int" ) {
1252 $this->yscale = new LinearScale($aYMin,$aYMax);
1253 $this->yscale->SetIntScale();
1255 elseif( $yt=="log" )
1256 $this->yscale = new LogScale($aYMin,$aYMax);
1258 JpGraphError::Raise("Unknown scale specification for Y-scale. ($aAxisType)");
1260 $xt=substr($aAxisType,0,3);
1261 if( $xt == "lin" || $xt == "tex" ) {
1262 $this->xscale = new LinearScale($aXMin,$aXMax,"x");
1263 $this->xscale->textscale = ($xt == "tex");
1265 elseif( $xt == "int" ) {
1266 $this->xscale = new LinearScale($aXMin,$aXMax,"x");
1267 $this->xscale->SetIntScale();
1269 elseif( $xt == "log" )
1270 $this->xscale = new LogScale($aXMin,$aXMax,"x");
1272 JpGraphError::Raise(" Unknown scale specification for X-scale. ($aAxisType)");
1274 $this->xscale->Init($this->img);
1275 $this->yscale->Init($this->img);
1277 $this->xaxis = new Axis($this->img,$this->xscale);
1278 $this->yaxis = new Axis($this->img,$this->yscale);
1279 $this->xgrid = new Grid($this->xaxis);
1280 $this->ygrid = new Grid($this->yaxis);
1281 $this->ygrid->Show();
1284 // Specify secondary Y scale
1285 function SetY2Scale($aAxisType="lin",$aY2Min=1,$aY2Max=1) {
1286 if( $aAxisType=="lin" )
1287 $this->y2scale = new LinearScale($aY2Min,$aY2Max);
1288 elseif( $aAxisType == "int" ) {
1289 $this->y2scale = new LinearScale($aY2Min,$aY2Max);
1290 $this->y2scale->SetIntScale();
1292 elseif( $aAxisType=="log" ) {
1293 $this->y2scale = new LogScale($aY2Min,$aY2Max);
1295 else JpGraphError::Raise("JpGraph: Unsupported Y2 axis type: $axtype<br>");
1297 $this->y2scale->Init($this->img);
1298 $this->y2axis = new Axis($this->img,$this->y2scale);
1299 $this->y2axis->scale->ticks->SetDirection(SIDE_LEFT);
1300 $this->y2axis->SetLabelSide(SIDE_RIGHT);
1302 // Deafult position is the max x-value
1303 $this->y2grid = new Grid($this->y2axis);
1306 // Specify density of ticks when autoscaling 'normal', 'dense', 'sparse', 'verysparse'
1307 // The dividing factor have been determined heuristically according to my aesthetic
1308 // sense (or lack off) y.m.m.v !
1309 function SetTickDensity($aYDensity=TICKD_NORMAL,$aXDensity=TICKD_NORMAL) {
1310 $this->xtick_factor=30;
1311 $this->ytick_factor=25;
1312 switch( $aYDensity ) {
1314 $this->ytick_factor=12;
1317 $this->ytick_factor=25;
1320 $this->ytick_factor=40;
1322 case TICKD_VERYSPARSE:
1323 $this->ytick_factor=100;
1326 JpGraphError::Raise("JpGraph: Unsupported Tick density: $densy");
1328 switch( $aXDensity ) {
1330 $this->xtick_factor=15;
1333 $this->xtick_factor=30;
1336 $this->xtick_factor=45;
1338 case TICKD_VERYSPARSE:
1339 $this->xtick_factor=60;
1342 JpGraphError::Raise("JpGraph: Unsupported Tick density: $densx");
1347 // Get a string of all image map areas
1348 function GetCSIMareas() {
1349 if( !$this->iHasStroked )
1350 $this->Stroke(_CSIM_SPECIALFILE);
1351 $csim=$this->legend->GetCSIMAreas();
1353 $n = count($this->plots);
1354 for( $i=0; $i<$n; ++$i )
1355 $csim .= $this->plots[$i]->GetCSIMareas();
1357 $n = count($this->y2plots);
1358 for( $i=0; $i<$n; ++$i )
1359 $csim .= $this->y2plots[$i]->GetCSIMareas();
1364 // Get a complete <MAP>..</MAP> tag for the final image map
1365 function GetHTMLImageMap($aMapName) {
1366 $im = "<MAP NAME=\"$aMapName\">\n";
1367 $im .= $this->GetCSIMareas();
1372 function CheckCSIMCache($aCacheName,$aTimeOut=60) {
1373 global $HTTP_SERVER_VARS;
1375 if( $aCacheName=='auto' )
1376 $aCacheName=basename($HTTP_SERVER_VARS['PHP_SELF']);
1378 $this->csimcachename = CSIMCACHE_DIR.$aCacheName;
1379 $this->csimcachetimeout = $aTimeOut;
1381 // First determine if we need to check for a cached version
1382 // This differs from the standard cache in the sense that the
1383 // image and CSIM map HTML file is written relative to the directory
1384 // the script executes in and not the specified cache directory.
1385 // The reason for this is that the cache directory is not necessarily
1386 // accessible from the HTTP server.
1387 if( $this->csimcachename != '' ) {
1388 $dir = dirname($this->csimcachename);
1389 $base = basename($this->csimcachename);
1390 $base = strtok($base,'.');
1391 $suffix = strtok('.');
1392 $basecsim = $dir.'/'.$base.'_csim_.html';
1393 $baseimg = $dir.'/'.$base.'.'.$this->img->img_format;
1397 // Does it exist at all ?
1399 if( file_exists($basecsim) && file_exists($baseimg) ) {
1400 // Check that it hasn't timed out
1401 $diff=time()-filemtime($basecsim);
1402 if( $this->csimcachetimeout>0 && ($diff > $this->csimcachetimeout*60) ) {
1408 if ($fh = @fopen($basecsim, "r")) {
1413 JpGraphError::Raise(" Can't open cached CSIM \"$basecsim\" for reading.");
1420 function StrokeCSIM($aScriptName='',$aCSIMName='',$aBorder=0) {
1421 GLOBAL $HTTP_GET_VARS;
1423 if( $aCSIMName=='' ) {
1424 // create a random map name
1425 srand ((double) microtime() * 1000000);
1426 $r = rand(0,100000);
1427 $aCSIMName='__mapname'.$r.'__';
1429 if( empty($HTTP_GET_VARS[_CSIM_DISPLAY]) ) {
1430 // First determine if we need to check for a cached version
1431 // This differs from the standard cache in the sense that the
1432 // image and CSIM map HTML file is written relative to the directory
1433 // the script executes in and not the specified cache directory.
1434 // The reason for this is that the cache directory is not necessarily
1435 // accessible from the HTTP server.
1436 if( $this->csimcachename != '' ) {
1437 $dir = dirname($this->csimcachename);
1438 $base = basename($this->csimcachename);
1439 $base = strtok($base,'.');
1440 $suffix = strtok('.');
1441 $basecsim = $dir.'/'.$base.'_csim_.html';
1442 $baseimg = $base.'.'.$this->img->img_format;
1444 // Check that apache can write to directory specified
1446 if( file_exists($dir) && !is_writeable($dir) ) {
1447 JpgraphError::Raise('Apache/PHP does not have permission to write to the CSIM cache directory ('.$dir.'). Check permissions.');
1450 // Make sure directory exists
1451 $this->cache->MakeDirs($dir);
1453 // Write the image file
1454 $this->Stroke(CSIMCACHE_DIR.$baseimg);
1456 // Construct wrapper HTML and write to file and send it back to browser
1457 $htmlwrap = $this->GetHTMLImageMap($aCSIMName)."\n".
1458 '<img src="'.CSIMCACHE_HTTP_DIR.$baseimg.'" ISMAP USEMAP="#'.$aCSIMName.'" border='.$aBorder.'>'."\n";
1459 if($fh = @fopen($basecsim,'w') ) {
1460 fwrite($fh,$htmlwrap);
1465 JpGraphError::Raise(" Can't write CSIM \"$basecsim\" for writing. Check free space and permissions.");
1469 if( $aScriptName=='' ) {
1470 JpGraphError::Raise('Missing script name in call to StrokeCSIM(). You must specify the name of the actual image script as the first parameter to StrokeCSIM().');
1474 // Construct the HTML wrapper page
1475 // Get all user defined URL arguments
1476 reset($HTTP_GET_VARS);
1478 // This is a JPGRAPH internal defined that prevents
1479 // us from recursively coming here again
1480 $urlarg='?'._CSIM_DISPLAY.'=1';
1482 while( list($key,$value) = each($HTTP_GET_VARS) ) {
1483 if( is_array($value) ) {
1485 for( $i=0; $i < $n; ++$i ) {
1486 $urlarg .= '&'.$key.'%5B%5D='.urlencode($value[$i]);
1490 $urlarg .= '&'.$key.'='.urlencode($value);
1494 echo $this->GetHTMLImageMap($aCSIMName);
1496 echo "<img src='".$aScriptName.$urlarg."' ISMAP USEMAP='#".$aCSIMName."' border=$aBorder>";
1504 function GetTextsYMinMax($aY2=false) {
1506 $txts = $this->y2texts;
1508 $txts = $this->texts;
1512 for( $i=0; $i < $n; ++$i ) {
1513 if( $txts[$i]->iScalePosY !== null &&
1514 $txts[$i]->iScalePosX !== null ) {
1515 if( $min === null ) {
1516 $min = $max = $txts[$i]->iScalePosY ;
1519 $min = min($min,$txts[$i]->iScalePosY);
1520 $max = max($max,$txts[$i]->iScalePosY);
1524 if( $min !== null ) {
1525 return array($min,$max);
1531 function GetTextsXMinMax($aY2=false) {
1533 $txts = $this->y2texts;
1535 $txts = $this->texts;
1539 for( $i=0; $i < $n; ++$i ) {
1540 if( $txts[$i]->iScalePosY !== null &&
1541 $txts[$i]->iScalePosX !== null ) {
1542 if( $min === null ) {
1543 $min = $max = $txts[$i]->iScalePosX ;
1546 $min = min($min,$txts[$i]->iScalePosX);
1547 $max = max($max,$txts[$i]->iScalePosX);
1551 if( $min !== null ) {
1552 return array($min,$max);
1558 function GetXMinMax() {
1559 list($min,$ymin) = $this->plots[0]->Min();
1560 list($max,$ymax) = $this->plots[0]->Max();
1561 foreach( $this->plots as $p ) {
1562 list($xmin,$ymin) = $p->Min();
1563 list($xmax,$ymax) = $p->Max();
1564 $min = Min($xmin,$min);
1565 $max = Max($xmax,$max);
1567 if( $this->y2axis != null ) {
1568 foreach( $this->y2plots as $p ) {
1569 list($xmin,$ymin) = $p->Min();
1570 list($xmax,$ymax) = $p->Max();
1571 $min = Min($xmin,$min);
1572 $max = Max($xmax,$max);
1575 return array($min,$max);
1578 function AdjustMarginsForTitles() {
1580 ($this->title->t != '' ?
1581 $this->title->GetTextHeight($this->img) + $this->title->margin + 5 : 0 ) +
1582 ($this->subtitle->t != '' ?
1583 $this->subtitle->GetTextHeight($this->img) + $this->subtitle->margin + 5 : 0 ) +
1584 ($this->subsubtitle->t != '' ?
1585 $this->subsubtitle->GetTextHeight($this->img) + $this->subsubtitle->margin + 5 : 0 ) ;
1589 if( !$this->xaxis->hide && !$this->xaxis->hide_labels ) {
1590 // Minimum bottom margin
1591 if( $this->xaxis->title->t != '' ) {
1592 if( $this->img->a == 90 )
1593 $btotrequired = $this->yaxis->title->GetTextHeight($this->img) + 5 ;
1595 $btotrequired = $this->xaxis->title->GetTextHeight($this->img) + 5 ;
1600 if( $this->img->a == 90 ) {
1601 $this->img->SetFont($this->yaxis->font_family,$this->yaxis->font_style,
1602 $this->yaxis->font_size);
1603 $lh = $this->img->GetTextHeight('Mg',$this->yaxis->label_angle);
1606 $this->img->SetFont($this->xaxis->font_family,$this->xaxis->font_style,
1607 $this->xaxis->font_size);
1608 $lh = $this->img->GetTextHeight('Mg',$this->xaxis->label_angle);
1611 $btotrequired += $lh + 5;
1614 if( $this->img->a == 90 ) {
1615 // DO Nothing. It gets too messy to do this properly for 90 deg...
1618 if( $this->img->top_margin < $totrequired ) {
1619 $this->SetMargin($this->img->left_margin,$this->img->right_margin,
1620 $totrequired,$this->img->bottom_margin);
1622 if( $this->img->bottom_margin < $btotrequired ) {
1623 $this->SetMargin($this->img->left_margin,$this->img->right_margin,
1624 $this->img->top_margin,$btotrequired);
1630 // $aStrokeFileName If != "" the image will be written to this file and NOT
1631 // streamed back to the browser
1632 function Stroke($aStrokeFileName="") {
1634 // Start by adjusting the margin so that potential titles will fit.
1635 $this->AdjustMarginsForTitles();
1637 // If the filename is the predefined value = '_csim_special_'
1638 // we assume that the call to stroke only needs to do enough
1639 // to correctly generate the CSIM maps.
1640 // We use this variable to skip things we don't strictly need
1641 // to do to generate the image map to improve performance
1642 // a best we can. Therefor you will see a lot of tests !$_csim in the
1644 $_csim = ($aStrokeFileName===_CSIM_SPECIALFILE);
1646 // We need to know if we have stroked the plot in the
1647 // GetCSIMareas. Otherwise the CSIM hasn't been generated
1648 // and in the case of GetCSIM called before stroke to generate
1649 // CSIM without storing an image to disk GetCSIM must call Stroke.
1650 $this->iHasStroked = true;
1652 // Do any pre-stroke adjustment that is needed by the different plot types
1653 // (i.e bar plots want's to add an offset to the x-labels etc)
1654 for($i=0; $i<count($this->plots) ; ++$i ) {
1655 $this->plots[$i]->PreStrokeAdjust($this);
1656 $this->plots[$i]->DoLegend($this);
1659 // Any plots on the second Y scale?
1660 if( $this->y2scale != null ) {
1661 for($i=0; $i<count($this->y2plots) ; ++$i ) {
1662 $this->y2plots[$i]->PreStrokeAdjust($this);
1663 $this->y2plots[$i]->DoLegend($this);
1667 // Bail out if any of the Y-axis not been specified and
1668 // has no plots. (This means it is impossible to do autoscaling and
1669 // no other scale was given so we can't possible draw anything). If you use manual
1670 // scaling you also have to supply the tick steps as well.
1671 if( (!$this->yscale->IsSpecified() && count($this->plots)==0) ||
1672 ($this->y2scale!=null && !$this->y2scale->IsSpecified() && count($this->y2plots)==0) ) {
1673 //$e = "n=".count($this->y2plots)."\n";
1674 $e = "Can't draw unspecified Y-scale.<br>\nYou have either:<br>\n";
1675 $e .= "1. Specified an Y axis for autoscaling but have not supplied any plots<br>\n";
1676 $e .= "2. Specified a scale manually but have forgot to specify the tick steps";
1677 JpGraphError::Raise($e);
1680 // Bail out if no plots and no specified X-scale
1681 if( (!$this->xscale->IsSpecified() && count($this->plots)==0 && count($this->y2plots)==0) )
1682 JpGraphError::Raise("<strong>JpGraph: Can't draw unspecified X-scale.</strong><br>No plots.<br>");
1684 //Check if we should autoscale y-axis
1685 if( !$this->yscale->IsSpecified() && count($this->plots)>0 ) {
1686 list($min,$max) = $this->GetPlotsYMinMax($this->plots);
1687 $lres = $this->GetLinesYMinMax($this->lines);
1689 list($linmin,$linmax) = $lres ;
1690 $min = min($min,$linmin);
1691 $max = max($max,$linmax);
1693 $tres = $this->GetTextsYMinMax();
1695 list($tmin,$tmax) = $tres ;
1696 $min = min($min,$tmin);
1697 $max = max($max,$tmax);
1699 $this->yscale->AutoScale($this->img,$min,$max,
1700 $this->img->plotheight/$this->ytick_factor);
1702 elseif( $this->yscale->IsSpecified() &&
1703 ( $this->yscale->auto_ticks || !$this->yscale->ticks->IsSpecified()) ) {
1704 // The tick calculation will use the user suplied min/max values to determine
1705 // the ticks. If auto_ticks is false the exact user specifed min and max
1706 // values will be used for the scale.
1707 // If auto_ticks is true then the scale might be slightly adjusted
1708 // so that the min and max values falls on an even major step.
1709 $min = $this->yscale->scale[0];
1710 $max = $this->yscale->scale[1];
1711 $this->yscale->AutoScale($this->img,$min,$max,
1712 $this->img->plotheight/$this->ytick_factor,
1713 $this->yscale->auto_ticks);
1716 if( $this->y2scale != null) {
1717 if( !$this->y2scale->IsSpecified() && count($this->y2plots)>0 ) {
1718 list($min,$max) = $this->GetPlotsYMinMax($this->y2plots);
1720 $lres = $this->GetLinesYMinMax($this->y2lines);
1722 list($linmin,$linmax) = $lres ;
1723 $min = min($min,$linmin);
1724 $max = max($max,$linmax);
1726 $tres = $this->GetTextsYMinMax(true);
1728 list($tmin,$tmax) = $tres ;
1729 $min = min($min,$tmin);
1730 $max = max($max,$tmax);
1732 $this->y2scale->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor);
1734 elseif( $this->y2scale->IsSpecified() &&
1735 ( $this->y2scale->auto_ticks || !$this->y2scale->ticks->IsSpecified()) ) {
1736 // The tick calculation will use the user suplied min/max values to determine
1737 // the ticks. If auto_ticks is false the exact user specifed min and max
1738 // values will be used for the scale.
1739 // If auto_ticks is true then the scale might be slightly adjusted
1740 // so that the min and max values falls on an even major step.
1741 $min = $this->y2scale->scale[0];
1742 $max = $this->y2scale->scale[1];
1743 $this->y2scale->AutoScale($this->img,$min,$max,
1744 $this->img->plotheight/$this->ytick_factor,
1745 $this->y2scale->auto_ticks);
1749 //Check if we should autoscale x-axis
1750 if( !$this->xscale->IsSpecified() ) {
1751 if( substr($this->axtype,0,4) == "text" ) {
1753 foreach( $this->plots as $p ) {
1754 $max=max($max,$p->numpoints-1);
1757 if( $this->y2axis != null ) {
1758 foreach( $this->y2plots as $p ) {
1759 $max=max($max,$p->numpoints-1);
1762 $this->xscale->Update($this->img,$min,$max);
1763 $this->xscale->ticks->Set($this->xaxis->tick_step,1);
1764 $this->xscale->ticks->SupressMinorTickMarks();
1767 list($min,$max) = $this->GetXMinMax();
1768 $lres = $this->GetLinesXMinMax($this->lines);
1770 list($linmin,$linmax) = $lres ;
1771 $min = min($min,$linmin);
1772 $max = max($max,$linmax);
1775 $lres = $this->GetLinesXMinMax($this->y2lines);
1777 list($linmin,$linmax) = $lres ;
1778 $min = min($min,$linmin);
1779 $max = max($max,$linmax);
1782 $tres = $this->GetTextsXMinMax();
1784 list($tmin,$tmax) = $tres ;
1785 $min = min($min,$tmin);
1786 $max = max($max,$tmax);
1789 $tres = $this->GetTextsXMinMax(true);
1791 list($tmin,$tmax) = $tres ;
1792 $min = min($min,$tmin);
1793 $max = max($max,$tmax);
1796 $this->xscale->AutoScale($this->img,$min,$max,$this->img->plotwidth/$this->xtick_factor);
1799 //Adjust position of y-axis and y2-axis to minimum/maximum of x-scale
1800 if( !is_numeric($this->yaxis->pos) && !is_string($this->yaxis->pos) )
1801 $this->yaxis->SetPos($this->xscale->GetMinVal());
1802 if( $this->y2axis != null ) {
1803 if( !is_numeric($this->y2axis->pos) && !is_string($this->y2axis->pos) )
1804 $this->y2axis->SetPos($this->xscale->GetMaxVal());
1805 $this->y2axis->SetTitleSide(SIDE_RIGHT);
1808 elseif( $this->xscale->IsSpecified() &&
1809 ( $this->xscale->auto_ticks || !$this->xscale->ticks->IsSpecified()) ) {
1810 // The tick calculation will use the user suplied min/max values to determine
1811 // the ticks. If auto_ticks is false the exact user specifed min and max
1812 // values will be used for the scale.
1813 // If auto_ticks is true then the scale might be slightly adjusted
1814 // so that the min and max values falls on an even major step.
1815 $min = $this->xscale->scale[0];
1816 $max = $this->xscale->scale[1];
1817 $this->xscale->AutoScale($this->img,$min,$max,
1818 $this->img->plotwidth/$this->xtick_factor,
1821 if( $this->y2axis != null ) {
1822 if( !is_numeric($this->y2axis->pos) && !is_string($this->y2axis->pos) )
1823 $this->y2axis->SetPos($this->xscale->GetMaxVal());
1824 $this->y2axis->SetTitleSide(SIDE_RIGHT);
1829 // If we have a negative values and x-axis position is at 0
1830 // we need to supress the first and possible the last tick since
1831 // they will be drawn on top of the y-axis (and possible y2 axis)
1832 // The test below might seem strange the reasone being that if
1833 // the user hasn't specified a value for position this will not
1834 // be set until we do the stroke for the axis so as of now it
1836 // For X-text scale we ignore all this since the tick are usually
1837 // much further in and not close to the Y-axis. Hence the test
1840 if( ($this->yaxis->pos==$this->xscale->GetMinVal() ||
1841 (is_string($this->yaxis->pos) && $this->yaxis->pos=='min')) &&
1842 !is_numeric($this->xaxis->pos) && $this->yscale->GetMinVal() < 0 &&
1843 substr($this->axtype,0,4) != 'text' && $this->xaxis->pos!="min" ) {
1845 //$this->yscale->ticks->SupressZeroLabel(false);
1846 $this->xscale->ticks->SupressFirst();
1847 if( $this->y2axis != null ) {
1848 $this->xscale->ticks->SupressLast();
1851 elseif( !is_numeric($this->yaxis->pos) && $this->yaxis->pos=='max' ) {
1852 $this->xscale->ticks->SupressLast();
1857 $this->StrokePlotArea();
1858 $this->StrokeAxis();
1862 if( $this->bands != null && !$_csim)
1863 for($i=0; $i < count($this->bands); ++$i) {
1864 // Stroke all bands that asks to be in the background
1865 if( $this->bands[$i]->depth == DEPTH_BACK )
1866 $this->bands[$i]->Stroke($this->img,$this->xscale,$this->yscale);
1869 if( $this->y2bands != null && $this->y2scale != null && !$_csim )
1870 for($i=0; $i < count($this->y2bands); ++$i) {
1871 // Stroke all bands that asks to be in the foreground
1872 if( $this->y2bands[$i]->depth == DEPTH_BACK )
1873 $this->y2bands[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
1877 if( $this->grid_depth == DEPTH_BACK && !$_csim) {
1878 $this->ygrid->Stroke();
1879 $this->xgrid->Stroke();
1883 if( $this->y2axis != null && !$_csim) {
1884 $this->y2axis->Stroke($this->xscale);
1885 $this->y2grid->Stroke();
1888 $oldoff=$this->xscale->off;
1889 if(substr($this->axtype,0,4)=="text") {
1890 $this->xscale->off +=
1891 ceil($this->xscale->scale_factor*$this->text_scale_off*$this->xscale->ticks->minor_step);
1894 if( $this->iDoClipping ) {
1895 $oldimage = $this->img->CloneCanvasH();
1898 if( ! $this->y2orderback ) {
1899 // Stroke all plots for Y1 axis
1900 for($i=0; $i < count($this->plots); ++$i) {
1901 $this->plots[$i]->Stroke($this->img,$this->xscale,$this->yscale);
1902 $this->plots[$i]->StrokeMargin($this->img);
1906 // Stroke all plots for Y2 axis
1907 if( $this->y2scale != null )
1908 for($i=0; $i< count($this->y2plots); ++$i ) {
1909 $this->y2plots[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
1912 if( $this->y2orderback ) {
1913 // Stroke all plots for Y1 axis
1914 for($i=0; $i < count($this->plots); ++$i) {
1915 $this->plots[$i]->Stroke($this->img,$this->xscale,$this->yscale);
1916 $this->plots[$i]->StrokeMargin($this->img);
1921 if( $this->iDoClipping ) {
1922 // Clipping only supports graphs at 0 and 90 degrees
1923 if( $this->img->a == 0 ) {
1924 $this->img->CopyCanvasH($oldimage,$this->img->img,
1925 $this->img->left_margin,$this->img->top_margin,
1926 $this->img->left_margin,$this->img->top_margin,
1927 $this->img->plotwidth+1,$this->img->plotheight);
1929 elseif( $this->img->a == 90 ) {
1930 $adj = ($this->img->height - $this->img->width)/2;
1931 $this->img->CopyCanvasH($oldimage,$this->img->img,
1932 $this->img->bottom_margin-$adj,$this->img->left_margin+$adj,
1933 $this->img->bottom_margin-$adj,$this->img->left_margin+$adj,
1934 $this->img->plotheight+1,$this->img->plotwidth);
1937 JpGraphError::Raise('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.');
1939 $this->img->Destroy();
1940 $this->img->SetCanvasH($oldimage);
1943 $this->xscale->off=$oldoff;
1945 if( $this->grid_depth == DEPTH_FRONT && !$_csim ) {
1946 $this->ygrid->Stroke();
1947 $this->xgrid->Stroke();
1951 if( $this->bands!= null )
1952 for($i=0; $i < count($this->bands); ++$i) {
1953 // Stroke all bands that asks to be in the foreground
1954 if( $this->bands[$i]->depth == DEPTH_FRONT )
1955 $this->bands[$i]->Stroke($this->img,$this->xscale,$this->yscale);
1958 if( $this->y2bands!= null && $this->y2scale != null )
1959 for($i=0; $i < count($this->y2bands); ++$i) {
1960 // Stroke all bands that asks to be in the foreground
1961 if( $this->y2bands[$i]->depth == DEPTH_FRONT )
1962 $this->y2bands[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
1966 // Stroke any lines added
1967 if( $this->lines != null ) {
1968 for($i=0; $i < count($this->lines); ++$i) {
1969 $this->lines[$i]->Stroke($this->img,$this->xscale,$this->yscale);
1973 if( $this->y2lines != null && $this->y2scale != null ) {
1974 for($i=0; $i < count($this->y2lines); ++$i) {
1975 $this->y2lines[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
1980 // Finally draw the axis again since some plots may have nagged
1981 // the axis in the edges.
1983 $this->StrokeAxis();
1985 if( $this->y2scale != null && !$_csim )
1986 $this->y2axis->Stroke($this->xscale);
1989 $this->StrokePlotBox();
1993 // The titles and legends never gets rotated so make sure
1994 // that the angle is 0 before stroking them
1995 $aa = $this->img->SetAngle(0);
1996 $this->StrokeTitles();
1997 $this->footer->Stroke($this->img);
2000 $this->legend->Stroke($this->img);
2004 $this->StrokeTexts();
2005 $this->img->SetAngle($aa);
2007 // Draw an outline around the image map
2009 $this->DisplayClientSideaImageMapAreas();
2011 // Adjust the appearance of the image
2012 $this->AdjustSaturationBrightnessContrast();
2014 // If the filename is given as the special "__handle"
2015 // then the image handler is returned and the image is NOT
2017 if( $aStrokeFileName == _IMG_HANDLER ) {
2018 return $this->img->img;
2021 // Finally stream the generated picture
2022 $this->cache->PutAndStream($this->img,$this->cache_name,$this->inline,
2030 function StrokeAxis() {
2033 if( $this->iAxisStyle != AXSTYLE_SIMPLE ) {
2034 switch( $this->iAxisStyle ) {
2035 case AXSTYLE_BOXIN :
2036 $toppos = SIDE_DOWN;
2037 $bottompos = SIDE_UP;
2038 $leftpos = SIDE_RIGHT;
2039 $rightpos = SIDE_LEFT;
2041 case AXSTYLE_BOXOUT :
2043 $bottompos = SIDE_DOWN;
2044 $leftpos = SIDE_LEFT;
2045 $rightpos = SIDE_RIGHT;
2047 case AXSTYLE_YBOXIN:
2049 $bottompos = SIDE_UP;
2050 $leftpos = SIDE_RIGHT;
2051 $rightpos = SIDE_LEFT;
2053 case AXSTYLE_YBOXOUT:
2055 $bottompos = SIDE_DOWN;
2056 $leftpos = SIDE_LEFT;
2057 $rightpos = SIDE_RIGHT;
2060 JpGRaphError::Raise('Unknown AxisStyle() : '.$this->iAxisStyle);
2063 $this->xaxis->SetPos('min');
2065 // By default we hide the first label so it doesn't cross the
2066 // Y-axis in case the positon hasn't been set by the user.
2067 // However, if we use a box we always want the first value
2068 // displayed so we make sure it will be displayed.
2069 $this->xscale->ticks->SupressFirst(false);
2071 $this->xaxis->SetLabelSide(SIDE_DOWN);
2072 $this->xaxis->scale->ticks->SetSide($bottompos);
2073 $this->xaxis->Stroke($this->yscale);
2075 if( $toppos != -100 ) {
2076 // To avoid side effects we work on a new copy
2077 $maxis = $this->xaxis;
2078 $maxis->SetPos('max');
2079 $maxis->SetLabelSide(SIDE_UP);
2080 $maxis->SetLabelMargin(7);
2081 $this->xaxis->scale->ticks->SetSide($toppos);
2082 $maxis->Stroke($this->yscale);
2085 $this->yaxis->SetPos('min');
2086 $this->yaxis->SetLabelMargin(10);
2087 $this->yaxis->SetLabelSide(SIDE_LEFT);
2088 $this->yaxis->scale->ticks->SetSide($leftpos);
2089 $this->yaxis->Stroke($this->xscale);
2091 $myaxis = $this->yaxis;
2092 $myaxis->SetPos('max');
2093 $myaxis->SetLabelMargin(10);
2094 $myaxis->SetLabelSide(SIDE_RIGHT);
2095 $myaxis->title->Set('');
2096 $myaxis->scale->ticks->SetSide($rightpos);
2097 $myaxis->Stroke($this->xscale);
2101 $this->xaxis->Stroke($this->yscale);
2102 $this->yaxis->Stroke($this->xscale);
2107 // Private helper function for backgound image
2108 function LoadBkgImage($aImgFormat='',$aFile='') {
2110 $aFile = $this->background_image;
2111 // Remove case sensitivity and setup appropriate function to create image
2112 // Get file extension. This should be the LAST '.' separated part of the filename
2113 $e = explode('.',$aFile);
2114 $ext = strtolower($e[count($e)-1]);
2115 if ($ext == "jpeg") {
2119 if( trim($ext) == '' )
2120 $ext = 'png'; // Assume PNG if no extension specified
2122 if( $aImgFormat == '' )
2125 $imgtag = $aImgFormat;
2127 if( $imgtag == "jpg" || $imgtag == "jpeg")
2129 $f = "imagecreatefromjpeg";
2134 $f = "imagecreatefrom".$imgtag;
2137 // Compare specified image type and file extension
2138 if( $imgtag != $ext ) {
2139 $t = " Background image seems to be of different type (has different file extension)".
2140 " than specified imagetype. Specified: '".
2141 $aImgFormat."'File: '".$aFile."'";
2142 JpGraphError::Raise($t);
2147 JpGraphError::Raise(" Can't read background image: '".$aFile."'");
2152 function StrokeBackgroundGrad() {
2153 if( $this->bkg_gradtype < 0 )
2155 $grad = new Gradient($this->img);
2156 if( $this->bkg_gradstyle == BGRAD_PLOT ) {
2157 $xl = $this->img->left_margin;
2158 $yt = $this->img->top_margin;
2159 $xr = $xl + $this->img->plotwidth ;
2160 $yb = $yt + $this->img->plotheight ;
2165 $xr = $xl + $this->img->width - 1;
2166 $yb = $yt + $this->img->height - 1;
2168 if( $this->doshadow ) {
2169 $xr -= $this->shadow_width;
2170 $yb -= $this->shadow_width;
2172 if( $this->doframe ) {
2174 $xl += $this->frame_weight;
2175 $xr -= $this->frame_weight;
2177 $grad->FilledRectangle($xl,$yt,$xr,$yb,
2178 $this->bkg_gradfrom,$this->bkg_gradto,
2179 $this->bkg_gradtype);
2182 function StrokeFrameBackground() {
2183 if( $this->background_image == "" )
2186 $bkgimg = $this->LoadBkgImage($this->background_image_format);
2187 $this->img->_AdjBrightContrast($bkgimg,$this->background_image_bright,
2188 $this->background_image_contr);
2189 $this->img->_AdjSat($bkgimg,$this->background_image_sat);
2190 $bw = ImageSX($bkgimg);
2191 $bh = ImageSY($bkgimg);
2193 // No matter what the angle is we always stroke the image and frame
2194 // assuming it is 0 degree
2195 $aa = $this->img->SetAngle(0);
2197 switch( $this->background_image_type ) {
2198 case BGIMG_FILLPLOT: // Resize to just fill the plotarea
2199 $this->StrokeFrame();
2200 $GLOBALS['copyfunc']($this->img->img,$bkgimg,
2201 $this->img->left_margin,$this->img->top_margin,
2202 0,0,$this->img->plotwidth,$this->img->plotheight,
2205 case BGIMG_FILLFRAME: // Fill the whole area from upper left corner, resize to just fit
2206 $GLOBALS['copyfunc']($this->img->img,$bkgimg,
2208 $this->img->width,$this->img->height,
2210 $this->StrokeFrame();
2212 case BGIMG_COPY: // Just copy the image from left corner, no resizing
2213 $GLOBALS['copyfunc']($this->img->img,$bkgimg,
2217 $this->StrokeFrame();
2219 case BGIMG_CENTER: // Center original image in the plot area
2220 $centerx = round($this->img->plotwidth/2+$this->img->left_margin-$bw/2);
2221 $centery = round($this->img->plotheight/2+$this->img->top_margin-$bh/2);
2222 $GLOBALS['copyfunc']($this->img->img,$bkgimg,
2227 $this->StrokeFrame();
2230 JpGraphError::Raise(" Unknown background image layout");
2232 $this->img->SetAngle($aa);
2236 // Draw a frame around the image
2237 function StrokeFrame() {
2238 if( !$this->doframe ) return;
2239 if( $this->background_image_type <= 1 &&
2240 ($this->bkg_gradtype < 0 ||
2241 ($this->bkg_gradtype > 0 && $this->bkg_gradstyle==BGRAD_PLOT) ) )
2242 $c = $this->margin_color;
2246 if( $this->doshadow ) {
2247 $this->img->SetColor($this->frame_color);
2248 $this->img->ShadowRectangle(0,0,$this->img->width,$this->img->height,
2249 $c,$this->shadow_width,$this->shadow_color);
2251 elseif( $this->framebevel ) {
2253 $this->img->SetColor($this->margin_color);
2254 $this->img->FilledRectangle(0,0,$this->img->width-1,$this->img->height-1);
2256 $this->img->Bevel(1,1,$this->img->width-2,$this->img->height-2,
2257 $this->framebeveldepth,
2258 $this->framebevelcolor1,$this->framebevelcolor2);
2259 if( $this->framebevelborder ) {
2260 $this->img->SetColor($this->framebevelbordercolor);
2261 $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);
2265 $this->img->SetLineWeight($this->frame_weight);
2267 $this->img->SetColor($this->margin_color);
2268 $this->img->FilledRectangle(0,0,$this->img->width-1,$this->img->height-1);
2270 $this->img->SetColor($this->frame_color);
2271 $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);
2275 // Stroke the plot area with either a solid color or a background image
2276 function StrokePlotArea() {
2277 // Note: To be consistent we really should take a possible shadow
2278 // into account. However, that causes some problem for the LinearScale class
2279 // since in the current design it does not have any links to class Graph which
2280 // means it has no way of compensating for the adjusted plotarea in case of a
2281 // shadow. So, until I redesign LinearScale we can't compensate for this.
2282 // So just set the two adjustment parameters to zero for now.
2283 $boxadj = 0; //$this->doframe ? $this->frame_weight : 0 ;
2284 $adj = 0; //$this->doshadow ? $this->shadow_width : 0 ;
2286 if( $this->background_image != "" ) {
2287 $this->StrokeFrameBackground();
2290 $aa = $this->img->SetAngle(0);
2291 $this->StrokeFrame();
2292 $aa = $this->img->SetAngle($aa);
2293 $this->StrokeBackgroundGrad();
2294 if( $this->bkg_gradtype < 0 ||
2295 ($this->bkg_gradtype > 0 && $this->bkg_gradstyle==BGRAD_MARGIN ) ) {
2296 $this->img->PushColor($this->plotarea_color);
2297 $this->img->FilledRectangle($this->img->left_margin+$boxadj,
2298 $this->img->top_margin+$boxadj,
2299 $this->img->width-$this->img->right_margin-$adj-2*$boxadj,
2300 $this->img->height-$this->img->bottom_margin-$adj-2*$boxadj);
2301 $this->img->PopColor();
2307 function StrokePlotBox() {
2308 // Should we draw a box around the plot area?
2309 if( $this->boxed ) {
2310 $this->img->SetLineWeight(1);
2311 $this->img->SetColor($this->box_color);
2312 for($i=0; $i < $this->box_weight; ++$i ) {
2313 $this->img->Rectangle(
2314 $this->img->left_margin-$i,$this->img->top_margin-$i,
2315 $this->img->width-$this->img->right_margin+$i,
2316 $this->img->height-$this->img->bottom_margin+$i);
2321 function SetTitleBackgroundFillStyle($aStyle,$aColor1='black',$aColor2='white') {
2322 $this->titlebkg_fillstyle = $aStyle;
2323 $this->titlebkg_scolor1 = $aColor1;
2324 $this->titlebkg_scolor2 = $aColor2;
2327 function SetTitleBackground($aBackColor='gray', $aStyle=TITLEBKG_STYLE1, $aFrameStyle=TITLEBKG_FRAME_NONE, $aFrameColor='black', $aFrameWeight=1, $aBevelHeight=3, $aEnable=true) {
2328 $this->titlebackground = $aEnable;
2329 $this->titlebackground_color = $aBackColor;
2330 $this->titlebackground_style = $aStyle;
2331 $this->titlebackground_framecolor = $aFrameColor;
2332 $this->titlebackground_framestyle = $aFrameStyle;
2333 $this->titlebackground_frameweight = $aFrameWeight;
2334 $this->titlebackground_bevelheight = $aBevelHeight ;
2338 function StrokeTitles() {
2342 if( $this->titlebackground ) {
2345 $this->title->margin += 2 ;
2346 $h = $this->title->GetTextHeight($this->img)+$this->title->margin+$margin;
2347 if( $this->subtitle->t != "" && !$this->subtitle->hide ) {
2348 $h += $this->subtitle->GetTextHeight($this->img)+$margin+
2349 $this->subtitle->margin;
2351 if( $this->subsubtitle->t != "" && !$this->subsubtitle->hide ) {
2352 $h += $this->subsubtitle->GetTextHeight($this->img)+$margin+
2353 $this->subsubtitle->margin;
2355 $this->img->PushColor($this->titlebackground_color);
2356 if( $this->titlebackground_style === 1 ) {
2358 if( $this->framebevel ) {
2359 $x1 = $y1 = $this->framebeveldepth + 1 ;
2360 $x2 = $this->img->width - $this->framebeveldepth - 2 ;
2361 $this->title->margin += $this->framebeveldepth + 1 ;
2365 $x1 = $y1 = $this->frame_weight;
2366 $x2 = $this->img->width - 2*$x1;
2369 elseif( $this->titlebackground_style === 2 ) {
2370 // Cover the frame as well
2372 $x2 = $this->img->width - 1 ;
2374 elseif( $this->titlebackground_style === 3) {
2375 // Cover the frame as well (the difference is that
2376 // for style==3 a bevel frame border is on top
2377 // of the title background)
2379 $x2 = $this->img->width - 1 ;
2380 $h += $this->framebeveldepth ;
2381 $this->title->margin += $this->framebeveldepth ;
2384 JpGraphError::Raise('Unknown title background style.');
2387 if( $this->titlebackground_framestyle === 3 ) {
2388 $h += $this->titlebackground_bevelheight*2 + 1 ;
2389 $this->title->margin += $this->titlebackground_bevelheight ;
2392 if( $this->doshadow ) {
2393 $x2 -= $this->shadow_width ;
2396 if( $this->titlebkg_fillstyle==TITLEBKG_FILLSTYLE_HSTRIPED ) {
2397 $this->img->FilledRectangle2($x1,$y1,$x2,$h,
2398 $this->titlebkg_scolor1,
2399 $this->titlebkg_scolor2);
2401 elseif( $this->titlebkg_fillstyle==TITLEBKG_FILLSTYLE_VSTRIPED ) {
2402 $this->img->FilledRectangle2($x1,$y1,$x2,$h,
2403 $this->titlebkg_scolor1,
2404 $this->titlebkg_scolor2,2);
2408 $this->img->FilledRectangle($x1,$y1,$x2,$h);
2410 $this->img->PopColor();
2412 $this->img->PushColor($this->titlebackground_framecolor);
2413 $this->img->SetLineWeight($this->titlebackground_frameweight);
2414 if( $this->titlebackground_framestyle == 1 ) {
2416 $this->img->Rectangle($x1,$y1,$x2,$h);
2418 elseif( $this->titlebackground_framestyle == 2 ) {
2420 $this->img->Line($x1,$h,$x2,$h);
2422 elseif( $this->titlebackground_framestyle == 3 ) {
2423 $this->img->Bevel($x1,$y1,$x2,$h,$this->titlebackground_bevelheight);
2425 $this->img->PopColor();
2427 // This is clumsy. But we neeed to stroke the whole graph frame if it is
2428 // set to bevel to get the bevel shading on top of the text background
2429 if( $this->framebevel && $this->doframe &&
2430 $this->titlebackground_style === 3 ) {
2431 $this->img->Bevel(1,1,$this->img->width-2,$this->img->height-2,
2432 $this->framebeveldepth,
2433 $this->framebevelcolor1,$this->framebevelcolor2);
2434 if( $this->framebevelborder ) {
2435 $this->img->SetColor($this->framebevelbordercolor);
2436 $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);
2442 $y = $this->title->margin;
2443 $this->title->Center(0,$this->img->width,$y);
2444 $this->title->Stroke($this->img);
2447 $y += $this->title->GetTextHeight($this->img) + $margin + $this->subtitle->margin;
2448 $this->subtitle->Center(0,$this->img->width,$y);
2449 $this->subtitle->Stroke($this->img);
2451 // ... and subsubtitle
2452 $y += $this->subtitle->GetTextHeight($this->img) + $margin + $this->subsubtitle->margin;
2453 $this->subsubtitle->Center(0,$this->img->width,$y);
2454 $this->subsubtitle->Stroke($this->img);
2456 // ... and fancy title
2457 $this->tabtitle->Stroke($this->img);
2461 function StrokeTexts() {
2462 // Stroke any user added text objects
2463 if( $this->texts != null ) {
2464 for($i=0; $i < count($this->texts); ++$i) {
2465 $this->texts[$i]->StrokeWithScale($this->img,$this->xscale,$this->yscale);
2469 if( $this->y2texts != null && $this->y2scale != null ) {
2470 for($i=0; $i < count($this->y2texts); ++$i) {
2471 $this->y2texts[$i]->StrokeWithScale($this->img,$this->xscale,$this->y2scale);
2477 function DisplayClientSideaImageMapAreas() {
2478 // Debug stuff - display the outline of the image map areas
2479 foreach ($this->plots as $p) {
2480 $csim.= $p->GetCSIMareas();
2482 $csim .= $this->legend->GetCSIMareas();
2483 if (preg_match_all("/area shape=\"(\w+)\" coords=\"([0-9\, ]+)\"/", $csim, $coords)) {
2484 $this->img->SetColor($this->csimcolor);
2485 for ($i=0; $i<count($coords[0]); $i++) {
2486 if ($coords[1][$i]=="poly") {
2487 preg_match_all('/\s*([0-9]+)\s*,\s*([0-9]+)\s*,*/',$coords[2][$i],$pts);
2488 $this->img->SetStartPoint($pts[1][count($pts[0])-1],$pts[2][count($pts[0])-1]);
2489 for ($j=0; $j<count($pts[0]); $j++) {
2490 $this->img->LineTo($pts[1][$j],$pts[2][$j]);
2492 } else if ($coords[1][$i]=="rect") {
2493 $pts = preg_split('/,/', $coords[2][$i]);
2494 $this->img->SetStartPoint($pts[0],$pts[1]);
2495 $this->img->LineTo($pts[2],$pts[1]);
2496 $this->img->LineTo($pts[2],$pts[3]);
2497 $this->img->LineTo($pts[0],$pts[3]);
2498 $this->img->LineTo($pts[0],$pts[1]);
2504 function AdjustSaturationBrightnessContrast() {
2505 // Adjust the brightness and contrast of the image
2506 if( $this->image_contr || $this->image_bright )
2507 $this->img->AdjBrightContrast($this->image_bright,$this->image_contr);
2508 if( $this->image_sat )
2509 $this->img->AdjSat($this->image_sat);
2512 // Text scale offset in world coordinates
2513 function SetTextScaleOff($aOff) {
2514 $this->text_scale_off = $aOff;
2515 $this->xscale->text_scale_off = $aOff;
2518 // Get Y min and max values for added lines
2519 function GetLinesYMinMax( $aLines ) {
2520 $n = count($aLines);
2521 if( $n == 0 ) return false;
2522 $min = $aLines[0]->scaleposition ;
2525 for( $i=0; $i < $n; ++$i ) {
2526 if( $aLines[$i]->direction == HORIZONTAL ) {
2528 $v = $aLines[$i]->scaleposition ;
2529 if( $min > $v ) $min = $v ;
2530 if( $max < $v ) $max = $v ;
2533 return $flg ? array($min,$max) : false ;
2536 // Get X min and max values for added lines
2537 function GetLinesXMinMax( $aLines ) {
2538 $n = count($aLines);
2539 if( $n == 0 ) return false ;
2540 $min = $aLines[0]->scaleposition ;
2543 for( $i=0; $i < $n; ++$i ) {
2544 if( $aLines[$i]->direction == VERTICAL ) {
2546 $v = $aLines[$i]->scaleposition ;
2547 if( $min > $v ) $min = $v ;
2548 if( $max < $v ) $max = $v ;
2551 return $flg ? array($min,$max) : false ;
2554 // Get min and max values for all included plots
2555 function GetPlotsYMinMax(&$aPlots) {
2556 list($xmax,$max) = $aPlots[0]->Max();
2557 list($xmin,$min) = $aPlots[0]->Min();
2558 for($i=0; $i<count($aPlots); ++$i ) {
2559 list($xmax,$ymax)=$aPlots[$i]->Max();
2560 list($xmin,$ymin)=$aPlots[$i]->Min();
2561 if (!is_string($ymax) || $ymax != "") $max=max($max,$ymax);
2562 if (!is_string($ymin) || $ymin != "") $min=min($min,$ymin);
2564 if( $min == "" ) $min = 0;
2565 if( $max == "" ) $max = 0;
2566 if( $min == 0 && $max == 0 ) {
2567 // Special case if all values are 0
2570 return array($min,$max);
2576 //===================================================
2578 // Description: Handle TTF font names
2579 //===================================================
2581 var $font_files,$style_names;
2585 $this->style_names=array(FS_NORMAL=>'normal',FS_BOLD=>'bold',FS_ITALIC=>'italic',FS_BOLDITALIC=>'bolditalic');
2586 // File names for available fonts
2587 $this->font_files=array(
2588 FF_COURIER => array(FS_NORMAL=>'cour.ttf', FS_BOLD=>'courbd.ttf', FS_ITALIC=>'couri.ttf', FS_BOLDITALIC=>'courbi.ttf' ),
2589 FF_GEORGIA => array(FS_NORMAL=>'georgia.ttf', FS_BOLD=>'georgiab.ttf', FS_ITALIC=>'georgiai.ttf', FS_BOLDITALIC=>'' ),
2590 FF_TREBUCHE =>array(FS_NORMAL=>'trebuc.ttf', FS_BOLD=>'trebucbd.ttf', FS_ITALIC=>'trebucit.ttf', FS_BOLDITALIC=>'trebucbi.ttf' ),
2591 FF_VERDANA => array(FS_NORMAL=>'verdana.ttf', FS_BOLD=>'verdanab.ttf', FS_ITALIC=>'verdanai.ttf', FS_BOLDITALIC=>'' ),
2592 FF_TIMES => array(FS_NORMAL=>'times.ttf', FS_BOLD=>'timesbd.ttf', FS_ITALIC=>'timesi.ttf', FS_BOLDITALIC=>'timesbi.ttf' ),
2593 FF_COMIC => array(FS_NORMAL=>'comic.ttf', FS_BOLD=>'comicbd.ttf', FS_ITALIC=>'.ttf', FS_BOLDITALIC=>'' ),
2594 FF_ARIAL => array(FS_NORMAL=>'arial.ttf', FS_BOLD=>'arialbd.ttf', FS_ITALIC=>'ariali.ttf', FS_BOLDITALIC=>'arialbi.ttf' ) ,
2595 FF_SIMSUN => array(FS_NORMAL=>'simsun.ttc', FS_BOLD=>'simhei.ttf', FS_ITALIC=>'', FS_BOLDITALIC=>'' ),
2596 FF_VERA => array(FS_NORMAL=>'Vera.ttf', FS_BOLD=>'VeraBd.ttf', FS_ITALIC=>'VeraIt.ttf', FS_BOLDITALIC=>'VeraBI.ttf' ),
2597 FF_VERAMONO => array(FS_NORMAL=>'VeraMono.ttf', FS_BOLD=>'VeraMoBd.ttf', FS_ITALIC=>'VeraMoIt.ttf', FS_BOLDITALIC=>'VeraMoBI.ttf' ),
2598 FF_VERASERIF => array(FS_NORMAL=>'VeraSe.ttf', FS_BOLD=>'VeraSeBd.ttf', FS_ITALIC=>'', FS_BOLDITALIC=>'' )
2604 // Create the TTF file from the font specification
2605 function File($family,$style=FS_NORMAL) {
2607 if( $family == FF_HANDWRT || $family==FF_BOOK ) {
2608 JpGraphError::Raise('Font families FF_HANDWRT and FF_BOOK are no longer available due to copyright problem with these fonts. Fonts can no longer be distributed with JpGraph. Please download fonts from http://corefonts.sourceforge.net/');
2611 $fam = @$this->font_files[$family];
2613 JpGraphError::Raise(
2614 "Specified TTF font family (id=$family) is unknown or does not exist. ".
2615 "Please note that TTF fonts are not distributed with JpGraph for copyright reasons.".
2616 " You can find the MS TTF WEB-fonts (arial, courier etc) for download at ".
2617 " http://corefonts.sourceforge.net/");
2622 JpGraphError::Raise('Style "'.$this->style_names[$style].'" is not available for font family '.$this->font_files[$family][FS_NORMAL].'.');
2624 JpGraphError::Raise("Unknown font style specification [$fam].");
2626 if( file_exists($f) === false || is_readable($f) === false ) {
2627 JpGraphError::Raise("Font file \"$f\" is not readable or does not exist.");
2633 //===================================================
2634 // CLASS LineProperty
2635 // Description: Holds properties for a line
2636 //===================================================
2637 class LineProperty {
2638 var $iWeight=1, $iColor="black",$iStyle="solid";
2643 function SetColor($aColor) {
2644 $this->iColor = $aColor;
2647 function SetWeight($aWeight) {
2648 $this->iWeight = $aWeight;
2651 function SetStyle($aStyle) {
2652 $this->iStyle = $aStyle;
2655 function Show($aShow=true) {
2656 $this->iShow=$aShow;
2659 function Stroke($aImg,$aX1,$aY1,$aX2,$aY2) {
2660 if( $this->iShow ) {
2661 $aImg->SetColor($this->iColor);
2662 $aImg->SetLineWeight($this->iWeight);
2663 $aImg->SetLineStyle($this->iStyle);
2664 $aImg->StyleLine($aX1,$aY1,$aX2,$aY2);
2670 //===================================================
2672 // Description: Arbitrary text object that can be added to the graph
2673 //===================================================
2675 var $t,$x=0,$y=0,$halign="left",$valign="top",$color=array(0,0,0);
2676 var $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12;
2677 var $hide=false, $dir=0;
2678 var $boxed=false; // Should the text be boxed
2679 var $paragraph_align="left";
2681 var $icornerradius=0,$ishadowwidth=3;
2682 var $iScalePosY=null,$iScalePosX=null;
2687 // Create new text at absolute pixel coordinates
2688 function Text($aTxt="",$aXAbsPos=0,$aYAbsPos=0) {
2690 $this->x = round($aXAbsPos);
2691 $this->y = round($aYAbsPos);
2696 // Set the string in the text object
2697 function Set($aTxt) {
2702 function SetPos($aXAbsPos=0,$aYAbsPos=0,$aHAlign="left",$aVAlign="top") {
2703 $this->Pos($aXAbsPos,$aYAbsPos,$aHAlign,$aVAlign);
2706 // Specify the position and alignment for the text object
2707 function Pos($aXAbsPos=0,$aYAbsPos=0,$aHAlign="left",$aVAlign="top") {
2708 $this->x = $aXAbsPos;
2709 $this->y = $aYAbsPos;
2710 $this->halign = $aHAlign;
2711 $this->valign = $aVAlign;
2714 function SetScalePos($aX,$aY) {
2715 $this->iScalePosX = $aX;
2716 $this->iScalePosY = $aY;
2719 // Specify alignment for the text
2720 function Align($aHAlign,$aVAlign="top",$aParagraphAlign="") {
2721 $this->halign = $aHAlign;
2722 $this->valign = $aVAlign;
2723 if( $aParagraphAlign != "" )
2724 $this->paragraph_align = $aParagraphAlign;
2728 function SetAlign($aHAlign,$aVAlign="top",$aParagraphAlign="") {
2729 $this->Align($aHAlign,$aVAlign,$aParagraphAlign);
2732 // Specifies the alignment for a multi line text
2733 function ParagraphAlign($aAlign) {
2734 $this->paragraph_align = $aAlign;
2737 function SetShadow($aShadowColor='gray',$aShadowWidth=3) {
2738 $this->ishadowwidth=$aShadowWidth;
2739 $this->shadow=$aShadowColor;
2743 // Specify that the text should be boxed. fcolor=frame color, bcolor=border color,
2744 // $shadow=drop shadow should be added around the text.
2745 function SetBox($aFrameColor=array(255,255,255),$aBorderColor=array(0,0,0),$aShadowColor=false,$aCornerRadius=4,$aShadowWidth=3) {
2746 if( $aFrameColor==false )
2750 $this->fcolor=$aFrameColor;
2751 $this->bcolor=$aBorderColor;
2752 // For backwards compatibility when shadow was just true or false
2753 if( $aShadowColor === true )
2754 $aShadowColor = 'gray';
2755 $this->shadow=$aShadowColor;
2756 $this->icornerradius=$aCornerRadius;
2757 $this->ishadowwidth=$aShadowWidth;
2761 function Hide($aHide=true) {
2765 // This looks ugly since it's not a very orthogonal design
2766 // but I added this "inverse" of Hide() to harmonize
2767 // with some classes which I designed more recently (especially)
2769 function Show($aShow=true) {
2770 $this->hide=!$aShow;
2774 function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
2775 $this->font_family=$aFamily;
2776 $this->font_style=$aStyle;
2777 $this->font_size=$aSize;
2780 // Center the text between $left and $right coordinates
2781 function Center($aLeft,$aRight,$aYAbsPos=false) {
2782 $this->x = $aLeft + ($aRight-$aLeft )/2;
2783 $this->halign = "center";
2784 if( is_numeric($aYAbsPos) )
2785 $this->y = $aYAbsPos;
2789 function SetColor($aColor) {
2790 $this->color = $aColor;
2793 function SetAngle($aAngle) {
2794 $this->SetOrientation($aAngle);
2797 // Orientation of text. Note only TTF fonts can have an arbitrary angle
2798 function SetOrientation($aDirection=0) {
2799 if( is_numeric($aDirection) )
2800 $this->dir=$aDirection;
2801 elseif( $aDirection=="h" )
2803 elseif( $aDirection=="v" )
2805 else JpGraphError::Raise(" Invalid direction specified for text.");
2808 // Total width of text
2809 function GetWidth($aImg) {
2810 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
2811 $w = $aImg->GetTextWidth($this->t,$this->dir);
2816 function GetFontHeight($aImg) {
2817 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
2818 $h = $aImg->GetFontHeight();
2823 function GetTextHeight($aImg) {
2824 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
2825 $h = $aImg->GetTextHeight($this->t,$this->dir);
2829 // Set the margin which will be interpretated differently depending
2831 function SetMargin($aMarg) {
2832 $this->margin = $aMarg;
2835 function StrokeWithScale($aImg,$axscale,$ayscale) {
2836 if( $this->iScalePosX === null ||
2837 $this->iScalePosY === null ) {
2838 $this->Stroke($aImg);
2841 $this->Stroke($aImg,
2842 round($axscale->Translate($this->iScalePosX)),
2843 round($ayscale->Translate($this->iScalePosY)));
2847 // Display text in image
2848 function Stroke($aImg,$x=null,$y=null) {
2850 if( !empty($x) ) $this->x = round($x);
2851 if( !empty($y) ) $this->y = round($y);
2853 // If position been given as a fraction of the image size
2854 // calculate the absolute position
2855 if( $this->x < 1 && $this->x > 0 ) $this->x *= $aImg->width;
2856 if( $this->y < 1 && $this->y > 0 ) $this->y *= $aImg->height;
2858 $aImg->PushColor($this->color);
2859 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
2860 $aImg->SetTextAlign($this->halign,$this->valign);
2861 if( $this->boxed ) {
2862 if( $this->fcolor=="nofill" )
2863 $this->fcolor=false;
2864 $aImg->SetLineWeight(1);
2865 $aImg->StrokeBoxedText($this->x,$this->y,$this->t,
2866 $this->dir,$this->fcolor,$this->bcolor,$this->shadow,
2867 $this->paragraph_align,6,2,$this->icornerradius,
2868 $this->ishadowwidth);
2871 $aImg->StrokeText($this->x,$this->y,$this->t,$this->dir,
2872 $this->paragraph_align);
2874 $aImg->PopColor($this->color);
2878 class GraphTabTitle extends Text{
2879 var $corner = 6 , $posx = 7, $posy = 4;
2880 var $color='darkred',$fillcolor='lightyellow',$bordercolor='black';
2881 var $align = 'left', $width=TABTITLE_WIDTHFIT;
2882 function GraphTabTitle() {
2884 $this->font_style = FS_BOLD;
2888 function SetColor($aTxtColor,$aFillColor='lightyellow',$aBorderColor='black') {
2889 $this->color = $aTxtColor;
2890 $this->fillcolor = $aFillColor;
2891 $this->bordercolor = $aBorderColor;
2894 function SetFillColor($aFillColor) {
2895 $this->fillcolor = $aFillColor;
2898 function SetTabAlign($aAlign) {
2899 // Synonym for SetPos
2900 $this->align = $aAlign;
2903 function SetPos($aAlign) {
2904 $this->align = $aAlign;
2907 function SetWidth($aWidth) {
2908 $this->width = $aWidth ;
2913 $this->hide = false;
2916 function SetCorner($aD) {
2917 $this->corner = $aD ;
2920 function Stroke($aImg) {
2923 $this->boxed = false;
2924 $w = $this->GetWidth($aImg) + 2*$this->posx;
2925 $h = $this->GetTextHeight($aImg) + 2*$this->posy;
2927 $x = $aImg->left_margin;
2928 $y = $aImg->top_margin;
2930 if( $this->width === TABTITLE_WIDTHFIT ) {
2931 if( $this->align == 'left' ) {
2933 $x, $y-$h+$this->corner,
2934 $x + $this->corner,$y-$h,
2935 $x + $w - $this->corner, $y-$h,
2936 $x + $w, $y-$h+$this->corner,
2939 elseif( $this->align == 'center' ) {
2940 $x += round($aImg->plotwidth/2) - round($w/2);
2942 $x, $y-$h+$this->corner,
2943 $x + $this->corner, $y-$h,
2944 $x + $w - $this->corner, $y-$h,
2945 $x + $w, $y-$h+$this->corner,
2949 $x += $aImg->plotwidth -$w;
2951 $x, $y-$h+$this->corner,
2952 $x + $this->corner,$y-$h,
2953 $x + $w - $this->corner, $y-$h,
2954 $x + $w, $y-$h+$this->corner,
2959 if( $this->width === TABTITLE_WIDTHFULL )
2960 $w = $aImg->plotwidth ;
2964 // Make the tab fit the width of the plot area
2966 $x, $y-$h+$this->corner,
2967 $x + $this->corner,$y-$h,
2968 $x + $w - $this->corner, $y-$h,
2969 $x + $w, $y-$h+$this->corner,
2973 $aImg->SetTextAlign('left','bottom');
2977 $aImg->SetColor($this->fillcolor);
2978 $aImg->FilledPolygon($p);
2980 $aImg->SetColor($this->bordercolor);
2981 $aImg->Polygon($p,true);
2983 $aImg->SetColor($this->color);
2984 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
2985 $aImg->StrokeText($x,$y,$this->t,0,'center');
2990 //===================================================
2991 // CLASS SuperScriptText
2992 // Description: Format a superscript text
2993 //===================================================
2994 class SuperScriptText extends Text {
2996 var $sfont_family="",$sfont_style="",$sfont_size=8;
2997 var $iSuperMargin=2,$iVertOverlap=4,$iSuperScale=0.65;
3001 function SuperScriptText($aTxt="",$aSuper="",$aXAbsPos=0,$aYAbsPos=0) {
3002 parent::Text($aTxt,$aXAbsPos,$aYAbsPos);
3003 $this->iSuper = $aSuper;
3006 function FromReal($aVal,$aPrecision=2) {
3007 // Convert a floating point number to scientific notation
3014 $l = floor(log10($aVal));
3015 $a = sprintf("%0.".$aPrecision."f",round($aVal / pow(10,$l),$aPrecision));
3017 if( $this->iSimple && ($a == 1 || $a==-1) ) $a = '';
3020 $this->t = $a.' * 10';
3030 function Set($aTxt,$aSuper="") {
3032 $this->iSuper = $aSuper;
3035 function SetSuperFont($aFontFam,$aFontStyle=FS_NORMAL,$aFontSize=8) {
3036 $this->sfont_family = $aFontFam;
3037 $this->sfont_style = $aFontStyle;
3038 $this->sfont_size = $aFontSize;
3041 // Total width of text
3042 function GetWidth(&$aImg) {
3043 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
3044 $w = $aImg->GetTextWidth($this->t);
3045 $aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size);
3046 $w += $aImg->GetTextWidth($this->iSuper);
3047 $w += $this->iSuperMargin;
3051 // Hight of font (approximate the height of the text)
3052 function GetFontHeight(&$aImg) {
3053 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
3054 $h = $aImg->GetFontHeight();
3055 $aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size);
3056 $h += $aImg->GetFontHeight();
3061 function GetTextHeight(&$aImg) {
3062 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
3063 $h = $aImg->GetTextHeight($this->t);
3064 $aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size);
3065 $h += $aImg->GetTextHeight($this->iSuper);
3069 function Stroke($aImg,$ax=-1,$ay=-1) {
3071 // To position the super script correctly we need different
3072 // cases to handle the alignmewnt specified since that will
3073 // determine how we can interpret the x,y coordinates
3075 $w = parent::GetWidth($aImg);
3076 $h = parent::GetTextHeight($aImg);
3077 switch( $this->valign ) {
3082 $sy = $this->y - $h/2;
3085 $sy = $this->y - $h;
3088 JpGraphError::Raise('PANIC: Internal error in SuperScript::Stroke(). Unknown vertical alignment for text');
3092 switch( $this->halign ) {
3094 $sx = $this->x + $w;
3097 $sx = $this->x + $w/2;
3103 JpGraphError::Raise('PANIC: Internal error in SuperScript::Stroke(). Unknown horizontal alignment for text');
3107 $sx += $this->iSuperMargin;
3108 $sy += $this->iVertOverlap;
3110 // Should we automatically determine the font or
3111 // has the user specified it explicetly?
3112 if( $this->sfont_family == "" ) {
3113 if( $this->font_family <= FF_FONT2 ) {
3114 if( $this->font_family == FF_FONT0 ) {
3117 elseif( $this->font_family == FF_FONT1 ) {
3118 if( $this->font_style == FS_NORMAL )
3126 $sfs = $this->font_style;
3127 $sfz = $this->font_size;
3131 $sff = $this->font_family;
3132 $sfs = $this->font_style;
3133 $sfz = floor($this->font_size*$this->iSuperScale);
3134 if( $sfz < 8 ) $sfz = 8;
3136 $this->sfont_family = $sff;
3137 $this->sfont_style = $sfs;
3138 $this->sfont_size = $sfz;
3141 $sff = $this->sfont_family;
3142 $sfs = $this->sfont_style;
3143 $sfz = $this->sfont_size;
3146 parent::Stroke($aImg,$ax,$ay);
3149 // For the builtin fonts we need to reduce the margins
3150 // since the bounding bx reported for the builtin fonts
3151 // are much larger than for the TTF fonts.
3152 if( $sff <= FF_FONT2 ) {
3157 $aImg->SetTextAlign('left','bottom');
3158 $aImg->SetFont($sff,$sfs,$sfz);
3159 $aImg->PushColor($this->color);
3160 $aImg->StrokeText($sx,$sy,$this->iSuper,$this->iSDir,'left');
3166 //===================================================
3168 // Description: responsible for drawing grid lines in graph
3169 //===================================================
3173 var $grid_color='#DDDDDD',$grid_mincolor='#DDDDDD';
3175 var $show=false, $showMinor=false,$weight=1;
3176 var $fill=false,$fillcolor=array('#EFEFEF','#BBCCFF');
3179 function Grid(&$aAxis) {
3180 $this->scale = &$aAxis->scale;
3181 $this->img = &$aAxis->img;
3185 function SetColor($aMajColor,$aMinColor=false) {
3186 $this->grid_color=$aMajColor;
3187 if( $aMinColor === false )
3188 $aMinColor = $aMajColor ;
3189 $this->grid_mincolor = $aMinColor;
3192 function SetWeight($aWeight) {
3193 $this->weight=$aWeight;
3196 // Specify if grid should be dashed, dotted or solid
3197 function SetLineStyle($aType) {
3198 $this->type = $aType;
3201 // Decide if both major and minor grid should be displayed
3202 function Show($aShowMajor=true,$aShowMinor=false) {
3203 $this->show=$aShowMajor;
3204 $this->showMinor=$aShowMinor;
3207 function SetFill($aFlg=true,$aColor1='lightgray',$aColor2='lightblue') {
3208 $this->fill = $aFlg;
3209 $this->fillcolor = array( $aColor1, $aColor2 );
3214 if( $this->showMinor ) {
3215 $tmp = $this->grid_color;
3216 $this->grid_color = $this->grid_mincolor;
3217 $this->DoStroke($this->scale->ticks->ticks_pos);
3219 $this->grid_color = $tmp;
3220 $this->DoStroke($this->scale->ticks->maj_ticks_pos);
3223 $this->DoStroke($this->scale->ticks->maj_ticks_pos);
3230 function DoStroke(&$aTicksPos) {
3233 $nbrgrids = count($aTicksPos);
3235 if( $this->scale->type=="y" ) {
3236 $xl=$this->img->left_margin;
3237 $xr=$this->img->width-$this->img->right_margin;
3240 // Draw filled areas
3241 $y2 = $aTicksPos[0];
3243 while( $i < $nbrgrids ) {
3245 $y2 = $aTicksPos[$i++];
3246 $this->img->SetColor($this->fillcolor[$i & 1]);
3247 $this->img->FilledRectangle($xl,$y1,$xr,$y2);
3251 $this->img->SetColor($this->grid_color);
3252 $this->img->SetLineWeight($this->weight);
3255 for($i=0; $i<$nbrgrids; ++$i) {
3257 if( $this->type == "solid" )
3258 $this->img->Line($xl,$y,$xr,$y);
3259 elseif( $this->type == "dotted" )
3260 $this->img->DashedLine($xl,$y,$xr,$y,1,6);
3261 elseif( $this->type == "dashed" )
3262 $this->img->DashedLine($xl,$y,$xr,$y,2,4);
3263 elseif( $this->type == "longdashed" )
3264 $this->img->DashedLine($xl,$y,$xr,$y,8,6);
3267 elseif( $this->scale->type=="x" ) {
3268 $yu=$this->img->top_margin;
3269 $yl=$this->img->height-$this->img->bottom_margin;
3270 $limit=$this->img->width-$this->img->right_margin;
3273 // Draw filled areas
3274 $x2 = $aTicksPos[0];
3276 while( $i < $nbrgrids ) {
3278 $x2 = min($aTicksPos[$i++],$limit) ;
3279 $this->img->SetColor($this->fillcolor[$i & 1]);
3280 $this->img->FilledRectangle($x1,$yu,$x2,$yl);
3284 $this->img->SetColor($this->grid_color);
3285 $this->img->SetLineWeight($this->weight);
3287 // We must also test for limit since we might have
3288 // an offset and the number of ticks is calculated with
3289 // assumption offset==0 so we might end up drawing one
3290 // to many gridlines
3293 while( $i<count($aTicksPos) && ($x=$aTicksPos[$i]) <= $limit ) {
3294 if( $this->type == "solid" )
3295 $this->img->Line($x,$yl,$x,$yu);
3296 elseif( $this->type == "dotted" )
3297 $this->img->DashedLine($x,$yl,$x,$yu,1,6);
3298 elseif( $this->type == "dashed" )
3299 $this->img->DashedLine($x,$yl,$x,$yu,2,4);
3300 elseif( $this->type == "longdashed" )
3301 $this->img->DashedLine($x,$yl,$x,$yu,8,6);
3306 JpGraphError::Raise('Internal error: Unknown grid axis ['.$this->scale->type.']');
3312 //===================================================
3314 // Description: Defines X and Y axis. Notes that at the
3315 // moment the code is not really good since the axis on
3316 // several occasion must know wheter it's an X or Y axis.
3317 // This was a design decision to make the code easier to
3319 //===================================================
3323 var $color=array(0,0,0),$label_color=array(0,0,0);
3324 var $img=null,$scale=null;
3326 var $ticks_label=false, $ticks_label_colors=null;
3327 var $show_first_label=true,$show_last_label=true;
3328 var $label_step=1; // Used by a text axis to specify what multiple of major steps
3329 // should be labeled.
3331 var $labelPos=0; // Which side of the axis should the labels be?
3332 var $title=null,$title_adjust,$title_margin,$title_side=SIDE_LEFT;
3333 var $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12,$label_angle=0;
3334 var $tick_label_margin=5;
3335 var $label_halign = '',$label_valign = '', $label_para_align='left';
3336 var $hide_line=false,$hide_labels=false;
3337 //var $hide_zero_label=false;
3341 function Axis(&$img,&$aScale,$color=array(0,0,0)) {
3343 $this->scale = &$aScale;
3344 $this->color = $color;
3345 $this->title=new Text("");
3347 if( $aScale->type=="y" ) {
3348 $this->title_margin = 25;
3349 $this->title_adjust="middle";
3350 $this->title->SetOrientation(90);
3351 $this->tick_label_margin=7;
3352 $this->labelPos=SIDE_LEFT;
3353 //$this->SetLabelFormat('%.1f');
3356 $this->title_margin = 5;
3357 $this->title_adjust="high";
3358 $this->title->SetOrientation(0);
3359 $this->tick_label_margin=3;
3360 $this->labelPos=SIDE_DOWN;
3361 //$this->SetLabelFormat('%.0f');
3367 function SetLabelFormat($aFormStr) {
3368 $this->scale->ticks->SetLabelFormat($aFormStr);
3371 function SetLabelFormatString($aFormStr) {
3372 $this->scale->ticks->SetLabelFormat($aFormStr);
3375 function SetLabelFormatCallback($aFuncName) {
3376 $this->scale->ticks->SetFormatCallback($aFuncName);
3379 function SetLabelAlign($aHAlign,$aVAlign="top",$aParagraphAlign='left') {
3380 $this->label_halign = $aHAlign;
3381 $this->label_valign = $aVAlign;
3382 $this->label_para_align = $aParagraphAlign;
3385 // Don't display the first label
3386 function HideFirstTickLabel($aShow=false) {
3387 $this->show_first_label=$aShow;
3390 function HideLastTickLabel($aShow=false) {
3391 $this->show_last_label=$aShow;
3394 function HideTicks($aHideMinor=true,$aHideMajor=true) {
3395 $this->scale->ticks->SupressMinorTickMarks($aHideMinor);
3396 $this->scale->ticks->SupressTickMarks($aHideMajor);
3400 function HideZeroLabel($aFlag=true) {
3401 $this->scale->ticks->SupressZeroLabel();
3402 //$this->hide_zero_label = $aFlag;
3405 function HideFirstLastLabel() {
3406 // The two first calls to ticks method will supress
3407 // automatically generated scale values. However, that
3408 // will not affect manually specified value, e.g text-scales.
3409 // therefor we also make a kludge here to supress manually
3410 // specified scale labels.
3411 $this->scale->ticks->SupressLast();
3412 $this->scale->ticks->SupressFirst();
3413 $this->show_first_label = false;
3414 $this->show_last_label = false;
3418 function Hide($aHide=true) {
3422 // Hide the actual axis-line, but still print the labels
3423 function HideLine($aHide=true) {
3424 $this->hide_line = $aHide;
3427 function HideLabels($aHide=true) {
3428 $this->hide_labels = $aHide;
3433 function SetWeight($aWeight) {
3434 $this->weight = $aWeight;
3438 function SetColor($aColor,$aLabelColor=false) {
3439 $this->color = $aColor;
3440 if( !$aLabelColor ) $this->label_color = $aColor;
3441 else $this->label_color = $aLabelColor;
3445 function SetTitle($aTitle,$aAdjustAlign="high") {
3446 $this->title->Set($aTitle);
3447 $this->title_adjust=$aAdjustAlign;
3450 // Specify distance from the axis
3451 function SetTitleMargin($aMargin) {
3452 $this->title_margin=$aMargin;
3455 // Which side of the axis should the axis title be?
3456 function SetTitleSide($aSideOfAxis) {
3457 $this->title_side = $aSideOfAxis;
3460 // Utility function to set the direction for tick marks
3461 function SetTickDirection($aDir) {
3462 // Will be deprecated from 1.7
3463 if( ERR_DEPRECATED )
3464 JpGraphError::Raise('Axis::SetTickDirection() is deprecated. Use Axis::SetTickSide() instead');
3465 $this->scale->ticks->SetSide($aDir);
3468 function SetTickSide($aDir) {
3469 $this->scale->ticks->SetSide($aDir);
3472 // Specify text labels for the ticks. One label for each data point
3473 function SetTickLabels($aLabelArray,$aLabelColorArray=null) {
3474 $this->ticks_label = $aLabelArray;
3475 $this->ticks_label_colors = $aLabelColorArray;
3478 // How far from the axis should the labels be drawn
3479 function SetTickLabelMargin($aMargin) {
3480 if( ERR_DEPRECATED )
3481 JpGraphError::Raise('SetTickLabelMargin() is deprecated. Use Axis::SetLabelMargin() instead.');
3482 $this->tick_label_margin=$aMargin;
3485 function SetLabelMargin($aMargin) {
3486 $this->tick_label_margin=$aMargin;
3489 // Specify that every $step of the ticks should be displayed starting
3491 // DEPRECATED FUNCTION: USE SetTextTickInterval() INSTEAD
3492 function SetTextTicks($step,$start=0) {
3493 JpGraphError::Raise(" SetTextTicks() is deprecated. Use SetTextTickInterval() instead.");
3496 // Specify that every $step of the ticks should be displayed starting
3498 function SetTextTickInterval($aStep,$aStart=0) {
3499 $this->scale->ticks->SetTextLabelStart($aStart);
3500 $this->tick_step=$aStep;
3503 // Specify that every $step tick mark should have a label
3504 // should be displayed starting
3505 function SetTextLabelInterval($aStep) {
3507 JpGraphError::Raise(" Text label interval must be specified >= 1.");
3508 $this->label_step=$aStep;
3511 // Which side of the axis should the labels be on?
3512 function SetLabelPos($aSidePos) {
3513 // This will be deprecated from 1.7
3514 if( ERR_DEPRECATED )
3515 JpGraphError::Raise('SetLabelPos() is deprecated. Use Axis::SetLabelSide() instead.');
3516 $this->labelPos=$aSidePos;
3519 function SetLabelSide($aSidePos) {
3520 $this->labelPos=$aSidePos;
3524 function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
3525 $this->font_family = $aFamily;
3526 $this->font_style = $aStyle;
3527 $this->font_size = $aSize;
3530 // Position for axis line on the "other" scale
3531 function SetPos($aPosOnOtherScale) {
3532 $this->pos=$aPosOnOtherScale;
3535 // Specify the angle for the tick labels
3536 function SetLabelAngle($aAngle) {
3537 $this->label_angle = $aAngle;
3541 function Stroke($aOtherAxisScale) {
3542 if( $this->hide ) return;
3543 if( is_numeric($this->pos) ) {
3544 $pos=$aOtherAxisScale->Translate($this->pos);
3546 else { // Default to minimum of other scale if pos not set
3547 if( ($aOtherAxisScale->GetMinVal() >= 0 && $this->pos==false) || $this->pos=="min" ) {
3548 $pos = $aOtherAxisScale->scale_abs[0];
3550 elseif($this->pos == "max") {
3551 $pos = $aOtherAxisScale->scale_abs[1];
3553 else { // If negative set x-axis at 0
3555 $pos=$aOtherAxisScale->Translate(0);
3558 $this->img->SetLineWeight($this->weight);
3559 $this->img->SetColor($this->color);
3560 $this->img->SetFont($this->font_family,$this->font_style,$this->font_size);
3561 if( $this->scale->type == "x" ) {
3562 if( !$this->hide_line )
3563 $this->img->FilledRectangle($this->img->left_margin,$pos,
3564 $this->img->width-$this->img->right_margin,$pos+$this->weight-1);
3565 $y=$pos+$this->img->GetFontHeight()+$this->title_margin+$this->title->margin;
3566 if( $this->title_adjust=="high" )
3567 $this->title->Pos($this->img->width-$this->img->right_margin,$y,"right","top");
3568 elseif( $this->title_adjust=="middle" || $this->title_adjust=="center" )
3569 $this->title->Pos(($this->img->width-$this->img->left_margin-$this->img->right_margin)/2+$this->img->left_margin,$y,"center","top");
3570 elseif($this->title_adjust=="low")
3571 $this->title->Pos($this->img->left_margin,$y,"left","top");
3573 JpGraphError::Raise('Unknown alignment specified for X-axis title. ('.$this->title_adjust.')');
3576 elseif( $this->scale->type == "y" ) {
3577 // Add line weight to the height of the axis since
3578 // the x-axis could have a width>1 and we want the axis to fit nicely together.
3579 if( !$this->hide_line )
3580 $this->img->FilledRectangle($pos-$this->weight+1,$this->img->top_margin,
3581 $pos,$this->img->height-$this->img->bottom_margin+$this->weight-1);
3583 if( $this->title_side == SIDE_LEFT ) {
3584 $x -= $this->title_margin;
3585 $x -= $this->title->margin;
3589 $x += $this->title_margin;
3590 $x += $this->title->margin;
3593 // If the user has manually specified an hor. align
3594 // then we override the automatic settings with this
3595 // specifed setting. Since default is 'left' we compare
3596 // with that. (This means a manually set 'left' align
3597 // will have no effect.)
3598 if( $this->title->halign != 'left' )
3599 $halign = $this->title->halign;
3600 if( $this->title_adjust=="high" )
3601 $this->title->Pos($x,$this->img->top_margin,$halign,"top");
3602 elseif($this->title_adjust=="middle" || $this->title_adjust=="center")
3603 $this->title->Pos($x,($this->img->height-$this->img->top_margin-$this->img->bottom_margin)/2+$this->img->top_margin,$halign,"center");
3604 elseif($this->title_adjust=="low")
3605 $this->title->Pos($x,$this->img->height-$this->img->bottom_margin,$halign,"bottom");
3607 JpGraphError::Raise('Unknown alignment specified for Y-axis title. ('.$this->title_adjust.')');
3610 $this->scale->ticks->Stroke($this->img,$this->scale,$pos);
3611 if( !$this->hide_labels ) {
3612 $this->StrokeLabels($pos);
3614 $this->title->Stroke($this->img);
3619 // Draw all the tick labels on major tick marks
3620 function StrokeLabels($aPos,$aMinor=false,$aAbsLabel=false) {
3622 $this->img->SetColor($this->label_color);
3623 $this->img->SetFont($this->font_family,$this->font_style,$this->font_size);
3624 $yoff=$this->img->GetFontHeight()/2;
3626 // Only draw labels at major tick marks
3627 $nbr = count($this->scale->ticks->maj_ticks_label);
3629 // We have the option to not-display the very first mark
3630 // (Usefull when the first label might interfere with another
3632 $i = $this->show_first_label ? 0 : 1 ;
3633 if( !$this->show_last_label ) --$nbr;
3634 // Now run through all labels making sure we don't overshoot the end
3637 if( isset($this->ticks_label_colors) )
3638 $ncolor=count($this->ticks_label_colors);
3641 // $tpos holds the absolute text position for the label
3642 $tpos=$this->scale->ticks->maj_ticklabels_pos[$i];
3644 // Note. the $limit is only used for the x axis since we
3645 // might otherwise overshoot if the scale has been centered
3646 // This is due to us "loosing" the last tick mark if we center.
3647 if( $this->scale->type=="x" && $tpos > $this->img->width-$this->img->right_margin+1 ) {
3650 // we only draw every $label_step label
3651 if( ($i % $this->label_step)==0 ) {
3653 // Set specific label color if specified
3655 $this->img->SetColor($this->ticks_label_colors[$i % $ncolor]);
3657 // If the label has been specified use that and in other case
3658 // just label the mark with the actual scale value
3659 $m=$this->scale->ticks->GetMajor();
3661 // ticks_label has an entry for each data point and is the array
3662 // that holds the labels set by the user. If the user hasn't
3663 // specified any values we use whats in the automatically asigned
3664 // labels in the maj_ticks_label
3665 if( isset($this->ticks_label[$i*$m]) )
3666 $label=$this->ticks_label[$i*$m];
3669 $label=abs($this->scale->ticks->maj_ticks_label[$i]);
3671 $label=$this->scale->ticks->maj_ticks_label[$i];
3672 if( $this->scale->textscale && $this->scale->ticks->label_formfunc == '' ) {
3677 //if( $this->hide_zero_label && $label==0.0 ) {
3682 if( $this->scale->type == "x" ) {
3683 if( $this->labelPos == SIDE_DOWN ) {
3684 if( $this->label_angle==0 || $this->label_angle==90 ) {
3685 if( $this->label_halign=='' && $this->label_valign=='')
3686 $this->img->SetTextAlign('center','top');
3688 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
3692 if( $this->label_halign=='' && $this->label_valign=='')
3693 $this->img->SetTextAlign("right","top");
3695 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
3698 $this->img->StrokeText($tpos,$aPos+$this->tick_label_margin,$label,
3699 $this->label_angle,$this->label_para_align);
3702 if( $this->label_angle==0 || $this->label_angle==90 ) {
3703 if( $this->label_halign=='' && $this->label_valign=='')
3704 $this->img->SetTextAlign("center","bottom");
3706 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
3709 if( $this->label_halign=='' && $this->label_valign=='')
3710 $this->img->SetTextAlign("right","bottom");
3712 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
3714 $this->img->StrokeText($tpos,$aPos-$this->tick_label_margin,$label,
3715 $this->label_angle,$this->label_para_align);
3719 // scale->type == "y"
3720 //if( $this->label_angle!=0 )
3721 //JpGraphError::Raise(" Labels at an angle are not supported on Y-axis");
3722 if( $this->labelPos == SIDE_LEFT ) { // To the left of y-axis
3723 if( $this->label_halign=='' && $this->label_valign=='')
3724 $this->img->SetTextAlign("right","center");
3726 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
3727 $this->img->StrokeText($aPos-$this->tick_label_margin,$tpos,$label,$this->label_angle,$this->label_para_align);
3729 else { // To the right of the y-axis
3730 if( $this->label_halign=='' && $this->label_valign=='')
3731 $this->img->SetTextAlign("left","center");
3733 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
3734 $this->img->StrokeText($aPos+$this->tick_label_margin,$tpos,$label,$this->label_angle,$this->label_para_align);
3744 //===================================================
3746 // Description: Abstract base class for drawing linear and logarithmic
3747 // tick marks on axis
3748 //===================================================
3750 var $minor_abs_size=3, $major_abs_size=5;
3751 var $direction=1; // Should ticks be in(=1) the plot area or outside (=-1)?
3755 var $supress_zerolabel=false,$supress_first=false;
3756 var $supress_last=false,$supress_tickmarks=false,$supress_minor_tickmarks=false;
3757 var $mincolor="",$majcolor="";
3759 var $label_formatstr=''; // C-style format string to use for labels
3760 var $label_formfunc='';
3765 function Ticks(&$aScale) {
3766 $this->scale=&$aScale;
3767 $this->precision = -1;
3772 // Set format string for automatic labels
3773 function SetLabelFormat($aFormatString) {
3774 $this->label_formatstr=$aFormatString;
3777 function SetFormatCallback($aCallbackFuncName) {
3778 $this->label_formfunc = $aCallbackFuncName;
3781 // Don't display the first zero label
3782 function SupressZeroLabel($aFlag=true) {
3783 $this->supress_zerolabel=$aFlag;
3786 // Don't display minor tick marks
3787 function SupressMinorTickMarks($aHide=true) {
3788 $this->supress_minor_tickmarks=$aHide;
3791 // Don't display major tick marks
3792 function SupressTickMarks($aHide=true) {
3793 $this->supress_tickmarks=$aHide;
3796 // Hide the first tick mark
3797 function SupressFirst($aHide=true) {
3798 $this->supress_first=$aHide;
3801 // Hide the last tick mark
3802 function SupressLast($aHide=true) {
3803 $this->supress_last=$aHide;
3806 // Size (in pixels) of minor tick marks
3807 function GetMinTickAbsSize() {
3808 return $this->minor_abs_size;
3811 // Size (in pixels) of major tick marks
3812 function GetMajTickAbsSize() {
3813 return $this->major_abs_size;
3816 function SetSize($aMajSize,$aMinSize=3) {
3817 $this->major_abs_size = $aMajSize;
3818 $this->minor_abs_size = $aMinSize;
3821 // Have the ticks been specified
3822 function IsSpecified() {
3823 return $this->is_set;
3826 // Set the distance between major and minor tick marks
3827 function Set($aMaj,$aMin) {
3829 // Should be implemented by the concrete subclass
3830 // if any action is wanted.
3833 // Specify number of decimals in automatic labels
3834 // Deprecated from 1.4. Use SetFormatString() instead
3835 function SetPrecision($aPrecision) {
3836 if( ERR_DEPRECATED )
3837 JpGraphError::Raise('Ticks::SetPrecision() is deprecated. Use Ticks::SetLabelFormat() (or Ticks::SetFormatCallback()) instead');
3838 $this->precision=$aPrecision;
3841 function SetSide($aSide) {
3842 $this->direction=$aSide;
3845 // Which side of the axis should the ticks be on
3846 function SetDirection($aSide=SIDE_RIGHT) {
3847 $this->direction=$aSide;
3850 // Set colors for major and minor tick marks
3851 function SetMarkColor($aMajorColor,$aMinorColor="") {
3852 $this->SetColor($aMajorColor,$aMinorColor);
3855 function SetColor($aMajorColor,$aMinorColor="") {
3856 $this->majcolor=$aMajorColor;
3858 // If not specified use same as major
3859 if( $aMinorColor=="" )
3860 $this->mincolor=$aMajorColor;
3862 $this->mincolor=$aMinorColor;
3865 function SetWeight($aWeight) {
3866 $this->weight=$aWeight;
3871 //===================================================
3872 // CLASS LinearTicks
3873 // Description: Draw linear ticks on axis
3874 //===================================================
3875 class LinearTicks extends Ticks {
3876 var $minor_step=1, $major_step=2;
3877 var $xlabel_offset=0,$xtick_offset=0;
3878 var $label_offset=0; // What offset should the displayed label have
3879 // i.e should we display 0,1,2 or 1,2,3,4 or 2,3,4 etc
3880 var $text_label_start=0;
3883 function LinearTicks() {
3884 $this->precision = -1;
3891 // Return major step size in world coordinates
3892 function GetMajor() {
3893 return $this->major_step;
3896 // Return minor step size in world coordinates
3897 function GetMinor() {
3898 return $this->minor_step;
3901 // Set Minor and Major ticks (in world coordinates)
3902 function Set($aMajStep,$aMinStep=false) {
3903 if( $aMinStep==false )
3904 $aMinStep=$aMajStep;
3906 if( $aMajStep <= 0 || $aMinStep <= 0 ) {
3907 JpGraphError::Raise(" Minor or major step size is 0. Check that you haven't
3908 got an accidental SetTextTicks(0) in your code.<p>
3909 If this is not the case you might have stumbled upon a bug in JpGraph.
3910 Please report this and if possible include the data that caused the
3914 $this->major_step=$aMajStep;
3915 $this->minor_step=$aMinStep;
3916 $this->is_set = true;
3919 // Draw linear ticks
3920 function Stroke(&$img,&$scale,$pos) {
3921 $maj_step_abs = $scale->scale_factor*$this->major_step;
3922 $min_step_abs = $scale->scale_factor*$this->minor_step;
3924 if( $min_step_abs==0 || $maj_step_abs==0 )
3925 JpGraphError::Raise(" 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. Try increasing the graph size or correct the lineplot.");
3926 $limit = $scale->scale_abs[1];
3927 $nbrmajticks=floor(1.000001*(($scale->GetMaxVal()-$scale->GetMinVal())/$this->major_step))+1;
3930 // If precision hasn't been specified set it to a sensible value
3931 if( $this->precision==-1 ) {
3932 $t = log10($this->minor_step);
3936 $precision = -floor($t);
3939 $precision = $this->precision;
3941 $img->SetLineWeight($this->weight);
3943 // Handle ticks on X-axis
3944 if( $scale->type == "x" ) {
3946 // Draw the minor tick marks
3948 $yu = $pos - $this->direction*$this->GetMinTickAbsSize();
3949 $label = $scale->GetMinVal();
3950 $x=$scale->scale_abs[0];
3953 $step = round($maj_step_abs/$min_step_abs);
3954 while( $x < $limit ) {
3955 $this->ticks_pos[]=$x;
3956 $this->ticks_label[]=$label;
3957 $label+=$this->minor_step;
3958 if( !$this->supress_tickmarks && !$this->supress_minor_tickmarks) {
3959 if( $this->mincolor!="" ) $img->PushColor($this->mincolor);
3960 $img->Line($x,$pos,$x,$yu);
3961 if( $this->mincolor!="" ) $img->PopColor();
3964 if( $i % $step == 0 ) {
3965 $this->maj_ticks_pos[$j]=round($x);//$xtick;
3970 $x += $min_step_abs;
3973 $this->maj_ticks_pos[$j]=$x;
3975 // Draw the major tick marks
3977 $yu = $pos - $this->direction*$this->GetMajTickAbsSize();
3979 // TODO: Add logic to set label_offset for text labels
3980 $label = (float)$scale->GetMinVal()+$this->text_label_start+$this->label_offset;
3982 $start_abs=$scale->scale_factor*$this->text_label_start;
3984 $nbrmajticks=ceil(($scale->GetMaxVal()-$scale->GetMinVal()-$this->text_label_start )/$this->major_step)+1;
3987 $x = $scale->scale_abs[0]+$start_abs+$this->xlabel_offset*$min_step_abs;
3988 for( $i=0; $label <= $scale->GetMaxVal()+$this->label_offset; ++$i ) {
3991 if( $this->label_formfunc != "" ) {
3992 $f=$this->label_formfunc;
3993 $l = call_user_func($f,$label);
3995 elseif( $this->label_formatstr != '' )
3996 $l = sprintf($this->label_formatstr,$label);
3998 $v = round($label,$precision);
3999 $l = sprintf("%01.".$precision."f",$v);
4002 if( ($this->supress_zerolabel && $l==0) ||
4003 ($this->supress_first && $i==0) ||
4004 ($this->supress_last && $i==$nbrmajticks-1) ) {
4008 $this->maj_ticks_label[$i]=$l;
4009 $label+=$this->major_step;
4010 $this->maj_ticklabels_pos[$i] = $x;
4011 // $this->maj_ticklabels_pos[$i] = $this->maj_ticks_pos[$i];
4013 // The x-position of the tick marks can be different from the labels.
4014 // Note that we record the tick position (not the label) so that the grid
4015 // happen upon tick marks and not labels.
4016 $xtick=$scale->scale_abs[0]+$start_abs+$this->xtick_offset*$min_step_abs+$i*$maj_step_abs;
4017 $this->maj_ticks_pos[$i]=$xtick;
4018 if(!($this->xtick_offset > 0 && $i==$nbrmajticks-1) &&
4019 !$this->supress_tickmarks) {
4020 if( $this->majcolor!="" ) $img->PushColor($this->majcolor);
4021 $img->Line($this->maj_ticks_pos[$i],$pos,$this->maj_ticks_pos[$i],$yu);
4022 if( $this->majcolor!="" ) $img->PopColor();
4025 $x += $maj_step_abs;
4030 elseif( $scale->type == "y" ) {
4032 // Draw the major tick marks
4033 $xr = $pos + $this->direction*$this->GetMajTickAbsSize();
4034 $label = $scale->GetMinVal();
4039 for( $i=0; $i<$nbrmajticks; ++$i) {
4040 $y=$scale->scale_abs[0]+$i*$maj_step_abs;
4045 // THe following two lines might seem to be unecessary but they are not!
4046 // The reason being that for X-axis we separate the position of the labels
4047 // and the tick marks which we don't do for the Y-axis.
4048 // We therefore need to make sure both arrays are corcectly filled
4049 // since Axis::StrokeLabels() uses the label positions and Grid::Stroke() uses
4050 // the tick positions.
4051 $this->maj_ticklabels_pos[$i]=$y;
4052 $this->maj_ticks_pos[$i]=$y;
4054 if( $this->label_formfunc != "" ) {
4055 $f=$this->label_formfunc;
4056 $l = call_user_func($f,$label);
4058 elseif( $this->label_formatstr != "" )
4059 $l = sprintf($this->label_formatstr,$label);
4061 $l = sprintf("%01.".$precision."f",round($label,$precision));
4063 if( ($this->supress_zerolabel && ($l + 0)==0) || ($this->supress_first && $i==0) ||
4064 ($this->supress_last && $i==$nbrmajticks-1) ) {
4068 $this->maj_ticks_label[$i]=$l;
4069 $label+=$this->major_step;
4070 if( !$this->supress_tickmarks ) {
4071 if( $this->majcolor!="" ) $img->PushColor($this->majcolor);
4072 $img->Line($pos,$y,$xr,$y);
4073 if( $this->majcolor!="" ) $img->PopColor();
4077 // Draw the minor tick marks
4078 $xr = $pos + $this->direction*$this->GetMinTickAbsSize();
4079 $label = $scale->GetMinVal();
4080 for( $i=0,$y=$scale->scale_abs[0]; $y>=$limit; ) {
4084 $this->ticks_pos[$i]=$y;
4085 $this->ticks_label[$i]=$label;
4086 $label+=$this->minor_step;
4087 if( !$this->supress_tickmarks && !$this->supress_minor_tickmarks) {
4088 if( $this->mincolor!="" ) $img->PushColor($this->mincolor);
4089 $img->Line($pos,$y,$xr,$y);
4090 if( $this->mincolor!="" ) $img->PopColor();
4093 $y=$scale->scale_abs[0]+$i*$min_step_abs;
4099 // Spoecify the offset of the displayed tick mark with the tick "space"
4100 // Legal values for $o is [0,1] used to adjust where the tick marks and label
4101 // should be positioned within the major tick-size
4102 // $lo specifies the label offset and $to specifies the tick offset
4103 // this comes in handy for example in bar graphs where we wont no offset for the
4104 // tick but have the labels displayed halfway under the bars.
4105 function SetXLabelOffset($aLabelOff,$aTickOff=-1) {
4106 $this->xlabel_offset=$aLabelOff;
4107 if( $aTickOff==-1 ) // Same as label offset
4108 $this->xtick_offset=$aLabelOff;
4110 $this->xtick_offset=$aTickOff;
4112 $this->SupressLast(); // The last tick wont fit
4115 // Which tick label should we start with?
4116 function SetTextLabelStart($aTextLabelOff) {
4117 $this->text_label_start=$aTextLabelOff;
4122 //===================================================
4123 // CLASS LinearScale
4124 // Description: Handle linear scaling between screen and world
4125 //===================================================
4127 var $scale=array(0,0);
4128 var $scale_abs=array(0,0);
4129 var $scale_factor; // Scale factor between world and screen
4130 var $world_size; // Plot area size in world coordinates
4131 var $world_abs_size; // Plot area size in pixels
4132 var $off; // Offset between image edge and plot area
4133 var $type; // is this x or y scale ?
4134 var $ticks=null; // Store ticks
4135 var $autoscale_min=false; // Forced minimum value, auto determine max
4136 var $autoscale_max=false; // Forced maximum value, auto determine min
4137 var $gracetop=0,$gracebottom=0;
4138 var $intscale=false; // Restrict autoscale to integers
4139 var $textscale=false; // Just a flag to let the Plot class find out if
4140 // we are a textscale or not. This is a cludge since
4141 // this ionformatyion is availabale in Graph::axtype but
4142 // we don't have access to the graph object in the Plots
4143 // stroke method. So we let graph store the status here
4144 // when the linear scale is created. A real cludge...
4145 var $text_scale_off = 0;
4146 var $auto_ticks=false; // When using manual scale should the ticks be automatically set?
4150 function LinearScale($aMin=0,$aMax=0,$aType="y") {
4151 assert($aType=="x" || $aType=="y" );
4152 assert($aMin<=$aMax);
4155 $this->scale=array($aMin,$aMax);
4156 $this->world_size=$aMax-$aMin;
4157 $this->ticks = new LinearTicks();
4162 // Second phase constructor
4163 function Init(&$aImg) {
4164 $this->InitConstants($aImg);
4165 // We want image to notify us when the margins changes so we
4166 // can recalculate the constants.
4167 // PHP <= 4.04 BUGWARNING: IT IS IMPOSSIBLE TO DO THIS IN THE CONSTRUCTOR
4168 // SINCE (FOR SOME REASON) IT IS IMPOSSIBLE TO PASS A REFERENCE
4169 // TO 'this' INSTEAD IT WILL ADD AN ANONYMOUS COPY OF THIS OBJECT WHICH WILL
4170 // GET ALL THE NOTIFICATIONS. (This took a while to track down...)
4172 // Add us as an observer to class Image
4173 $aImg->AddObserver("InitConstants",$this);
4176 // Check if scale is set or if we should autoscale
4177 // We should do this is either scale or ticks has not been set
4178 function IsSpecified() {
4179 if( $this->GetMinVal()==$this->GetMaxVal() ) { // Scale not set
4185 // Set the minimum data value when the autoscaling is used.
4186 // Usefull if you want a fix minimum (like 0) but have an
4187 // automatic maximum
4188 function SetAutoMin($aMin) {
4189 $this->autoscale_min=$aMin;
4192 // Set the minimum data value when the autoscaling is used.
4193 // Usefull if you want a fix minimum (like 0) but have an
4194 // automatic maximum
4195 function SetAutoMax($aMax) {
4196 $this->autoscale_max=$aMax;
4199 // If the user manually specifies a scale should the ticks
4200 // still be set automatically?
4201 function SetAutoTicks($aFlag=true) {
4202 $this->auto_ticks = $aFlag;
4205 // Specify scale "grace" value (top and bottom)
4206 function SetGrace($aGraceTop,$aGraceBottom=0) {
4207 if( $aGraceTop<0 || $aGraceBottom < 0 )
4208 JpGraphError::Raise(" Grace must be larger then 0");
4209 $this->gracetop=$aGraceTop;
4210 $this->gracebottom=$aGraceBottom;
4213 // Get the minimum value in the scale
4214 function GetMinVal() {
4215 return $this->scale[0];
4218 // get maximum value for scale
4219 function GetMaxVal() {
4220 return $this->scale[1];
4223 // Specify a new min/max value for sclae
4224 function Update(&$aImg,$aMin,$aMax) {
4225 $this->scale=array($aMin,$aMax);
4226 $this->world_size=$aMax-$aMin;
4227 $this->InitConstants($aImg);
4230 // Translate between world and screen
4231 function Translate($aCoord) {
4232 return $this->off+($aCoord - $this->GetMinVal()) * $this->scale_factor;
4235 // Relative translate (don't include offset) usefull when we just want
4236 // to know the relative position (in pixels) on the axis
4237 function RelTranslate($aCoord) {
4238 return ($aCoord - $this->GetMinVal()) * $this->scale_factor;
4241 // Restrict autoscaling to only use integers
4242 function SetIntScale($aIntScale=true) {
4243 $this->intscale=$aIntScale;
4246 // Calculate an integer autoscale
4247 function IntAutoScale(&$img,$min,$max,$maxsteps,$majend=true) {
4248 // Make sure limits are integers
4251 if( abs($min-$max)==0 ) {
4254 $maxsteps = floor($maxsteps);
4256 $gracetop=round(($this->gracetop/100.0)*abs($max-$min));
4257 $gracebottom=round(($this->gracebottom/100.0)*abs($max-$min));
4258 if( is_numeric($this->autoscale_min) ) {
4259 $min = ceil($this->autoscale_min);
4260 if( $min >= $max ) {
4261 JpGraphError::Raise('You have specified a min value with SetAutoMin() which is larger than the maximum value used for the scale. This is not possible.');
4266 if( is_numeric($this->autoscale_max) ) {
4267 $max = ceil($this->autoscale_max);
4268 if( $min >= $max ) {
4269 JpGraphError::Raise('You have specified a max value with SetAutoMax() which is smaller than the miminum value used for the scale. This is not possible.');
4274 if( abs($min-$max ) == 0 ) {
4279 $min -= $gracebottom;
4282 // First get tickmarks as multiples of 1, 10, ...
4284 list($num1steps,$adj1min,$adj1max,$maj1step) =
4285 $this->IntCalcTicks($maxsteps,$min,$max,1);
4290 list($num1steps,$maj1step) =
4291 $this->IntCalcTicksFreeze($maxsteps,$min,$max,1);
4294 if( abs($min-$max) > 2 ) {
4295 // Then get tick marks as 2:s 2, 20, ...
4297 list($num2steps,$adj2min,$adj2max,$maj2step) =
4298 $this->IntCalcTicks($maxsteps,$min,$max,5);
4303 list($num2steps,$maj2step) =
4304 $this->IntCalcTicksFreeze($maxsteps,$min,$max,5);
4308 $num2steps = 10000; // Dummy high value so we don't choose this
4311 if( abs($min-$max) > 5 ) {
4312 // Then get tickmarks as 5:s 5, 50, 500, ...
4314 list($num5steps,$adj5min,$adj5max,$maj5step) =
4315 $this->IntCalcTicks($maxsteps,$min,$max,2);
4320 list($num5steps,$maj5step) =
4321 $this->IntCalcTicksFreeze($maxsteps,$min,$max,2);
4325 $num5steps = 10000; // Dummy high value so we don't choose this
4328 // Check to see whichof 1:s, 2:s or 5:s fit better with
4329 // the requested number of major ticks
4330 $match1=abs($num1steps-$maxsteps);
4331 $match2=abs($num2steps-$maxsteps);
4332 if( !empty($maj5step) && $maj5step > 1 )
4333 $match5=abs($num5steps-$maxsteps);
4335 $match5=10000; // Dummy high value
4337 // Compare these three values and see which is the closest match
4338 // We use a 0.6 weight to gravitate towards multiple of 5:s
4339 if( $match1 < $match2 ) {
4340 if( $match1 < $match5 )
4346 if( $match2 < $match5 )
4351 // Minsteps are always the same as maxsteps for integer scale
4354 $this->Update($img,$adj1min,$adj1max);
4355 $this->ticks->Set($maj1step,$maj1step);
4358 $this->Update($img,$adj2min,$adj2max);
4359 $this->ticks->Set($maj2step,$maj2step);
4362 $this->Update($img,$adj5min,$adj5max);
4363 $this->ticks->Set($maj5step,$maj2step);
4369 // Calculate autoscale. Used if user hasn't given a scale and ticks
4370 // $maxsteps is the maximum number of major tickmarks allowed.
4371 function AutoScale(&$img,$min,$max,$maxsteps,$majend=true) {
4372 if( $this->intscale ) {
4373 $this->IntAutoScale($img,$min,$max,$maxsteps,$majend);
4376 if( abs($min-$max) < 0.00001 ) {
4377 // We need some difference to be able to autoscale
4378 // make it 5% above and 5% below value
4379 if( $min==0 && $max==0 ) { // Special case
4383 $delta = (abs($max)+abs($min))*0.005;
4389 $gracetop=($this->gracetop/100.0)*abs($max-$min);
4390 $gracebottom=($this->gracebottom/100.0)*abs($max-$min);
4391 if( is_numeric($this->autoscale_min) ) {
4392 $min = $this->autoscale_min;
4393 if( $min >= $max ) {
4394 JpGraphError::Raise('You have specified a min value with SetAutoMin() which is larger than the maximum value used for the scale. This is not possible.');
4397 if( abs($min-$max ) < 0.00001 )
4401 if( is_numeric($this->autoscale_max) ) {
4402 $max = $this->autoscale_max;
4403 if( $min >= $max ) {
4404 JpGraphError::Raise('You have specified a max value with SetAutoMax() which is smaller than the miminum value used for the scale. This is not possible.');
4407 if( abs($min-$max ) < 0.00001 )
4412 $min -= $gracebottom;
4415 // First get tickmarks as multiples of 0.1, 1, 10, ...
4417 list($num1steps,$adj1min,$adj1max,$min1step,$maj1step) =
4418 $this->CalcTicks($maxsteps,$min,$max,1,2);
4423 list($num1steps,$min1step,$maj1step) =
4424 $this->CalcTicksFreeze($maxsteps,$min,$max,1,2,false);
4427 // Then get tick marks as 2:s 0.2, 2, 20, ...
4429 list($num2steps,$adj2min,$adj2max,$min2step,$maj2step) =
4430 $this->CalcTicks($maxsteps,$min,$max,5,2);
4435 list($num2steps,$min2step,$maj2step) =
4436 $this->CalcTicksFreeze($maxsteps,$min,$max,5,2,false);
4439 // Then get tickmarks as 5:s 0.05, 0.5, 5, 50, ...
4441 list($num5steps,$adj5min,$adj5max,$min5step,$maj5step) =
4442 $this->CalcTicks($maxsteps,$min,$max,2,5);
4447 list($num5steps,$min5step,$maj5step) =
4448 $this->CalcTicksFreeze($maxsteps,$min,$max,2,5,false);
4451 // Check to see whichof 1:s, 2:s or 5:s fit better with
4452 // the requested number of major ticks
4453 $match1=abs($num1steps-$maxsteps);
4454 $match2=abs($num2steps-$maxsteps);
4455 $match5=abs($num5steps-$maxsteps);
4456 // Compare these three values and see which is the closest match
4457 // We use a 0.8 weight to gravitate towards multiple of 5:s
4458 $r=$this->MatchMin3($match1,$match2,$match5,0.8);
4461 $this->Update($img,$adj1min,$adj1max);
4462 $this->ticks->Set($maj1step,$min1step);
4465 $this->Update($img,$adj2min,$adj2max);
4466 $this->ticks->Set($maj2step,$min2step);
4469 $this->Update($img,$adj5min,$adj5max);
4470 $this->ticks->Set($maj5step,$min5step);
4478 // This method recalculates all constants that are depending on the
4479 // margins in the image. If the margins in the image are changed
4480 // this method should be called for every scale that is registred with
4481 // that image. Should really be installed as an observer of that image.
4482 function InitConstants(&$img) {
4483 if( $this->type=="x" ) {
4484 $this->world_abs_size=$img->width - $img->left_margin - $img->right_margin;
4485 $this->off=$img->left_margin;
4486 $this->scale_factor = 0;
4487 if( $this->world_size > 0 )
4488 $this->scale_factor=$this->world_abs_size/($this->world_size*1.0);
4491 $this->world_abs_size=$img->height - $img->top_margin - $img->bottom_margin;
4492 $this->off=$img->top_margin+$this->world_abs_size;
4493 $this->scale_factor = 0;
4494 if( $this->world_size > 0 )
4495 $this->scale_factor=-$this->world_abs_size/($this->world_size*1.0);
4497 $size = $this->world_size * $this->scale_factor;
4498 $this->scale_abs=array($this->off,$this->off + $size);
4501 // Initialize the conversion constants for this scale
4502 // This tries to pre-calculate as much as possible to speed up the
4503 // actual conversion (with Translate()) later on
4504 // $start =scale start in absolute pixels (for x-scale this is an y-position
4505 // and for an y-scale this is an x-position
4506 // $len =absolute length in pixels of scale
4507 function SetConstants($aStart,$aLen) {
4508 $this->world_abs_size=$aLen;
4511 if( $this->world_size<=0 ) {
4512 JpGraphError::Raise("<b>JpGraph Fatal Error</b>:<br>
4513 You have unfortunately stumbled upon a bug in JpGraph. <br>
4514 It seems like the scale range is ".$this->world_size." [for ".
4515 $this->type." scale] <br>
4516 Please report Bug #01 to jpgraph@aditus.nu and include the script
4517 that gave this error. <br>
4518 This problem could potentially be caused by trying to use \"illegal\"
4519 values in the input data arrays (like trying to send in strings or
4520 only NULL values) which causes the autoscaling to fail.");
4523 // scale_factor = number of pixels per world unit
4524 $this->scale_factor=$this->world_abs_size/($this->world_size*1.0);
4526 // scale_abs = start and end points of scale in absolute pixels
4527 $this->scale_abs=array($this->off,$this->off+$this->world_size*$this->scale_factor);
4531 // Calculate number of ticks steps with a specific division
4532 // $a is the divisor of 10**x to generate the first maj tick intervall
4533 // $a=1, $b=2 give major ticks with multiple of 10, ...,0.1,1,10,...
4534 // $a=5, $b=2 give major ticks with multiple of 2:s ...,0.2,2,20,...
4535 // $a=2, $b=5 give major ticks with multiple of 5:s ...,0.5,5,50,...
4536 // We return a vector of
4537 // [$numsteps,$adjmin,$adjmax,$minstep,$majstep]
4538 // If $majend==true then the first and last marks on the axis will be major
4539 // labeled tick marks otherwise it will be adjusted to the closest min tick mark
4540 function CalcTicks($maxsteps,$min,$max,$a,$b,$majend=true) {
4545 $ld=floor(log10($diff));
4547 // Gravitate min towards zero if we are close
4548 if( $min>0 && $min < pow(10,$ld) ) $min=0;
4550 //$majstep=pow(10,$ld-1)/$a;
4551 $majstep=pow(10,$ld)/$a;
4552 $minstep=$majstep/$b;
4554 $adjmax=ceil($max/$minstep)*$minstep;
4555 $adjmin=floor($min/$minstep)*$minstep;
4556 $adjdiff = $adjmax-$adjmin;
4557 $numsteps=$adjdiff/$majstep;
4559 while( $numsteps>$maxsteps ) {
4560 $majstep=pow(10,$ld)/$a;
4561 $numsteps=$adjdiff/$majstep;
4565 $minstep=$majstep/$b;
4566 $adjmin=floor($min/$minstep)*$minstep;
4567 $adjdiff = $adjmax-$adjmin;
4569 $adjmin = floor($min/$majstep)*$majstep;
4570 $adjdiff = $adjmax-$adjmin;
4571 $adjmax = ceil($adjdiff/$majstep)*$majstep+$adjmin;
4574 $adjmax=ceil($max/$minstep)*$minstep;
4576 return array($numsteps,$adjmin,$adjmax,$minstep,$majstep);
4579 function CalcTicksFreeze($maxsteps,$min,$max,$a,$b) {
4580 // Same as CalcTicks but don't adjust min/max values
4585 $ld=floor(log10($diff));
4587 //$majstep=pow(10,$ld-1)/$a;
4588 $majstep=pow(10,$ld)/$a;
4589 $minstep=$majstep/$b;
4590 $numsteps=floor($diff/$majstep);
4592 while( $numsteps > $maxsteps ) {
4593 $majstep=pow(10,$ld)/$a;
4594 $numsteps=floor($diff/$majstep);
4597 $minstep=$majstep/$b;
4598 return array($numsteps,$minstep,$majstep);
4602 function IntCalcTicks($maxsteps,$min,$max,$a,$majend=true) {
4605 JpGraphError::Raise('Can\'t automatically determine ticks since min==max.');
4607 $ld=floor(log10($diff));
4609 // Gravitate min towards zero if we are close
4610 if( $min>0 && $min < pow(10,$ld) ) $min=0;
4612 if( $ld == 0 ) $ld=1;
4617 $majstep=pow(10,$ld)/$a;
4618 $adjmax=ceil($max/$majstep)*$majstep;
4620 $adjmin=floor($min/$majstep)*$majstep;
4621 $adjdiff = $adjmax-$adjmin;
4622 $numsteps=$adjdiff/$majstep;
4623 while( $numsteps>$maxsteps ) {
4624 $majstep=pow(10,$ld)/$a;
4625 $numsteps=$adjdiff/$majstep;
4629 $adjmin=floor($min/$majstep)*$majstep;
4630 $adjdiff = $adjmax-$adjmin;
4632 $adjmin = floor($min/$majstep)*$majstep;
4633 $adjdiff = $adjmax-$adjmin;
4634 $adjmax = ceil($adjdiff/$majstep)*$majstep+$adjmin;
4637 $adjmax=ceil($max/$majstep)*$majstep;
4639 return array($numsteps,$adjmin,$adjmax,$majstep);
4643 function IntCalcTicksFreeze($maxsteps,$min,$max,$a) {
4644 // Same as IntCalcTick but don't change min/max values
4647 JpGraphError::Raise('Can\'t automatically determine ticks since min==max.');
4649 $ld=floor(log10($diff));
4651 if( $ld == 0 ) $ld=1;
4656 $majstep=pow(10,$ld)/$a;
4658 $numsteps=floor($diff/$majstep);
4659 while( $numsteps > $maxsteps ) {
4660 $majstep=pow(10,$ld)/$a;
4661 $numsteps=floor($diff/$majstep);
4665 return array($numsteps,$majstep);
4670 // Determine the minimum of three values witha weight for last value
4671 function MatchMin3($a,$b,$c,$weight) {
4673 if( $a < ($c*$weight) )
4674 return 1; // $a smallest
4676 return 3; // $c smallest
4678 elseif( $b < ($c*$weight) )
4679 return 2; // $b smallest
4680 return 3; // $c smallest
4684 //===================================================
4686 // Description: Color definitions as RGB triples
4687 //===================================================
4691 function RGB($aImg=null) {
4694 // Conversion array between color names and RGB
4695 $this->rgb_table = array(
4696 "aqua"=> array(0,255,255),
4697 "lime"=> array(0,255,0),
4698 "teal"=> array(0,128,128),
4699 "whitesmoke"=>array(245,245,245),
4700 "gainsboro"=>array(220,220,220),
4701 "oldlace"=>array(253,245,230),
4702 "linen"=>array(250,240,230),
4703 "antiquewhite"=>array(250,235,215),
4704 "papayawhip"=>array(255,239,213),
4705 "blanchedalmond"=>array(255,235,205),
4706 "bisque"=>array(255,228,196),
4707 "peachpuff"=>array(255,218,185),
4708 "navajowhite"=>array(255,222,173),
4709 "moccasin"=>array(255,228,181),
4710 "cornsilk"=>array(255,248,220),
4711 "ivory"=>array(255,255,240),
4712 "lemonchiffon"=>array(255,250,205),
4713 "seashell"=>array(255,245,238),
4714 "mintcream"=>array(245,255,250),
4715 "azure"=>array(240,255,255),
4716 "aliceblue"=>array(240,248,255),
4717 "lavender"=>array(230,230,250),
4718 "lavenderblush"=>array(255,240,245),
4719 "mistyrose"=>array(255,228,225),
4720 "white"=>array(255,255,255),
4721 "black"=>array(0,0,0),
4722 "darkslategray"=>array(47,79,79),
4723 "dimgray"=>array(105,105,105),
4724 "slategray"=>array(112,128,144),
4725 "lightslategray"=>array(119,136,153),
4726 "gray"=>array(190,190,190),
4727 "lightgray"=>array(211,211,211),
4728 "midnightblue"=>array(25,25,112),
4729 "navy"=>array(0,0,128),
4730 "cornflowerblue"=>array(100,149,237),
4731 "darkslateblue"=>array(72,61,139),
4732 "slateblue"=>array(106,90,205),
4733 "mediumslateblue"=>array(123,104,238),
4734 "lightslateblue"=>array(132,112,255),
4735 "mediumblue"=>array(0,0,205),
4736 "royalblue"=>array(65,105,225),
4737 "blue"=>array(0,0,255),
4738 "dodgerblue"=>array(30,144,255),
4739 "deepskyblue"=>array(0,191,255),
4740 "skyblue"=>array(135,206,235),
4741 "lightskyblue"=>array(135,206,250),
4742 "steelblue"=>array(70,130,180),
4743 "lightred"=>array(211,167,168),
4744 "lightsteelblue"=>array(176,196,222),
4745 "lightblue"=>array(173,216,230),
4746 "powderblue"=>array(176,224,230),
4747 "paleturquoise"=>array(175,238,238),
4748 "darkturquoise"=>array(0,206,209),
4749 "mediumturquoise"=>array(72,209,204),
4750 "turquoise"=>array(64,224,208),
4751 "cyan"=>array(0,255,255),
4752 "lightcyan"=>array(224,255,255),
4753 "cadetblue"=>array(95,158,160),
4754 "mediumaquamarine"=>array(102,205,170),
4755 "aquamarine"=>array(127,255,212),
4756 "darkgreen"=>array(0,100,0),
4757 "darkolivegreen"=>array(85,107,47),
4758 "darkseagreen"=>array(143,188,143),
4759 "seagreen"=>array(46,139,87),
4760 "mediumseagreen"=>array(60,179,113),
4761 "lightseagreen"=>array(32,178,170),
4762 "palegreen"=>array(152,251,152),
4763 "springgreen"=>array(0,255,127),
4764 "lawngreen"=>array(124,252,0),
4765 "green"=>array(0,255,0),
4766 "chartreuse"=>array(127,255,0),
4767 "mediumspringgreen"=>array(0,250,154),
4768 "greenyellow"=>array(173,255,47),
4769 "limegreen"=>array(50,205,50),
4770 "yellowgreen"=>array(154,205,50),
4771 "forestgreen"=>array(34,139,34),
4772 "olivedrab"=>array(107,142,35),
4773 "darkkhaki"=>array(189,183,107),
4774 "khaki"=>array(240,230,140),
4775 "palegoldenrod"=>array(238,232,170),
4776 "lightgoldenrodyellow"=>array(250,250,210),
4777 "lightyellow"=>array(255,255,200),
4778 "yellow"=>array(255,255,0),
4779 "gold"=>array(255,215,0),
4780 "lightgoldenrod"=>array(238,221,130),
4781 "goldenrod"=>array(218,165,32),
4782 "darkgoldenrod"=>array(184,134,11),
4783 "rosybrown"=>array(188,143,143),
4784 "indianred"=>array(205,92,92),
4785 "saddlebrown"=>array(139,69,19),
4786 "sienna"=>array(160,82,45),
4787 "peru"=>array(205,133,63),
4788 "burlywood"=>array(222,184,135),
4789 "beige"=>array(245,245,220),
4790 "wheat"=>array(245,222,179),
4791 "sandybrown"=>array(244,164,96),
4792 "tan"=>array(210,180,140),
4793 "chocolate"=>array(210,105,30),
4794 "firebrick"=>array(178,34,34),
4795 "brown"=>array(165,42,42),
4796 "darksalmon"=>array(233,150,122),
4797 "salmon"=>array(250,128,114),
4798 "lightsalmon"=>array(255,160,122),
4799 "orange"=>array(255,165,0),
4800 "darkorange"=>array(255,140,0),
4801 "coral"=>array(255,127,80),
4802 "lightcoral"=>array(240,128,128),
4803 "tomato"=>array(255,99,71),
4804 "orangered"=>array(255,69,0),
4805 "red"=>array(255,0,0),
4806 "hotpink"=>array(255,105,180),
4807 "deeppink"=>array(255,20,147),
4808 "pink"=>array(255,192,203),
4809 "lightpink"=>array(255,182,193),
4810 "palevioletred"=>array(219,112,147),
4811 "maroon"=>array(176,48,96),
4812 "mediumvioletred"=>array(199,21,133),
4813 "violetred"=>array(208,32,144),
4814 "magenta"=>array(255,0,255),
4815 "violet"=>array(238,130,238),
4816 "plum"=>array(221,160,221),
4817 "orchid"=>array(218,112,214),
4818 "mediumorchid"=>array(186,85,211),
4819 "darkorchid"=>array(153,50,204),
4820 "darkviolet"=>array(148,0,211),
4821 "blueviolet"=>array(138,43,226),
4822 "purple"=>array(160,32,240),
4823 "mediumpurple"=>array(147,112,219),
4824 "thistle"=>array(216,191,216),
4825 "snow1"=>array(255,250,250),
4826 "snow2"=>array(238,233,233),
4827 "snow3"=>array(205,201,201),
4828 "snow4"=>array(139,137,137),
4829 "seashell1"=>array(255,245,238),
4830 "seashell2"=>array(238,229,222),
4831 "seashell3"=>array(205,197,191),
4832 "seashell4"=>array(139,134,130),
4833 "AntiqueWhite1"=>array(255,239,219),
4834 "AntiqueWhite2"=>array(238,223,204),
4835 "AntiqueWhite3"=>array(205,192,176),
4836 "AntiqueWhite4"=>array(139,131,120),
4837 "bisque1"=>array(255,228,196),
4838 "bisque2"=>array(238,213,183),
4839 "bisque3"=>array(205,183,158),
4840 "bisque4"=>array(139,125,107),
4841 "peachPuff1"=>array(255,218,185),
4842 "peachpuff2"=>array(238,203,173),
4843 "peachpuff3"=>array(205,175,149),
4844 "peachpuff4"=>array(139,119,101),
4845 "navajowhite1"=>array(255,222,173),
4846 "navajowhite2"=>array(238,207,161),
4847 "navajowhite3"=>array(205,179,139),
4848 "navajowhite4"=>array(139,121,94),
4849 "lemonchiffon1"=>array(255,250,205),
4850 "lemonchiffon2"=>array(238,233,191),
4851 "lemonchiffon3"=>array(205,201,165),
4852 "lemonchiffon4"=>array(139,137,112),
4853 "ivory1"=>array(255,255,240),
4854 "ivory2"=>array(238,238,224),
4855 "ivory3"=>array(205,205,193),
4856 "ivory4"=>array(139,139,131),
4857 "honeydew"=>array(193,205,193),
4858 "lavenderblush1"=>array(255,240,245),
4859 "lavenderblush2"=>array(238,224,229),
4860 "lavenderblush3"=>array(205,193,197),
4861 "lavenderblush4"=>array(139,131,134),
4862 "mistyrose1"=>array(255,228,225),
4863 "mistyrose2"=>array(238,213,210),
4864 "mistyrose3"=>array(205,183,181),
4865 "mistyrose4"=>array(139,125,123),
4866 "azure1"=>array(240,255,255),
4867 "azure2"=>array(224,238,238),
4868 "azure3"=>array(193,205,205),
4869 "azure4"=>array(131,139,139),
4870 "slateblue1"=>array(131,111,255),
4871 "slateblue2"=>array(122,103,238),
4872 "slateblue3"=>array(105,89,205),
4873 "slateblue4"=>array(71,60,139),
4874 "royalblue1"=>array(72,118,255),
4875 "royalblue2"=>array(67,110,238),
4876 "royalblue3"=>array(58,95,205),
4877 "royalblue4"=>array(39,64,139),
4878 "dodgerblue1"=>array(30,144,255),
4879 "dodgerblue2"=>array(28,134,238),
4880 "dodgerblue3"=>array(24,116,205),
4881 "dodgerblue4"=>array(16,78,139),
4882 "steelblue1"=>array(99,184,255),
4883 "steelblue2"=>array(92,172,238),
4884 "steelblue3"=>array(79,148,205),
4885 "steelblue4"=>array(54,100,139),
4886 "deepskyblue1"=>array(0,191,255),
4887 "deepskyblue2"=>array(0,178,238),
4888 "deepskyblue3"=>array(0,154,205),
4889 "deepskyblue4"=>array(0,104,139),
4890 "skyblue1"=>array(135,206,255),
4891 "skyblue2"=>array(126,192,238),
4892 "skyblue3"=>array(108,166,205),
4893 "skyblue4"=>array(74,112,139),
4894 "lightskyblue1"=>array(176,226,255),
4895 "lightskyblue2"=>array(164,211,238),
4896 "lightskyblue3"=>array(141,182,205),
4897 "lightskyblue4"=>array(96,123,139),
4898 "slategray1"=>array(198,226,255),
4899 "slategray2"=>array(185,211,238),
4900 "slategray3"=>array(159,182,205),
4901 "slategray4"=>array(108,123,139),
4902 "lightsteelblue1"=>array(202,225,255),
4903 "lightsteelblue2"=>array(188,210,238),
4904 "lightsteelblue3"=>array(162,181,205),
4905 "lightsteelblue4"=>array(110,123,139),
4906 "lightblue1"=>array(191,239,255),
4907 "lightblue2"=>array(178,223,238),
4908 "lightblue3"=>array(154,192,205),
4909 "lightblue4"=>array(104,131,139),
4910 "lightcyan1"=>array(224,255,255),
4911 "lightcyan2"=>array(209,238,238),
4912 "lightcyan3"=>array(180,205,205),
4913 "lightcyan4"=>array(122,139,139),
4914 "paleturquoise1"=>array(187,255,255),
4915 "paleturquoise2"=>array(174,238,238),
4916 "paleturquoise3"=>array(150,205,205),
4917 "paleturquoise4"=>array(102,139,139),
4918 "cadetblue1"=>array(152,245,255),
4919 "cadetblue2"=>array(142,229,238),
4920 "cadetblue3"=>array(122,197,205),
4921 "cadetblue4"=>array(83,134,139),
4922 "turquoise1"=>array(0,245,255),
4923 "turquoise2"=>array(0,229,238),
4924 "turquoise3"=>array(0,197,205),
4925 "turquoise4"=>array(0,134,139),
4926 "cyan1"=>array(0,255,255),
4927 "cyan2"=>array(0,238,238),
4928 "cyan3"=>array(0,205,205),
4929 "cyan4"=>array(0,139,139),
4930 "darkslategray1"=>array(151,255,255),
4931 "darkslategray2"=>array(141,238,238),
4932 "darkslategray3"=>array(121,205,205),
4933 "darkslategray4"=>array(82,139,139),
4934 "aquamarine1"=>array(127,255,212),
4935 "aquamarine2"=>array(118,238,198),
4936 "aquamarine3"=>array(102,205,170),
4937 "aquamarine4"=>array(69,139,116),
4938 "darkseagreen1"=>array(193,255,193),
4939 "darkseagreen2"=>array(180,238,180),
4940 "darkseagreen3"=>array(155,205,155),
4941 "darkseagreen4"=>array(105,139,105),
4942 "seagreen1"=>array(84,255,159),
4943 "seagreen2"=>array(78,238,148),
4944 "seagreen3"=>array(67,205,128),
4945 "seagreen4"=>array(46,139,87),
4946 "palegreen1"=>array(154,255,154),
4947 "palegreen2"=>array(144,238,144),
4948 "palegreen3"=>array(124,205,124),
4949 "palegreen4"=>array(84,139,84),
4950 "springgreen1"=>array(0,255,127),
4951 "springgreen2"=>array(0,238,118),
4952 "springgreen3"=>array(0,205,102),
4953 "springgreen4"=>array(0,139,69),
4954 "chartreuse1"=>array(127,255,0),
4955 "chartreuse2"=>array(118,238,0),
4956 "chartreuse3"=>array(102,205,0),
4957 "chartreuse4"=>array(69,139,0),
4958 "olivedrab1"=>array(192,255,62),
4959 "olivedrab2"=>array(179,238,58),
4960 "olivedrab3"=>array(154,205,50),
4961 "olivedrab4"=>array(105,139,34),
4962 "darkolivegreen1"=>array(202,255,112),
4963 "darkolivegreen2"=>array(188,238,104),
4964 "darkolivegreen3"=>array(162,205,90),
4965 "darkolivegreen4"=>array(110,139,61),
4966 "khaki1"=>array(255,246,143),
4967 "khaki2"=>array(238,230,133),
4968 "khaki3"=>array(205,198,115),
4969 "khaki4"=>array(139,134,78),
4970 "lightgoldenrod1"=>array(255,236,139),
4971 "lightgoldenrod2"=>array(238,220,130),
4972 "lightgoldenrod3"=>array(205,190,112),
4973 "lightgoldenrod4"=>array(139,129,76),
4974 "yellow1"=>array(255,255,0),
4975 "yellow2"=>array(238,238,0),
4976 "yellow3"=>array(205,205,0),
4977 "yellow4"=>array(139,139,0),
4978 "gold1"=>array(255,215,0),
4979 "gold2"=>array(238,201,0),
4980 "gold3"=>array(205,173,0),
4981 "gold4"=>array(139,117,0),
4982 "goldenrod1"=>array(255,193,37),
4983 "goldenrod2"=>array(238,180,34),
4984 "goldenrod3"=>array(205,155,29),
4985 "goldenrod4"=>array(139,105,20),
4986 "darkgoldenrod1"=>array(255,185,15),
4987 "darkgoldenrod2"=>array(238,173,14),
4988 "darkgoldenrod3"=>array(205,149,12),
4989 "darkgoldenrod4"=>array(139,101,8),
4990 "rosybrown1"=>array(255,193,193),
4991 "rosybrown2"=>array(238,180,180),
4992 "rosybrown3"=>array(205,155,155),
4993 "rosybrown4"=>array(139,105,105),
4994 "indianred1"=>array(255,106,106),
4995 "indianred2"=>array(238,99,99),
4996 "indianred3"=>array(205,85,85),
4997 "indianred4"=>array(139,58,58),
4998 "sienna1"=>array(255,130,71),
4999 "sienna2"=>array(238,121,66),
5000 "sienna3"=>array(205,104,57),
5001 "sienna4"=>array(139,71,38),
5002 "burlywood1"=>array(255,211,155),
5003 "burlywood2"=>array(238,197,145),
5004 "burlywood3"=>array(205,170,125),
5005 "burlywood4"=>array(139,115,85),
5006 "wheat1"=>array(255,231,186),
5007 "wheat2"=>array(238,216,174),
5008 "wheat3"=>array(205,186,150),
5009 "wheat4"=>array(139,126,102),
5010 "tan1"=>array(255,165,79),
5011 "tan2"=>array(238,154,73),
5012 "tan3"=>array(205,133,63),
5013 "tan4"=>array(139,90,43),
5014 "chocolate1"=>array(255,127,36),
5015 "chocolate2"=>array(238,118,33),
5016 "chocolate3"=>array(205,102,29),
5017 "chocolate4"=>array(139,69,19),
5018 "firebrick1"=>array(255,48,48),
5019 "firebrick2"=>array(238,44,44),
5020 "firebrick3"=>array(205,38,38),
5021 "firebrick4"=>array(139,26,26),
5022 "brown1"=>array(255,64,64),
5023 "brown2"=>array(238,59,59),
5024 "brown3"=>array(205,51,51),
5025 "brown4"=>array(139,35,35),
5026 "salmon1"=>array(255,140,105),
5027 "salmon2"=>array(238,130,98),
5028 "salmon3"=>array(205,112,84),
5029 "salmon4"=>array(139,76,57),
5030 "lightsalmon1"=>array(255,160,122),
5031 "lightsalmon2"=>array(238,149,114),
5032 "lightsalmon3"=>array(205,129,98),
5033 "lightsalmon4"=>array(139,87,66),
5034 "orange1"=>array(255,165,0),
5035 "orange2"=>array(238,154,0),
5036 "orange3"=>array(205,133,0),
5037 "orange4"=>array(139,90,0),
5038 "darkorange1"=>array(255,127,0),
5039 "darkorange2"=>array(238,118,0),
5040 "darkorange3"=>array(205,102,0),
5041 "darkorange4"=>array(139,69,0),
5042 "coral1"=>array(255,114,86),
5043 "coral2"=>array(238,106,80),
5044 "coral3"=>array(205,91,69),
5045 "coral4"=>array(139,62,47),
5046 "tomato1"=>array(255,99,71),
5047 "tomato2"=>array(238,92,66),
5048 "tomato3"=>array(205,79,57),
5049 "tomato4"=>array(139,54,38),
5050 "orangered1"=>array(255,69,0),
5051 "orangered2"=>array(238,64,0),
5052 "orangered3"=>array(205,55,0),
5053 "orangered4"=>array(139,37,0),
5054 "deeppink1"=>array(255,20,147),
5055 "deeppink2"=>array(238,18,137),
5056 "deeppink3"=>array(205,16,118),
5057 "deeppink4"=>array(139,10,80),
5058 "hotpink1"=>array(255,110,180),
5059 "hotpink2"=>array(238,106,167),
5060 "hotpink3"=>array(205,96,144),
5061 "hotpink4"=>array(139,58,98),
5062 "pink1"=>array(255,181,197),
5063 "pink2"=>array(238,169,184),
5064 "pink3"=>array(205,145,158),
5065 "pink4"=>array(139,99,108),
5066 "lightpink1"=>array(255,174,185),
5067 "lightpink2"=>array(238,162,173),
5068 "lightpink3"=>array(205,140,149),
5069 "lightpink4"=>array(139,95,101),
5070 "palevioletred1"=>array(255,130,171),
5071 "palevioletred2"=>array(238,121,159),
5072 "palevioletred3"=>array(205,104,137),
5073 "palevioletred4"=>array(139,71,93),
5074 "maroon1"=>array(255,52,179),
5075 "maroon2"=>array(238,48,167),
5076 "maroon3"=>array(205,41,144),
5077 "maroon4"=>array(139,28,98),
5078 "violetred1"=>array(255,62,150),
5079 "violetred2"=>array(238,58,140),
5080 "violetred3"=>array(205,50,120),
5081 "violetred4"=>array(139,34,82),
5082 "magenta1"=>array(255,0,255),
5083 "magenta2"=>array(238,0,238),
5084 "magenta3"=>array(205,0,205),
5085 "magenta4"=>array(139,0,139),
5086 "mediumred"=>array(140,34,34),
5087 "orchid1"=>array(255,131,250),
5088 "orchid2"=>array(238,122,233),
5089 "orchid3"=>array(205,105,201),
5090 "orchid4"=>array(139,71,137),
5091 "plum1"=>array(255,187,255),
5092 "plum2"=>array(238,174,238),
5093 "plum3"=>array(205,150,205),
5094 "plum4"=>array(139,102,139),
5095 "mediumorchid1"=>array(224,102,255),
5096 "mediumorchid2"=>array(209,95,238),
5097 "mediumorchid3"=>array(180,82,205),
5098 "mediumorchid4"=>array(122,55,139),
5099 "darkorchid1"=>array(191,62,255),
5100 "darkorchid2"=>array(178,58,238),
5101 "darkorchid3"=>array(154,50,205),
5102 "darkorchid4"=>array(104,34,139),
5103 "purple1"=>array(155,48,255),
5104 "purple2"=>array(145,44,238),
5105 "purple3"=>array(125,38,205),
5106 "purple4"=>array(85,26,139),
5107 "mediumpurple1"=>array(171,130,255),
5108 "mediumpurple2"=>array(159,121,238),
5109 "mediumpurple3"=>array(137,104,205),
5110 "mediumpurple4"=>array(93,71,139),
5111 "thistle1"=>array(255,225,255),
5112 "thistle2"=>array(238,210,238),
5113 "thistle3"=>array(205,181,205),
5114 "thistle4"=>array(139,123,139),
5115 "gray1"=>array(10,10,10),
5116 "gray2"=>array(40,40,30),
5117 "gray3"=>array(70,70,70),
5118 "gray4"=>array(100,100,100),
5119 "gray5"=>array(130,130,130),
5120 "gray6"=>array(160,160,160),
5121 "gray7"=>array(190,190,190),
5122 "gray8"=>array(210,210,210),
5123 "gray9"=>array(240,240,240),
5124 "darkgray"=>array(100,100,100),
5125 "darkblue"=>array(0,0,139),
5126 "darkcyan"=>array(0,139,139),
5127 "darkmagenta"=>array(139,0,139),
5128 "darkred"=>array(139,0,0),
5129 "silver"=>array(192, 192, 192),
5130 "eggplant"=>array(144,176,168),
5131 "lightgreen"=>array(144,238,144));
5135 // Colors can be specified as either
5136 // 1. #xxxxxx HTML style
5137 // 2. "colorname" as a named color
5138 // 3. array(r,g,b) RGB triple
5139 // This function translates this to a native RGB format and returns an
5141 function Color($aColor) {
5142 if (is_string($aColor)) {
5143 // Strip of any alpha factor
5144 $pos = strpos($aColor,'@');
5145 if( $pos === false ) {
5149 $pos2 = strpos($aColor,':');
5151 $pos2 = $pos-1; // Sentinel
5152 if( $pos > $pos2 ) {
5153 $alpha = substr($aColor,$pos+1);
5154 $aColor = substr($aColor,0,$pos);
5157 $alpha = substr($aColor,$pos+1,$pos2-$pos-1);
5158 $aColor = substr($aColor,0,$pos).substr($aColor,$pos2);
5162 // Extract potential adjustment figure at end of color
5164 $pos = strpos($aColor,":");
5165 if( $pos === false ) {
5169 $adj = 0.0 + substr($aColor,$pos+1);
5170 $aColor = substr($aColor,0,$pos);
5173 JpGraphError::Raise('Adjustment factor for color must be > 0');
5175 if (substr($aColor, 0, 1) == "#") {
5176 $r = hexdec(substr($aColor, 1, 2));
5177 $g = hexdec(substr($aColor, 3, 2));
5178 $b = hexdec(substr($aColor, 5, 2));
5180 if(!isset($this->rgb_table[$aColor]) )
5181 JpGraphError::Raise(" Unknown color: <strong>$aColor</strong>");
5182 $tmp=$this->rgb_table[$aColor];
5187 // Scale adj so that an adj=2 always
5188 // makes the color 100% white (i.e. 255,255,255.
5189 // and adj=1 neutral and adj=0 black.
5191 $m = ($adj-1.0)*(255-min(255,min($r,min($g,$b))));
5192 return array(min(255,$r+$m), min(255,$g+$m), min(255,$b+$m),$alpha);
5194 elseif( $adj < 1 ) {
5195 $m = ($adj-1.0)*max(255,max($r,max($g,$b)));
5196 return array(max(0,$r+$m), max(0,$g+$m), max(0,$b+$m),$alpha);
5199 return array($r,$g,$b,$alpha);
5202 } elseif( is_array($aColor) ) {
5203 if( count($aColor)==3 ) {
5211 JpGraphError::Raise(" Unknown color specification: $aColor , size=".count($aColor));
5214 // Compare two colors
5215 // return true if equal
5216 function Equal($aCol1,$aCol2) {
5217 $c1 = $this->Color($aCol1);
5218 $c2 = $this->Color($aCol2);
5219 if( $c1[0]==$c2[0] && $c1[1]==$c2[1] && $c1[2]==$c2[2] )
5225 // Allocate a new color in the current image
5226 // Return new color index, -1 if no more colors could be allocated
5227 function Allocate($aColor,$aAlpha=0.0) {
5228 list ($r, $g, $b, $a) = $this->color($aColor);
5229 // If alpha is specified in the color string then this
5230 // takes precedence over the second argument
5233 if(@$GLOBALS['gd2']==true) {
5234 if( $aAlpha < 0 || $aAlpha > 1 ) {
5235 JpGraphError::Raise('Alpha parameter for color must be between 0.0 and 1.0');
5238 return imagecolorresolvealpha($this->img, $r, $g, $b, round($aAlpha * 127));
5240 $index = imagecolorexact($this->img, $r, $g, $b);
5242 $index = imagecolorallocate($this->img, $r, $g, $b);
5243 if( USE_APPROX_COLORS && $index == -1 )
5244 $index = imagecolorresolve($this->img, $r, $g, $b);
5252 //===================================================
5254 // Description: Wrapper class with some goodies to form the
5255 // Interface to low level image drawing routines.
5256 //===================================================
5261 var $left_margin=30,$right_margin=30,$top_margin=20,$bottom_margin=30;
5262 var $plotwidth=0,$plotheight=0;
5264 var $current_color,$current_color_name;
5265 var $lastx=0, $lasty=0;
5266 var $width, $height;
5268 var $line_style=1; // Default line style is solid
5269 var $obs_list=array();
5270 var $font_size=12,$font_family=FF_FONT1, $font_style=FS_NORMAL;
5272 var $text_halign="left",$text_valign="bottom";
5274 var $use_anti_aliasing=false;
5276 var $colorstack=array(),$colorstackidx=0;
5277 var $canvascolor = 'white' ;
5278 var $langconv = null ;
5282 function Image($aWidth,$aHeight,$aFormat=DEFAULT_GFORMAT) {
5283 $this->CreateImgCanvas($aWidth,$aHeight);
5284 $this->SetAutoMargin();
5286 if( !$this->SetImgFormat($aFormat) ) {
5287 JpGraphError::Raise("JpGraph: Selected graphic format is either not supported or unknown [$aFormat]");
5289 $this->ttf = new TTF();
5290 $this->langconv = new LanguageConv();
5293 // Should we use anti-aliasing. Note: This really slows down graphics!
5294 function SetAntiAliasing() {
5295 $this->use_anti_aliasing=true;
5298 function CreateRawCanvas($aWidth=0,$aHeight=0) {
5299 if( @$GLOBALS['gd2']==true && USE_TRUECOLOR ) {
5300 $this->img = @imagecreatetruecolor($aWidth, $aHeight);
5301 if( $this->img < 1 ) {
5302 die("<font color=red><b>JpGraph Error:</b></font> Can't create truecolor image. Check that you really have GD2 library installed.");
5304 $this->SetAlphaBlending();
5306 $this->img = @imagecreate($aWidth, $aHeight);
5307 if( $this->img < 1 ) {
5308 die("<font color=red><b>JpGraph Error:</b></font> Can't create image. Check that you really have the GD library installed.");
5311 if( $this->rgb != null )
5312 $this->rgb->img = $this->img ;
5314 $this->rgb = new RGB($this->img);
5317 function CloneCanvasH() {
5318 $oldimage = $this->img;
5319 $this->CreateRawCanvas($this->width,$this->height);
5320 imagecopy($this->img,$oldimage,0,0,0,0,$this->width,$this->height);
5324 function CreateImgCanvas($aWidth=0,$aHeight=0) {
5326 $old = array($this->img,$this->width,$this->height);
5328 $aWidth = round($aWidth);
5329 $aHeight = round($aHeight);
5331 $this->width=$aWidth;
5332 $this->height=$aHeight;
5335 if( $aWidth==0 || $aHeight==0 ) {
5336 // We will set the final size later.
5337 // Note: The size must be specified before any other
5338 // img routines that stroke anything are called.
5344 $this->CreateRawCanvas($aWidth,$aHeight);
5346 // Set canvas color (will also be the background color for a
5348 $this->SetColor($this->canvascolor);
5349 $this->FilledRectangle(0,0,$aWidth,$aHeight);
5355 function CopyCanvasH($aToHdl,$aFromHdl,$aToX,$aToY,$aFromX,$aFromY,$aWidth,$aHeight,$aw=-1,$ah=-1) {
5359 $f = 'imagecopyresized';
5362 $f = $GLOBALS['copyfunc'] ;
5364 $f($aToHdl,$aFromHdl,
5365 $aToX,$aToY,$aFromX,$aFromY, $aWidth,$aHeight,$aw,$ah);
5368 function Copy($fromImg,$toX,$toY,$fromX,$fromY,
5369 $toWidth,$toHeight,$fromWidth=-1,$fromHeight=-1) {
5370 $this->CopyCanvasH($this->img,$fromImg,$toX,$toY,$fromX,$fromY,
5371 $toWidth,$toHeight,$fromWidth,$fromHeight);
5374 function GetWidth($aImg=null) {
5375 if( $aImg === null )
5377 return imagesx($aImg);
5380 function GetHeight($aImg=null) {
5381 if( $aImg === null )
5383 return imagesy($aImg);
5386 function CreateFromString($aStr) {
5387 return imagecreatefromstring($aStr);
5390 function SetCanvasH($aHdl) {
5392 $this->rgb->img = $aHdl;
5395 function SetCanvasColor($aColor) {
5396 $this->canvascolor = $aColor ;
5399 function SetAlphaBlending($aFlg=true) {
5400 if( $GLOBALS['gd2'] )
5401 ImageAlphaBlending($this->img,$aFlg);
5403 JpGraphError::Raise('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.');
5407 function SetAutoMargin() {
5408 GLOBAL $gJpgBrandTiming;
5411 if( $gJpgBrandTiming )
5414 $lm = max(0,$this->width/7);
5415 $rm = max(0,$this->width/10);
5416 $tm = max(0,$this->height/7);
5417 $bm = max($min_bm,$this->height/7);
5418 $this->SetMargin($lm,$rm,$tm,$bm);
5425 // Add observer. The observer will be notified when
5426 // the margin changes
5427 function AddObserver($aMethod,&$aObject) {
5428 $this->obs_list[]=array($aMethod,&$aObject);
5431 // Call all observers
5432 function NotifyObservers() {
5433 // foreach($this->obs_list as $o)
5434 // $o[1]->$o[0]($this);
5435 for($i=0; $i < count($this->obs_list); ++$i) {
5436 $obj = & $this->obs_list[$i][1];
5437 $method = $this->obs_list[$i][0];
5438 $obj->$method($this);
5442 function SetFont($family,$style=FS_NORMAL,$size=10) {
5443 if($family==FONT1_BOLD || $family==FONT2_BOLD || $family==FONT0 || $family==FONT1 || $family==FONT2 )
5444 JpGraphError::Raise(" Usage of FONT0, FONT1, FONT2 is deprecated. Use FF_xxx instead.");
5447 $this->font_family=$family;
5448 $this->font_style=$style;
5449 $this->font_size=$size;
5450 $this->font_file='';
5451 if( ($this->font_family==FF_FONT1 || $this->font_family==FF_FONT2) && $this->font_style==FS_BOLD ){
5452 ++$this->font_family;
5454 if( $this->font_family > FF_FONT2+1 ) { // A TTF font so get the font file
5456 // Check that this PHP has support for TTF fonts
5457 if( !function_exists('imagettfbbox') ) {
5458 JpGraphError::Raise('This PHP build has not been configured with TTF support. You need to recompile your PHP installation with FreeType support.');
5461 $this->font_file = $this->ttf->File($this->font_family,$this->font_style);
5465 // Get the specific height for a text string
5466 function GetTextHeight($txt="",$angle=0) {
5467 $tmp = split("\n",$txt);
5470 for($i=0; $i< $n; ++$i)
5471 $m = max($m,strlen($tmp[$i]));
5473 if( $this->font_family <= FF_FONT2+1 ) {
5475 return $n*imagefontheight($this->font_family);
5477 return $m*imagefontwidth($this->font_family);
5480 $bbox = $this->GetTTFBBox($txt,$angle);
5481 return $bbox[1]-$bbox[5];
5485 // Estimate font height
5486 function GetFontHeight($angle=0) {
5488 return $this->GetTextHeight($txt,$angle);
5491 // Approximate font width with width of letter "O"
5492 function GetFontWidth($angle=0) {
5494 return $this->GetTextWidth($txt,$angle);
5497 // Get actual width of text in absolute pixels
5498 function GetTextWidth($txt,$angle=0) {
5500 $tmp = split("\n",$txt);
5502 if( $this->font_family <= FF_FONT2+1 ) {
5505 for($i=0; $i < $n; ++$i) {
5506 $l=strlen($tmp[$i]);
5513 $width=$m*imagefontwidth($this->font_family);
5517 // 90 degrees internal so height becomes width
5518 return $n*imagefontheight($this->font_family);
5522 // For TTF fonts we must walk through a lines and find the
5523 // widest one which we use as the width of the multi-line
5526 for( $i=0; $i < $n; ++$i ) {
5527 $bbox = $this->GetTTFBBox($tmp[$i],$angle);
5528 $mm = $bbox[2] - $bbox[0];
5536 // Draw text with a box around it
5537 function StrokeBoxedText($x,$y,$txt,$dir=0,$fcolor="white",$bcolor="black",
5538 $shadowcolor=false,$paragraph_align="left",
5539 $xmarg=6,$ymarg=4,$cornerradius=0,$dropwidth=3) {
5541 if( !is_numeric($dir) ) {
5542 if( $dir=="h" ) $dir=0;
5543 elseif( $dir=="v" ) $dir=90;
5544 else JpGraphError::Raise(" Unknown direction specified in call to StrokeBoxedText() [$dir]");
5547 if( $this->font_family >= FF_FONT0 && $this->font_family <= FF_FONT2+1) {
5548 $width=$this->GetTextWidth($txt,$dir) ;
5549 $height=$this->GetTextHeight($txt,$dir) ;
5552 $width=$this->GetBBoxWidth($txt,$dir) ;
5553 $height=$this->GetBBoxHeight($txt,$dir) ;
5556 $height += 2*$ymarg;
5559 if( $this->text_halign=="right" ) $x -= $width;
5560 elseif( $this->text_halign=="center" ) $x -= $width/2;
5561 if( $this->text_valign=="bottom" ) $y -= $height;
5562 elseif( $this->text_valign=="center" ) $y -= $height/2;
5564 if( $shadowcolor ) {
5565 $this->PushColor($shadowcolor);
5566 $this->FilledRoundedRectangle($x-$xmarg+$dropwidth,$y-$ymarg+$dropwidth,
5567 $x+$width+$dropwidth,$y+$height+$dropwidth,
5570 $this->PushColor($fcolor);
5571 $this->FilledRoundedRectangle($x-$xmarg,$y-$ymarg,$x+$width,$y+$height,$cornerradius);
5573 $this->PushColor($bcolor);
5574 $this->RoundedRectangle($x-$xmarg,$y-$ymarg,$x+$width,$y+$height,$cornerradius);
5579 $oc=$this->current_color;
5580 $this->SetColor($fcolor);
5581 $this->FilledRoundedRectangle($x-$xmarg,$y-$ymarg,$x+$width,$y+$height,$cornerradius);
5582 $this->current_color=$oc;
5585 $oc=$this->current_color;
5586 $this->SetColor($bcolor);
5587 $this->RoundedRectangle($x-$xmarg,$y-$ymarg,$x+$width,$y+$height,$cornerradius);
5588 $this->current_color=$oc;
5592 $h=$this->text_halign;
5593 $v=$this->text_valign;
5594 $this->SetTextAlign("left","top");
5595 $this->StrokeText($x, $y, $txt, $dir, $paragraph_align);
5596 $this->SetTextAlign($h,$v);
5599 // Set text alignment
5600 function SetTextAlign($halign,$valign="bottom") {
5601 $this->text_halign=$halign;
5602 $this->text_valign=$valign;
5606 function _StrokeBuiltinFont($x,$y,$txt,$dir=0,$paragraph_align="left") {
5608 if( is_numeric($dir) && $dir!=90 && $dir!=0)
5609 JpGraphError::Raise(" Internal font does not support drawing text at arbitrary angle. Use TTF fonts instead.");
5611 $h=$this->GetTextHeight($txt);
5612 $fh=$this->GetFontHeight();
5613 $w=$this->GetTextWidth($txt);
5615 if( $this->text_halign=="right")
5616 $x -= $dir==0 ? $w : $h;
5617 elseif( $this->text_halign=="center" ) {
5618 // For center we subtract 1 pixel since this makes the middle
5619 // be prefectly in the middle
5620 $x -= $dir==0 ? $w/2-1 : $h/2;
5622 if( $this->text_valign=="top" )
5623 $y += $dir==0 ? $h : $w;
5624 elseif( $this->text_valign=="center" )
5625 $y += $dir==0 ? $h/2 : $w/2;
5628 imagestringup($this->img,$this->font_family,$x,$y,$txt,$this->current_color);
5630 if( ereg("\n",$txt) ) {
5631 $tmp = split("\n",$txt);
5632 for($i=0; $i < count($tmp); ++$i) {
5633 $w1 = $this->GetTextWidth($tmp[$i]);
5634 if( $paragraph_align=="left" ) {
5635 imagestring($this->img,$this->font_family,$x,$y-$h+1+$i*$fh,$tmp[$i],$this->current_color);
5637 elseif( $paragraph_align=="right" ) {
5638 imagestring($this->img,$this->font_family,$x+($w-$w1),
5639 $y-$h+1+$i*$fh,$tmp[$i],$this->current_color);
5642 imagestring($this->img,$this->font_family,$x+$w/2-$w1/2,
5643 $y-$h+1+$i*$fh,$tmp[$i],$this->current_color);
5649 imagestring($this->img,$this->font_family,$x,$y-$h+1,$txt,$this->current_color);
5654 function AddTxtCR($aTxt) {
5655 // If the user has just specified a '\n'
5656 // instead of '\n\t' we have to add '\r' since
5657 // the width will be too muchy otherwise since when
5658 // we print we stroke the individually lines by hand.
5659 $e = explode("\n",$aTxt);
5661 for($i=0; $i<$n; ++$i) {
5662 $e[$i]=str_replace("\r","",$e[$i]);
5664 return implode("\n\r",$e);
5667 function GetTTFBBox($aTxt,$aAngle=0) {
5668 $bbox = @ImageTTFBBox($this->font_size,$aAngle,$this->font_file,$aTxt);
5669 if( $bbox === false ) {
5670 JpGraphError::Raise("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.");
5675 function GetBBoxTTF($aTxt,$aAngle=0) {
5676 // Normalize the bounding box to become a minimum
5677 // enscribing rectangle
5679 $aTxt = $this->AddTxtCR($aTxt);
5681 if( !is_readable($this->font_file) ) {
5682 JpGraphError::Raise('Can not read font file ('.$this->font_file.') in call to Image::GetBBoxTTF. Please make sure that you have set a font before calling this method and that the font is installed in the TTF directory.');
5684 $bbox = $this->GetTTFBBox($aTxt,$aAngle);
5688 if( $aAngle >= 0 ) {
5689 if( $aAngle <= 90 ) { //<=0
5690 $bbox = array($bbox[6],$bbox[1],$bbox[2],$bbox[1],
5691 $bbox[2],$bbox[5],$bbox[6],$bbox[5]);
5693 elseif( $aAngle <= 180 ) { //<= 2
5694 $bbox = array($bbox[4],$bbox[7],$bbox[0],$bbox[7],
5695 $bbox[0],$bbox[3],$bbox[4],$bbox[3]);
5697 elseif( $aAngle <= 270 ) { //<= 3
5698 $bbox = array($bbox[2],$bbox[5],$bbox[6],$bbox[5],
5699 $bbox[6],$bbox[1],$bbox[2],$bbox[1]);
5702 $bbox = array($bbox[0],$bbox[3],$bbox[4],$bbox[3],
5703 $bbox[4],$bbox[7],$bbox[0],$bbox[7]);
5706 elseif( $aAngle < 0 ) {
5707 if( $aAngle <= -270 ) { // <= -3
5708 $bbox = array($bbox[6],$bbox[1],$bbox[2],$bbox[1],
5709 $bbox[2],$bbox[5],$bbox[6],$bbox[5]);
5711 elseif( $aAngle <= -180 ) { // <= -2
5712 $bbox = array($bbox[0],$bbox[3],$bbox[4],$bbox[3],
5713 $bbox[4],$bbox[7],$bbox[0],$bbox[7]);
5715 elseif( $aAngle <= -90 ) { // <= -1
5716 $bbox = array($bbox[2],$bbox[5],$bbox[6],$bbox[5],
5717 $bbox[6],$bbox[1],$bbox[2],$bbox[1]);
5720 $bbox = array($bbox[0],$bbox[3],$bbox[4],$bbox[3],
5721 $bbox[4],$bbox[7],$bbox[0],$bbox[7]);
5727 function GetBBoxHeight($aTxt,$aAngle=0) {
5728 $box = $this->GetBBoxTTF($aTxt,$aAngle);
5729 return $box[1]-$box[7]+1;
5732 function GetBBoxWidth($aTxt,$aAngle=0) {
5733 $box = $this->GetBBoxTTF($aTxt,$aAngle);
5734 return $box[2]-$box[0]+1;
5737 function _StrokeTTF($x,$y,$txt,$dir=0,$paragraph_align="left",$debug=false) {
5739 // Remember the anchor point before adjustment
5745 if( !ereg("\n",$txt) || ($dir>0 && ereg("\n",$txt)) ) {
5746 // Format a single line
5748 $txt = $this->AddTxtCR($txt);
5750 $bbox=$this->GetBBoxTTF($txt,$dir);
5752 // Align x,y ot lower left corner of bbox
5756 // Note to self: "topanchor" is deprecated after we changed the
5757 // bopunding box stuff.
5758 if( $this->text_halign=="right" || $this->text_halign=="topanchor" )
5759 $x -= $bbox[2]-$bbox[0];
5760 elseif( $this->text_halign=="center" ) $x -= ($bbox[2]-$bbox[0])/2;
5762 if( $this->text_valign=="top" ) $y += abs($bbox[5])+$bbox[1];
5763 elseif( $this->text_valign=="center" ) $y -= ($bbox[5]-$bbox[1])/2;
5765 ImageTTFText ($this->img, $this->font_size, $dir, $x, $y,
5766 $this->current_color,$this->font_file,$txt);
5769 // Draw the bounding rectangle and the bounding box
5770 $box=ImageTTFBBox($this->font_size,$dir,$this->font_file,$txt);
5773 for($i=0; $i < 4; ++$i) {
5774 $p[] = $bbox[$i*2]+$x;
5775 $p[] = $bbox[$i*2+1]+$y;
5776 $p1[] = $box[$i*2]+$x;
5777 $p1[] = $box[$i*2+1]+$y;
5780 // Draw bounding box
5781 $this->PushColor('green');
5782 $this->Polygon($p1,true);
5785 // Draw bounding rectangle
5786 $this->PushColor('darkgreen');
5787 $this->Polygon($p,true);
5790 // Draw a cross at the anchor point
5791 $this->PushColor('red');
5792 $this->Line($ox-15,$oy,$ox+15,$oy);
5793 $this->Line($ox,$oy-15,$ox,$oy+15);
5798 // Format a text paragraph
5799 $fh=$this->GetFontHeight();
5801 // Line margin is 15% of font height
5802 $linemargin=round($fh*0.15);
5804 $w=$this->GetTextWidth($txt);
5806 $y -= $linemargin/2;
5807 $tmp = split("\n",$txt);
5811 if( $this->text_halign=="right")
5812 $x -= $dir==0 ? $w : $h;
5813 elseif( $this->text_halign=="center" ) {
5814 $x -= $dir==0 ? $w/2 : $h/2;
5817 if( $this->text_valign=="top" )
5818 $y += $dir==0 ? $h : $w;
5819 elseif( $this->text_valign=="center" )
5820 $y += $dir==0 ? $h/2 : $w/2;
5822 // Here comes a tricky bit.
5823 // Since we have to give the position for the string at the
5824 // baseline this means thaht text will move slightly up
5825 // and down depending on any of it's character descend below
5826 // the baseline, for example a 'g'. To adjust the Y-position
5827 // we therefore adjust the text with the baseline Y-offset
5828 // as used for the current font and size. This will keep the
5829 // baseline at a fixed positoned disregarding the actual
5830 // characters in the string.
5831 $standardbox = $this->GetTTFBBox('Gg',$dir);
5832 $yadj = $standardbox[1];
5833 $xadj = $standardbox[0];
5834 for($i=0; $i < $nl; ++$i) {
5835 $wl = $this->GetTextWidth($tmp[$i]);
5836 $bbox = $this->GetTTFBBox($tmp[$i],$dir);
5837 if( $paragraph_align=="left" ) {
5840 elseif( $paragraph_align=="right" ) {
5841 $xl = $x + ($w-$wl);
5845 $xl = $x + $w/2 - $wl/2 ;
5851 ImageTTFText ($this->img, $this->font_size, $dir,
5852 $xl, $yl-($h-$fh)+$fh*$i,
5853 $this->current_color,$this->font_file,$tmp[$i]);
5857 // Draw the bounding rectangle around each line
5858 $box=ImageTTFBBox($this->font_size,$dir,$this->font_file,$tmp[$i]);
5860 for($j=0; $j < 4; ++$j) {
5861 $p[] = $bbox[$j*2]+$xl;
5862 $p[] = $bbox[$j*2+1]+$yl-($h-$fh)+$fh*$i;
5865 // Draw bounding rectangle
5866 $this->PushColor('darkgreen');
5867 $this->Polygon($p,true);
5874 // Draw a cross at the anchor point
5875 $this->PushColor('red');
5876 $this->Line($ox-25,$oy,$ox+25,$oy);
5877 $this->Line($ox,$oy-25,$ox,$oy+25);
5884 function StrokeText($x,$y,$txt,$dir=0,$paragraph_align="left",$debug=false) {
5889 // Do special language encoding
5890 $txt = $this->langconv->Convert($txt,$this->font_family);
5892 if( !is_numeric($dir) )
5893 JpGraphError::Raise(" Direction for text most be given as an angle between 0 and 90.");
5895 if( $this->font_family >= FF_FONT0 && $this->font_family <= FF_FONT2+1) {
5896 $this->_StrokeBuiltinFont($x,$y,$txt,$dir,$paragraph_align,$debug);
5898 elseif($this->font_family >= FF_COURIER && $this->font_family <= FF_BOOK) {
5899 $this->_StrokeTTF($x,$y,$txt,$dir,$paragraph_align,$debug);
5902 JpGraphError::Raise(" Unknown font font family specification. ");
5905 function SetMargin($lm,$rm,$tm,$bm) {
5906 $this->left_margin=$lm;
5907 $this->right_margin=$rm;
5908 $this->top_margin=$tm;
5909 $this->bottom_margin=$bm;
5910 $this->plotwidth=$this->width - $this->left_margin-$this->right_margin ;
5911 $this->plotheight=$this->height - $this->top_margin-$this->bottom_margin ;
5912 if( $this->plotwidth < 0 || $this->plotheight < 0 )
5913 JpGraphError::raise("To small plot area. ($lm,$rm,$tm,$bm : $this->plotwidth x $this->plotheight). With the given image size and margins there is to little space left for the plot. Increase the plot size or reduce the margins.");
5914 $this->NotifyObservers();
5917 function SetTransparent($color) {
5918 imagecolortransparent ($this->img,$this->rgb->allocate($color));
5921 function SetColor($color,$aAlpha=0) {
5922 $this->current_color_name = $color;
5923 $this->current_color=$this->rgb->allocate($color,$aAlpha);
5924 if( $this->current_color == -1 ) {
5925 $tc=imagecolorstotal($this->img);
5926 JpGraphError::Raise("Can't allocate any more colors.
5927 Image has already allocated maximum of <b>$tc colors</b>.
5928 This might happen if you have anti-aliasing turned on
5929 together with a background image or perhaps gradient fill
5930 since this requires many, many colors. Try to turn off
5932 If there is still a problem try downgrading the quality of
5933 the background image to use a smaller pallete to leave some
5934 entries for your graphs. You should try to limit the number
5935 of colors in your background image to 64.<p>
5936 If there is still problem set the constant
5938 DEFINE(\"USE_APPROX_COLORS\",true);
5940 in jpgraph.php This will use approximative colors
5941 when the palette is full.
5943 Unfortunately there is not much JpGraph can do about this
5944 since the palette size is a limitation of current graphic format and
5945 what the underlying GD library suppports.");
5947 return $this->current_color;
5950 function PushColor($color) {
5951 if( $color != "" ) {
5952 $this->colorstack[$this->colorstackidx]=$this->current_color_name;
5953 $this->colorstack[$this->colorstackidx+1]=$this->current_color;
5954 $this->colorstackidx+=2;
5955 $this->SetColor($color);
5958 JpGraphError::Raise("Color specified as empty string in PushColor().");
5962 function PopColor() {
5963 if($this->colorstackidx<1)
5964 JpGraphError::Raise(" Negative Color stack index. Unmatched call to PopColor()");
5965 $this->current_color=$this->colorstack[--$this->colorstackidx];
5966 $this->current_color_name=$this->colorstack[--$this->colorstackidx];
5970 // Why this duplication? Because this way we can call this method
5971 // for any image and not only the current objsct
5972 function AdjSat($sat) { $this->_AdjSat($this->img,$sat); }
5974 function _AdjSat($img,$sat) {
5975 $nbr = imagecolorstotal ($img);
5976 for( $i=0; $i<$nbr; ++$i ) {
5977 $colarr = imagecolorsforindex ($img,$i);
5978 $rgb[0]=$colarr["red"];
5979 $rgb[1]=$colarr["green"];
5980 $rgb[2]=$colarr["blue"];
5981 $rgb = $this->AdjRGBSat($rgb,$sat);
5982 imagecolorset ($img, $i, $rgb[0], $rgb[1], $rgb[2]);
5986 function AdjBrightContrast($bright,$contr=0) {
5987 $this->_AdjBrightContrast($this->img,$bright,$contr);
5989 function _AdjBrightContrast($img,$bright,$contr=0) {
5990 if( $bright < -1 || $bright > 1 || $contr < -1 || $contr > 1 )
5991 JpGraphError::Raise(" Parameters for brightness and Contrast out of range [-1,1]");
5992 $nbr = imagecolorstotal ($img);
5993 for( $i=0; $i<$nbr; ++$i ) {
5994 $colarr = imagecolorsforindex ($img,$i);
5995 $r = $this->AdjRGBBrightContrast($colarr["red"],$bright,$contr);
5996 $g = $this->AdjRGBBrightContrast($colarr["green"],$bright,$contr);
5997 $b = $this->AdjRGBBrightContrast($colarr["blue"],$bright,$contr);
5998 imagecolorset ($img, $i, $r, $g, $b);
6002 // Private helper function for adj sat
6003 // Adjust saturation for RGB array $u. $sat is a value between -1 and 1
6004 // Note: Due to GD inability to handle true color the RGB values are only between
6005 // 8 bit. This makes saturation quite sensitive for small increases in parameter sat.
6007 // Tip: To get a grayscale picture set sat=-100, values <-100 changes the colors
6008 // to it's complement.
6010 // Implementation note: The saturation is implemented directly in the RGB space
6011 // by adjusting the perpendicular distance between the RGB point and the "grey"
6012 // line (1,1,1). Setting $sat>0 moves the point away from the line along the perp.
6013 // distance and a negative value moves the point closer to the line.
6014 // The values are truncated when the color point hits the bounding box along the
6016 // DISCLAIMER: I'm not 100% sure this is he correct way to implement a color
6017 // saturation function in RGB space. However, it looks ok and has the expected effect.
6018 function AdjRGBSat($rgb,$sat) {
6019 // TODO: Should be moved to the RGB class
6024 $dot = $rgb[0]*$v[0]+$rgb[1]*$v[1]+$rgb[2]*$v[2];
6026 // Normalize dot product
6027 $normdot = $dot/3; // dot/|v|^2
6029 // Direction vector between $u and its projection onto $v
6030 for($i=0; $i<3; ++$i)
6031 $r[$i] = $rgb[$i] - $normdot*$v[$i];
6033 // Adjustment factor so that sat==1 sets the highest RGB value to 255
6036 for( $i=0; $i<3; ++$i) {
6037 if( sign($r[$i]) == 1 && $r[$i]>0)
6038 $m=max($m,(255-$rgb[$i])/$r[$i]);
6046 for($i=0; $i<3; ++$i) {
6047 $un[$i] = round($rgb[$i] + $tadj*$r[$i]);
6048 if( $un[$i]<0 ) $un[$i]=0; // Truncate color when they reach 0
6049 if( $un[$i]>255 ) $un[$i]=255;// Avoid potential rounding error
6054 // Private helper function for AdjBrightContrast
6055 function AdjRGBBrightContrast($rgb,$bright,$contr) {
6056 // TODO: Should be moved to the RGB class
6057 // First handle contrast, i.e change the dynamic range around grey
6059 // Decrease contrast
6060 $adj = abs($rgb-128) * (-$contr);
6061 if( $rgb < 128 ) $rgb += $adj;
6064 else { // $contr > 0
6065 // Increase contrast
6066 if( $rgb < 128 ) $rgb = $rgb - ($rgb * $contr);
6067 else $rgb = $rgb + ((255-$rgb) * $contr);
6070 // Add (or remove) various amount of white
6071 $rgb += $bright*255;
6077 function SetLineWeight($weight) {
6078 $this->line_weight = $weight;
6081 function SetStartPoint($x,$y) {
6082 $this->lastx=round($x);
6083 $this->lasty=round($y);
6086 function Arc($cx,$cy,$w,$h,$s,$e) {
6087 // GD Arc doesn't like negative angles
6088 while( $s < 0) $s += 360;
6089 while( $e < 0) $e += 360;
6091 imagearc($this->img,round($cx),round($cy),round($w),round($h),
6092 $s,$e,$this->current_color);
6095 function FilledArc($xc,$yc,$w,$h,$s,$e,$style="") {
6097 if( $GLOBALS['gd2'] ) {
6098 while( $s < 0 ) $s += 360;
6099 while( $e < 0 ) $e += 360;
6102 imagefilledarc($this->img,round($xc),round($yc),round($w),round($h),
6103 round($s),round($e),$this->current_color,$style);
6108 // In GD 1.x we have to do it ourself interesting enough there is surprisingly
6109 // little difference in time between doing it PHP and using the optimised GD
6110 // library (roughly ~20%) I had expected it to be at least 100% slower doing it
6111 // manually with a polygon approximation in PHP.....
6112 $fillcolor = $this->current_color_name;
6114 $w /= 2; // We use radius in our calculations instead
6117 // Setup the angles so we have the same conventions as the builtin
6118 // FilledArc() which is a little bit strange if you ask me....
6134 // Calculate a polygon approximation
6138 // Heuristic on how many polygons we need to make the
6140 $numsteps = round(8 * abs($da) * ($w+$h)*($w+$h)/1500);
6142 if( $numsteps == 0 ) return;
6143 if( $numsteps < 7 ) $numsteps=7;
6144 $delta = abs($da)/$numsteps;
6148 for($i=1; $i<=$numsteps; ++$i ) {
6149 $p[2*$i] = round($xc + $w*cos($a));
6150 $p[2*$i+1] = round($yc - $h*sin($a));
6151 //$a = $s + $i*$delta;
6153 $pa[2*($i-1)] = $p[2*$i];
6154 $pa[2*($i-1)+1] = $p[2*$i+1];
6157 // Get the last point at the exact ending angle to avoid
6158 // any rounding errors.
6159 $p[2*$i] = round($xc + $w*cos($e));
6160 $p[2*$i+1] = round($yc - $h*sin($e));
6161 $pa[2*($i-1)] = $p[2*$i];
6162 $pa[2*($i-1)+1] = $p[2*$i+1];
6167 if( $fillcolor != "" ) {
6168 $this->PushColor($fillcolor);
6169 imagefilledpolygon($this->img,$p,count($p)/2,$this->current_color);
6174 function FilledCakeSlice($cx,$cy,$w,$h,$s,$e) {
6175 $this->CakeSlice($cx,$cy,$w,$h,$s,$e,$this->current_color_name);
6178 function CakeSlice($xc,$yc,$w,$h,$s,$e,$fillcolor="",$arccolor="") {
6179 $s = round($s); $e = round($e);
6180 $w = round($w); $h = round($h);
6181 $xc = round($xc); $yc = round($yc);
6182 $this->PushColor($fillcolor);
6183 $this->FilledArc($xc,$yc,2*$w,2*$h,$s,$e);
6185 if( $arccolor != "" ) {
6186 $this->PushColor($arccolor);
6187 // We add 2 pixels to make the Arc() better aligned with
6189 if( $GLOBALS['gd2'] ) {
6190 imagefilledarc($this->img,$xc,$yc,2*$w,2*$h,$s,$e,$this->current_color_name,
6191 IMG_ARC_NOFILL | IMG_ARC_EDGED ) ;
6194 $this->Arc($xc,$yc,2*$w+2,2*$h+2,$s,$e);
6195 $xx = $w * cos(2*M_PI - $s*M_PI/180) + $xc;
6196 $yy = $yc - $h * sin(2*M_PI - $s*M_PI/180);
6197 $this->Line($xc,$yc,$xx,$yy);
6198 $xx = $w * cos(2*M_PI - $e*M_PI/180) + $xc;
6199 $yy = $yc - $h * sin(2*M_PI - $e*M_PI/180);
6200 $this->Line($xc,$yc,$xx,$yy);
6206 function Ellipse($xc,$yc,$w,$h) {
6207 $this->Arc($xc,$yc,$w,$h,0,360);
6210 // Breseham circle gives visually better result then using GD
6211 // built in arc(). It takes some more time but gives better
6213 function BresenhamCircle($xc,$yc,$r) {
6218 $this->Point($xc+$x,$yc+$y);
6219 $this->Point($xc+$x,$yc-$y);
6220 $this->Point($xc-$x,$yc+$y);
6221 $this->Point($xc-$x,$yc-$y);
6223 $this->Point($xc+$y,$yc+$x);
6224 $this->Point($xc+$y,$yc-$x);
6225 $this->Point($xc-$y,$yc+$x);
6226 $this->Point($xc-$y,$yc-$x);
6228 if( $d<0 ) $d += 4*$x+6;
6237 function Circle($xc,$yc,$r) {
6239 $this->BresenhamCircle($xc,$yc,$r);
6243 // Some experimental code snippet to see if we can get a decent
6244 // result doing a trig-circle
6245 // Create an approximated circle with 0.05 rad resolution
6249 $step_size = 2*M_PI/(2*$r*M_PI/$l);
6253 for( $a=$step_size; $a <= $end; $a += $step_size ) {
6254 $pts[] = round($xc + $r*cos($a));
6255 $pts[] = round($yc - $r*sin($a));
6257 imagepolygon($this->img,$pts,count($pts)/2,$this->current_color);
6260 $this->Arc($xc,$yc,$r*2,$r*2,0,360);
6262 // For some reason imageellipse() isn't in GD 2.0.1, PHP 4.1.1
6263 //imageellipse($this->img,$xc,$yc,$r,$r,$this->current_color);
6267 function FilledCircle($xc,$yc,$r) {
6268 if( $GLOBALS['gd2'] ) {
6269 imagefilledellipse($this->img,round($xc),round($yc),
6270 2*$r,2*$r,$this->current_color);
6273 for( $i=1; $i < 2*$r; $i += 2 ) {
6274 // To avoid moire patterns we have to draw some
6275 // 1 extra "skewed" filled circles
6276 $this->Arc($xc,$yc,$i,$i,0,360);
6277 $this->Arc($xc,$yc,$i+1,$i,0,360);
6278 $this->Arc($xc,$yc,$i+1,$i+1,0,360);
6283 // Linear Color InterPolation
6284 function lip($f,$t,$p) {
6286 $r = $f[0] + ($t[0]-$f[0])*$p;
6287 $g = $f[1] + ($t[1]-$f[1])*$p;
6288 $b = $f[2] + ($t[2]-$f[2])*$p;
6289 return array($r,$g,$b);
6292 // Anti-aliased line.
6293 // Note that this is roughly 8 times slower then a normal line!
6294 function WuLine($x1,$y1,$x2,$y2) {
6295 // Get foreground line color
6296 $lc = imagecolorsforindex($this->img,$this->current_color);
6297 $lc = array($lc["red"],$lc["green"],$lc["blue"]);
6302 if( abs($dx) > abs($dy) ) {
6304 $dx = -$dx;$dy = -$dy;
6305 $tmp=$x2;$x2=$x1;$x1=$tmp;
6306 $tmp=$y2;$y2=$y1;$y1=$tmp;
6308 $x=$x1<<16; $y=$y1<<16;
6309 $yinc = ($dy*65535)/$dx;
6310 while( ($x >> 16) < $x2 ) {
6312 $bc = @imagecolorsforindex($this->img,imagecolorat($this->img,$x>>16,$y>>16));
6314 JpGraphError::Raise('Problem with color palette and your GD setup. Please disable anti-aliasing or use GD2 with true-color. If you have GD2 library installed please make sure that you have set the USE_GD2 constant to true and that truecolor is enabled.');
6316 $bc=array($bc["red"],$bc["green"],$bc["blue"]);
6318 $this->SetColor($this->lip($lc,$bc,($y & 0xFFFF)/65535));
6319 imagesetpixel($this->img,$x>>16,$y>>16,$this->current_color);
6320 $this->SetColor($this->lip($lc,$bc,(~$y & 0xFFFF)/65535));
6321 imagesetpixel($this->img,$x>>16,($y>>16)+1,$this->current_color);
6322 $x += 65536; $y += $yinc;
6327 $dx = -$dx;$dy = -$dy;
6328 $tmp=$x2;$x2=$x1;$x1=$tmp;
6329 $tmp=$y2;$y2=$y1;$y1=$tmp;
6331 $x=$x1<<16; $y=$y1<<16;
6332 $xinc = ($dx*65535)/$dy;
6333 while( ($y >> 16) < $y2 ) {
6335 $bc = @imagecolorsforindex($this->img,imagecolorat($this->img,$x>>16,$y>>16));
6337 JpGraphError::Raise('Problem with color palette and your GD setup. Please disable anti-aliasing or use GD2 with true-color. If you have GD2 library installed please make sure that you have set the USE_GD2 constant to true and truecolor is enabled.');
6341 $bc=array($bc["red"],$bc["green"],$bc["blue"]);
6343 $this->SetColor($this->lip($lc,$bc,($x & 0xFFFF)/65535));
6344 imagesetpixel($this->img,$x>>16,$y>>16,$this->current_color);
6345 $this->SetColor($this->lip($lc,$bc,(~$x & 0xFFFF)/65535));
6346 imagesetpixel($this->img,($x>>16)+1,$y>>16,$this->current_color);
6347 $y += 65536; $x += $xinc;
6350 $this->SetColor($lc);
6351 imagesetpixel($this->img,$x2,$y2,$this->current_color);
6352 imagesetpixel($this->img,$x1,$y1,$this->current_color);
6355 // Set line style dashed, dotted etc
6356 function SetLineStyle($s) {
6357 if( is_numeric($s) ) {
6359 JpGraphError::Raise(" Illegal numeric argument to SetLineStyle(): ($s)");
6361 elseif( is_string($s) ) {
6362 if( $s == "solid" ) $s=1;
6363 elseif( $s == "dotted" ) $s=2;
6364 elseif( $s == "dashed" ) $s=3;
6365 elseif( $s == "longdashed" ) $s=4;
6366 else JpGraphError::Raise(" Illegal string argument to SetLineStyle(): $s");
6368 else JpGraphError::Raise(" Illegal argument to SetLineStyle $s");
6369 $this->line_style=$s;
6372 // Same as Line but take the line_style into account
6373 function StyleLine($x1,$y1,$x2,$y2) {
6374 switch( $this->line_style ) {
6376 $this->Line($x1,$y1,$x2,$y2);
6379 $this->DashedLine($x1,$y1,$x2,$y2,1,6);
6382 $this->DashedLine($x1,$y1,$x2,$y2,2,4);
6384 case 4: // Longdashes
6385 $this->DashedLine($x1,$y1,$x2,$y2,8,6);
6388 JpGraphError::Raise(" Unknown line style: $this->line_style ");
6393 function Line($x1,$y1,$x2,$y2) {
6400 if( $this->line_weight==0 ) return;
6401 if( $this->use_anti_aliasing ) {
6404 // Vertical, Horizontal or 45 lines don't need anti-aliasing
6405 if( $dx!=0 && $dy!=0 && $dx!=$dy ) {
6406 $this->WuLine($x1,$y1,$x2,$y2);
6410 if( $this->line_weight==1 ) {
6411 imageline($this->img,$x1,$y1,$x2,$y2,$this->current_color);
6413 elseif( $x1==$x2 ) { // Special case for vertical lines
6414 imageline($this->img,$x1,$y1,$x2,$y2,$this->current_color);
6415 $w1=floor($this->line_weight/2);
6416 $w2=floor(($this->line_weight-1)/2);
6417 for($i=1; $i<=$w1; ++$i)
6418 imageline($this->img,$x1+$i,$y1,$x2+$i,$y2,$this->current_color);
6419 for($i=1; $i<=$w2; ++$i)
6420 imageline($this->img,$x1-$i,$y1,$x2-$i,$y2,$this->current_color);
6422 elseif( $y1==$y2 ) { // Special case for horizontal lines
6423 imageline($this->img,$x1,$y1,$x2,$y2,$this->current_color);
6424 $w1=floor($this->line_weight/2);
6425 $w2=floor(($this->line_weight-1)/2);
6426 for($i=1; $i<=$w1; ++$i)
6427 imageline($this->img,$x1,$y1+$i,$x2,$y2+$i,$this->current_color);
6428 for($i=1; $i<=$w2; ++$i)
6429 imageline($this->img,$x1,$y1-$i,$x2,$y2-$i,$this->current_color);
6431 else { // General case with a line at an angle
6432 $a = atan2($y1-$y2,$x2-$x1);
6433 // Now establish some offsets from the center. This gets a little
6434 // bit involved since we are dealing with integer functions and we
6435 // want the apperance to be as smooth as possible and never be thicker
6436 // then the specified width.
6438 // We do the trig stuff to make sure that the endpoints of the line
6439 // are perpendicular to the line itself.
6440 $dx=(sin($a)*$this->line_weight/2);
6441 $dy=(cos($a)*$this->line_weight/2);
6443 $pnts = array($x2+$dx,$y2+$dy,$x2-$dx,$y2-$dy,$x1-$dx,$y1-$dy,$x1+$dx,$y1+$dy);
6444 imagefilledpolygon($this->img,$pnts,count($pnts)/2,$this->current_color);
6446 $this->lastx=$x2; $this->lasty=$y2;
6449 function Polygon($p,$closed=FALSE) {
6450 if( $this->line_weight==0 ) return;
6454 for( $i=2; $i < $n; $i+=2 ) {
6455 $this->Line($oldx,$oldy,$p[$i],$p[$i+1]);
6460 $this->Line($oldx,$oldy,$p[0],$p[1]);
6463 function FilledPolygon($pts) {
6466 JpGraphError::Raise('Internal error: Calling Image::FilledPolygon() with no polygon');
6467 for($i=0; $i < $n; ++$i)
6468 $pts[$i] = round($pts[$i]);
6469 imagefilledpolygon($this->img,$pts,count($pts)/2,$this->current_color);
6472 function Rectangle($xl,$yu,$xr,$yl) {
6473 $this->Polygon(array($xl,$yu,$xr,$yu,$xr,$yl,$xl,$yl,$xl,$yu));
6476 function FilledRectangle($xl,$yu,$xr,$yl) {
6477 $this->FilledPolygon(array($xl,$yu,$xr,$yu,$xr,$yl,$xl,$yl));
6480 function FilledRectangle2($xl,$yu,$xr,$yl,$color1,$color2,$style=1) {
6481 // Fill a rectangle with lines of two colors
6483 // Horizontal stripe
6485 $t = $yl; $yl=$yu; $yu=$t;
6487 for( $y=$yu; $y <= $yl; ++$y) {
6488 $this->SetColor($color1);
6489 $this->Line($xl,$y,$xr,$y);
6491 $this->SetColor($color2);
6492 $this->Line($xl,$y,$xr,$y);
6497 $t = $xl; $xl=$xr; $xr=$t;
6499 for( $x=$xl; $x <= $xr; ++$x) {
6500 $this->SetColor($color1);
6501 $this->Line($x,$yu,$x,$yl);
6503 $this->SetColor($color2);
6504 $this->Line($x,$yu,$x,$yl);
6509 function ShadowRectangle($xl,$yu,$xr,$yl,$fcolor=false,$shadow_width=3,$shadow_color=array(102,102,102)) {
6510 // This is complicated by the fact that we must also handle the case where
6511 // the reactangle has no fill color
6512 $this->PushColor($shadow_color);
6513 $this->FilledRectangle($xr-$shadow_width,$yu+$shadow_width,$xr,$yl-$shadow_width-1);
6514 $this->FilledRectangle($xl+$shadow_width,$yl-$shadow_width,$xr,$yl);
6515 //$this->FilledRectangle($xl+$shadow_width,$yu+$shadow_width,$xr,$yl);
6517 if( $fcolor==false )
6518 $this->Rectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1);
6520 $this->PushColor($fcolor);
6521 $this->FilledRectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1);
6523 $this->Rectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1);
6527 function FilledRoundedRectangle($xt,$yt,$xr,$yl,$r=5) {
6529 $this->FilledRectangle($xt,$yt,$xr,$yl);
6533 // To avoid overlapping fillings (which will look strange
6534 // when alphablending is enabled) we have no choice but
6535 // to fill the five distinct areas one by one.
6538 $this->FilledRectangle($xt+$r,$yt+$r,$xr-$r,$yl-$r);
6540 $this->FilledRectangle($xt+$r,$yt,$xr-$r,$yt+$r-1);
6542 $this->FilledRectangle($xt+$r,$yl-$r+1,$xr-$r,$yl);
6544 $this->FilledRectangle($xt,$yt+$r+1,$xt+$r-1,$yl-$r);
6546 $this->FilledRectangle($xr-$r+1,$yt+$r,$xr,$yl-$r);
6548 // Topleft & Topright arc
6549 $this->FilledArc($xt+$r,$yt+$r,$r*2,$r*2,180,270);
6550 $this->FilledArc($xr-$r,$yt+$r,$r*2,$r*2,270,360);
6552 // Bottomleft & Bottom right arc
6553 $this->FilledArc($xt+$r,$yl-$r,$r*2,$r*2,90,180);
6554 $this->FilledArc($xr-$r,$yl-$r,$r*2,$r*2,0,90);
6558 function RoundedRectangle($xt,$yt,$xr,$yl,$r=5) {
6561 $this->Rectangle($xt,$yt,$xr,$yl);
6565 // Top & Bottom line
6566 $this->Line($xt+$r,$yt,$xr-$r,$yt);
6567 $this->Line($xt+$r,$yl,$xr-$r,$yl);
6569 // Left & Right line
6570 $this->Line($xt,$yt+$r,$xt,$yl-$r);
6571 $this->Line($xr,$yt+$r,$xr,$yl-$r);
6573 // Topleft & Topright arc
6574 $this->Arc($xt+$r,$yt+$r,$r*2,$r*2,180,270);
6575 $this->Arc($xr-$r,$yt+$r,$r*2,$r*2,270,360);
6577 // Bottomleft & Bottomright arc
6578 $this->Arc($xt+$r,$yl-$r,$r*2,$r*2,90,180);
6579 $this->Arc($xr-$r,$yl-$r,$r*2,$r*2,0,90);
6582 function FilledBevel($x1,$y1,$x2,$y2,$depth=2,$color1='white@0.4',$color2='darkgray@0.4') {
6583 $this->FilledRectangle($x1,$y1,$x2,$y2);
6584 $this->Bevel($x1,$y1,$x2,$y2,$depth,$color1,$color2);
6587 function Bevel($x1,$y1,$x2,$y2,$depth=2,$color1='white@0.4',$color2='black@0.5') {
6588 $this->PushColor($color1);
6589 for( $i=0; $i < $depth; ++$i ) {
6590 $this->Line($x1+$i,$y1+$i,$x1+$i,$y2-$i);
6591 $this->Line($x1+$i,$y1+$i,$x2-$i,$y1+$i);
6595 $this->PushColor($color2);
6596 for( $i=0; $i < $depth; ++$i ) {
6597 $this->Line($x1+$i,$y2-$i,$x2-$i,$y2-$i);
6598 $this->Line($x2-$i,$y1+$i,$x2-$i,$y2-$i-1);
6603 function StyleLineTo($x,$y) {
6604 $this->StyleLine($this->lastx,$this->lasty,$x,$y);
6609 function LineTo($x,$y) {
6610 $this->Line($this->lastx,$this->lasty,$x,$y);
6615 function Point($x,$y) {
6616 imagesetpixel($this->img,round($x),round($y),$this->current_color);
6619 function Fill($x,$y) {
6620 imagefill($this->img,round($x),round($y),$this->current_color);
6623 function FillToBorder($x,$y,$aBordColor) {
6624 $bc = $this->rgb->allocate($aBordColor);
6626 JpGraphError::Raise('Image::FillToBorder : Can not allocate more colors');
6629 imagefilltoborder($this->img,round($x),round($y),$bc,$this->current_color);
6632 function DashedLine($x1,$y1,$x2,$y2,$dash_length=1,$dash_space=4) {
6633 // Code based on, but not identical to, work by Ariel Garza and James Pine
6634 $line_length = ceil (sqrt(pow(($x2 - $x1),2) + pow(($y2 - $y1),2)) );
6635 $dx = ($line_length) ? ($x2 - $x1) / $line_length : 0;
6636 $dy = ($line_length) ? ($y2 - $y1) / $line_length : 0;
6637 $lastx = $x1; $lasty = $y1;
6638 $xmax = max($x1,$x2);
6639 $xmin = min($x1,$x2);
6640 $ymax = max($y1,$y2);
6641 $ymin = min($y1,$y2);
6642 for ($i = 0; $i < $line_length; $i += ($dash_length + $dash_space)) {
6643 $x = ($dash_length * $dx) + $lastx;
6644 $y = ($dash_length * $dy) + $lasty;
6646 // The last section might overshoot so we must take a computational hit
6648 if( $x>$xmax ) $x=$xmax;
6649 if( $y>$ymax ) $y=$ymax;
6651 if( $x<$xmin ) $x=$xmin;
6652 if( $y<$ymin ) $y=$ymin;
6654 $this->Line($lastx,$lasty,$x,$y);
6655 $lastx = $x + ($dash_space * $dx);
6656 $lasty = $y + ($dash_space * $dy);
6660 function SetExpired($aFlg=true) {
6661 $this->expired = $aFlg;
6664 // Generate image header
6665 function Headers() {
6666 if ($this->expired) {
6667 header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
6668 header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT");
6669 header("Cache-Control: no-cache, must-revalidate");
6670 header("Pragma: no-cache");
6672 header("Content-type: image/$this->img_format");
6675 // Adjust image quality for formats that allow this
6676 function SetQuality($q) {
6677 $this->quality = $q;
6680 // Stream image to browser or to file
6681 function Stream($aFile="") {
6682 $func="image".$this->img_format;
6683 if( $this->img_format=="jpeg" && $this->quality != null ) {
6684 $res = @$func($this->img,$aFile,$this->quality);
6687 if( $aFile != "" ) {
6688 $res = @$func($this->img,$aFile);
6691 $res = @$func($this->img);
6694 JpGraphError::Raise("Can't create or stream image to file $aFile Check that PHP has enough permission to write a file to the current directory.");
6697 // Clear resource tide up by image
6698 function Destroy() {
6699 imagedestroy($this->img);
6702 // Specify image format. Note depending on your installation
6703 // of PHP not all formats may be supported.
6704 function SetImgFormat($aFormat) {
6705 $aFormat = strtolower($aFormat);
6707 $supported = imagetypes();
6708 if( $aFormat=="auto" ) {
6709 if( $supported & IMG_PNG )
6710 $this->img_format="png";
6711 elseif( $supported & IMG_JPG )
6712 $this->img_format="jpeg";
6713 elseif( $supported & IMG_GIF )
6714 $this->img_format="gif";
6716 JpGraphError::Raise(" Your PHP (and GD-lib) installation does not appear to support any known graphic formats.".
6717 "You need to first make sure GD is compiled as a module to PHP. If you also want to use JPEG images".
6718 "you must get the JPEG library. Please see the PHP docs for details.");
6723 if( $aFormat=="jpeg" || $aFormat=="png" || $aFormat=="gif" ) {
6724 if( $aFormat=="jpeg" && !($supported & IMG_JPG) )
6726 elseif( $aFormat=="png" && !($supported & IMG_PNG) )
6728 elseif( $aFormat=="gif" && !($supported & IMG_GIF) )
6731 $this->img_format=$aFormat;
6738 JpGraphError::Raise(" Your PHP installation does not support the chosen graphic format: $aFormat");
6743 //===================================================
6745 // Description: Exactly as Image but draws the image at
6746 // a specified angle around a specified rotation point.
6747 //===================================================
6748 class RotImage extends Image {
6751 var $dx=0,$dy=0,$transx=0,$transy=0;
6753 function RotImage($aWidth,$aHeight,$a=0,$aFormat=DEFAULT_GFORMAT) {
6754 $this->Image($aWidth,$aHeight,$aFormat);
6755 $this->dx=$this->left_margin+$this->plotwidth/2;
6756 $this->dy=$this->top_margin+$this->plotheight/2;
6757 $this->SetAngle($a);
6760 function SetCenter($dx,$dy) {
6761 $old_dx = $this->dx;
6762 $old_dy = $this->dy;
6765 $this->SetAngle($this->a);
6766 return array($old_dx,$old_dy);
6769 function SetTranslation($dx,$dy) {
6770 $old = array($this->transx,$this->transy);
6771 $this->transx = $dx;
6772 $this->transy = $dy;
6776 function UpdateRotMatrice() {
6779 $sa=sin($a); $ca=cos($a);
6780 // Create the rotation matrix
6781 $this->m[0][0] = $ca;
6782 $this->m[0][1] = -$sa;
6783 $this->m[0][2] = $this->dx*(1-$ca) + $sa*$this->dy ;
6784 $this->m[1][0] = $sa;
6785 $this->m[1][1] = $ca;
6786 $this->m[1][2] = $this->dy*(1-$ca) - $sa*$this->dx ;
6789 function SetAngle($a) {
6792 $this->UpdateRotMatrice();
6796 function Circle($xc,$yc,$r) {
6797 // Circle get's rotated through the Arc() call
6798 // made in the parent class
6799 parent::Circle($xc,$yc,$r);
6802 function FilledCircle($xc,$yc,$r) {
6803 // If we use GD1 then Image::FilledCircle will use a
6804 // call to Arc so it will get rotated through the Arc
6806 if( $GLOBALS['gd2'] ) {
6807 list($xc,$yc) = $this->Rotate($xc,$yc);
6809 parent::FilledCircle($xc,$yc,$r);
6813 function Arc($xc,$yc,$w,$h,$s,$e) {
6814 list($xc,$yc) = $this->Rotate($xc,$yc);
6817 parent::Arc($xc,$yc,$w,$h,$s,$e);
6820 function FilledArc($xc,$yc,$w,$h,$s,$e) {
6821 list($xc,$yc) = $this->Rotate($xc,$yc);
6824 parent::FilledArc($xc,$yc,$w,$h,$s,$e);
6827 function SetMargin($lm,$rm,$tm,$bm) {
6828 parent::SetMargin($lm,$rm,$tm,$bm);
6829 $this->dx=$this->left_margin+$this->plotwidth/2;
6830 $this->dy=$this->top_margin+$this->plotheight/2;
6831 $this->UpdateRotMatrice();
6834 function Rotate($x,$y) {
6835 // Optimization. Ignore rotation if Angle==0 || ANgle==360
6836 if( $this->a == 0 || $this->a == 360 ) {
6837 return array($x + $this->transx, $y + $this->transy );
6840 $x1=round($this->m[0][0]*$x + $this->m[0][1]*$y,1) + $this->m[0][2] + $this->transx;
6841 $y1=round($this->m[1][0]*$x + $this->m[1][1]*$y,1) + $this->m[1][2] + $this->transy;
6842 return array($x1,$y1);
6846 function ArrRotate($pnts) {
6847 for($i=0; $i < count($pnts)-1; $i+=2)
6848 list($pnts[$i],$pnts[$i+1]) = $this->Rotate($pnts[$i],$pnts[$i+1]);
6852 function Line($x1,$y1,$x2,$y2) {
6853 list($x1,$y1) = $this->Rotate($x1,$y1);
6854 list($x2,$y2) = $this->Rotate($x2,$y2);
6855 parent::Line($x1,$y1,$x2,$y2);
6858 function Rectangle($x1,$y1,$x2,$y2) {
6859 // Rectangle uses Line() so it will be rotated through that call
6860 parent::Rectangle($x1,$y1,$x2,$y2);
6863 function FilledRectangle($x1,$y1,$x2,$y2) {
6864 if( $y1==$y2 || $x1==$x2 )
6865 $this->Line($x1,$y1,$x2,$y2);
6867 $this->FilledPolygon(array($x1,$y1,$x2,$y1,$x2,$y2,$x1,$y2));
6870 function Polygon($pnts,$closed=FALSE) {
6871 //Polygon uses Line() so it will be rotated through that call
6872 parent::Polygon($pnts,$closed);
6875 function FilledPolygon($pnts) {
6876 parent::FilledPolygon($this->ArrRotate($pnts));
6879 function Point($x,$y) {
6880 list($xp,$yp) = $this->Rotate($x,$y);
6881 parent::Point($xp,$yp);
6884 function StrokeText($x,$y,$txt,$dir=0,$paragraph_align="left",$debug=false) {
6885 list($xp,$yp) = $this->Rotate($x,$y);
6886 parent::StrokeText($xp,$yp,$txt,$dir,$paragraph_align,$debug);
6890 //===================================================
6891 // CLASS ImgStreamCache
6892 // Description: Handle caching of graphs to files
6893 //===================================================
6894 class ImgStreamCache {
6897 var $timeout=0; // Infinite timeout
6900 function ImgStreamCache(&$aImg, $aCacheDir=CACHE_DIR) {
6901 $this->img = &$aImg;
6902 $this->cache_dir = $aCacheDir;
6908 // Specify a timeout (in minutes) for the file. If the file is older then the
6909 // timeout value it will be overwritten with a newer version.
6910 // If timeout is set to 0 this is the same as infinite large timeout and if
6911 // timeout is set to -1 this is the same as infinite small timeout
6912 function SetTimeout($aTimeout) {
6913 $this->timeout=$aTimeout;
6916 // Output image to browser and also write it to the cache
6917 function PutAndStream(&$aImage,$aCacheFileName,$aInline,$aStrokeFileName) {
6918 // Some debugging code to brand the image with numbe of colors
6920 GLOBAL $gJpgBrandTiming;
6922 if( $gJpgBrandTiming ) {
6924 $t=$tim->Pop()/1000.0;
6925 $c=$aImage->SetColor("black");
6926 $t=sprintf(BRAND_TIME_FORMAT,round($t,3));
6927 imagestring($this->img->img,2,5,$this->img->height-20,$t,$c);
6930 // Check if we should stroke the image to an arbitrary file
6931 if( $aStrokeFileName!="" ) {
6932 if( $aStrokeFileName == "auto" )
6933 $aStrokeFileName = GenImgName();
6934 if( file_exists($aStrokeFileName) ) {
6935 // Delete the old file
6936 if( !@unlink($aStrokeFileName) )
6937 JpGraphError::Raise(" Can't delete cached image $aStrokeFileName. Permission problem?");
6939 $aImage->Stream($aStrokeFileName);
6943 if( $aCacheFileName != "" && USE_CACHE) {
6945 $aCacheFileName = $this->cache_dir . $aCacheFileName;
6946 if( file_exists($aCacheFileName) ) {
6948 // If we are generating image off-line (just writing to the cache)
6949 // and the file exists and is still valid (no timeout)
6950 // then do nothing, just return.
6951 $diff=time()-filemtime($aCacheFileName);
6953 JpGraphError::Raise(" Cached imagefile ($aCacheFileName) has file date in the future!!");
6954 if( $this->timeout>0 && ($diff <= $this->timeout*60) )
6957 if( !@unlink($aCacheFileName) )
6958 JpGraphError::Raise(" Can't delete cached image $aStrokeFileName. Permission problem?");
6959 $aImage->Stream($aCacheFileName);
6962 $this->MakeDirs(dirname($aCacheFileName));
6963 if( !is_writeable(dirname($aCacheFileName)) ) {
6964 JpGraphError::Raise('PHP has not enough permissions to write to the cache file '.$aCacheFileName.'. Please make sure that the user running PHP has write permission for this file if you wan to use the cache system with JpGraph.');
6966 $aImage->Stream($aCacheFileName);
6970 // Set group to specified
6971 if( CACHE_FILE_GROUP != "" )
6972 $res = @chgrp($aCacheFileName,CACHE_FILE_GROUP);
6973 if( CACHE_FILE_MOD != "" )
6974 $res = @chmod($aCacheFileName,CACHE_FILE_MOD);
6976 JpGraphError::Raise(" Can't set permission for cached image $aStrokeFileName. Permission problem?");
6980 if ($fh = @fopen($aCacheFileName, "rb") ) {
6981 $this->img->Headers();
6986 JpGraphError::Raise(" Cant open file from cache [$aFile]");
6989 elseif( $aInline ) {
6990 $this->img->Headers();
6996 // Check if a given image is in cache and in that case
6997 // pass it directly on to web browser. Return false if the
6998 // image file doesn't exist or exists but is to old
6999 function GetAndStream($aCacheFileName) {
7000 $aCacheFileName = $this->cache_dir.$aCacheFileName;
7001 if ( USE_CACHE && file_exists($aCacheFileName) && $this->timeout>=0 ) {
7002 $diff=time()-filemtime($aCacheFileName);
7003 if( $this->timeout>0 && ($diff > $this->timeout*60) ) {
7007 if ($fh = @fopen($aCacheFileName, "rb")) {
7008 $this->img->Headers();
7013 JpGraphError::Raise(" Can't open cached image \"$aCacheFileName\" for reading.");
7021 // Create all necessary directories in a path
7022 function MakeDirs($aFile) {
7024 while ( !(file_exists($aFile)) ) {
7026 $aFile = dirname($aFile);
7028 for ($i = sizeof($dirs)-1; $i>=0; $i--) {
7029 if(! @mkdir($dirs[$i],0777) )
7030 JpGraphError::Raise(" Can't create directory $aFile. Make sure PHP has write permission to this directory.");
7031 // We also specify mode here after we have changed group.
7032 // This is necessary if Apache user doesn't belong the
7033 // default group and hence can't specify group permission
7034 // in the previous mkdir() call
7035 if( CACHE_FILE_GROUP != "" ) {
7037 $res =@chgrp($dirs[$i],CACHE_FILE_GROUP);
7038 $res &= @chmod($dirs[$i],0777);
7040 JpGraphError::Raise(" Can't set permissions for $aFile. Permission problems?");
7047 //===================================================
7049 // Description: Responsible for drawing the box containing
7050 // all the legend text for the graph
7051 //===================================================
7052 DEFINE('_DEFAULT_LPM_SIZE',8);
7054 var $color=array(0,0,0); // Default fram color
7055 var $fill_color=array(235,235,235); // Default fill color
7056 var $shadow=true; // Shadow around legend "box"
7057 var $shadow_color='gray';
7058 var $txtcol=array();
7059 var $mark_abs_size=_DEFAULT_LPM_SIZE;
7060 var $xmargin=10,$ymargin=6,$shadow_width=2;
7061 var $xpos=0.05, $ypos=0.15, $xabspos=-1, $yabspos=-1;
7062 var $halign="right", $valign="top";
7063 var $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12;
7064 var $font_color='black';
7065 var $hide=false,$layout_n=1;
7066 var $weight=1,$frameweight=1;
7068 var $reverse = false ;
7076 function Hide($aHide=true) {
7080 function SetShadow($aShow='gray',$aWidth=2) {
7081 if( is_string($aShow) ) {
7082 $this->shadow_color = $aShow;
7086 $this->shadow=$aShow;
7087 $this->shadow_width=$aWidth;
7090 function SetMarkAbsSize($aSize) {
7091 $this->mark_abs_size = $aSize ;
7094 function SetLineWeight($aWeight) {
7095 $this->weight = $aWeight;
7098 function SetFrameWeight($aWeight) {
7099 $this->frameweight = $aWeight;
7102 function SetLayout($aDirection=LEGEND_VERT) {
7103 $this->layout_n = $aDirection==LEGEND_VERT ? 1 : 99 ;
7106 function SetColumns($aCols) {
7107 $this->layout_n = $aCols ;
7110 function SetLineSpacing($aSpacing) {
7111 $this->ymargin = $aSpacing ;
7114 function SetReverse($f=true) {
7115 $this->reverse = $f ;
7118 // Set color on frame around box
7119 function SetColor($aFontColor,$aColor='black') {
7120 $this->font_color=$aFontColor;
7121 $this->color=$aColor;
7124 function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
7125 $this->font_family = $aFamily;
7126 $this->font_style = $aStyle;
7127 $this->font_size = $aSize;
7130 function SetPos($aX,$aY,$aHAlign="right",$aVAlign="top") {
7131 $this->Pos($aX,$aY,$aHAlign,$aVAlign);
7134 function SetAbsPos($aX,$aY,$aHAlign="right",$aVAlign="top") {
7137 $this->halign=$aHAlign;
7138 $this->valign=$aVAlign;
7142 function Pos($aX,$aY,$aHAlign="right",$aVAlign="top") {
7143 if( !($aX<1 && $aY<1) )
7144 JpGraphError::Raise(" Position for legend must be given as percentage in range 0-1");
7147 $this->halign=$aHAlign;
7148 $this->valign=$aVAlign;
7151 function SetFillColor($aColor) {
7152 $this->fill_color=$aColor;
7155 function Add($aTxt,$aColor,$aPlotmark="",$aLinestyle=0,$csimtarget="",$csimalt="") {
7156 $this->txtcol[]=array($aTxt,$aColor,$aPlotmark,$aLinestyle,$csimtarget,$csimalt);
7159 function GetCSIMAreas() {
7160 return $this->csimareas;
7163 function Stroke(&$aImg) {
7164 if( $this->hide ) return;
7166 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
7168 if( $this->reverse ) {
7169 $this->txtcol = array_reverse($this->txtcol);
7172 $n=count($this->txtcol);
7173 if( $n == 0 ) return;
7175 // Find out the max width and height of each column to be able
7176 // to size the legend box.
7177 $numcolumns = ($n > $this->layout_n ? $this->layout_n : $n);
7178 for( $i=0; $i < $numcolumns; ++$i ) {
7179 $colwidth[$i] = $aImg->GetTextWidth($this->txtcol[$i][0]) +
7180 2*$this->xmargin + 2*$this->mark_abs_size;
7184 // Find our maximum height in each row
7186 for( $i=0; $i < $n; ++$i ) {
7187 $h = max($this->mark_abs_size,$aImg->GetTextHeight($this->txtcol[$i][0]))+$this->ymargin;
7188 if( $i % $numcolumns == 0 ) {
7190 $rowheight[$rows] = 0;
7192 $rowheight[$rows] = max($rowheight[$rows],$h);
7196 for( $i=0; $i <= $rows; ++$i ) {
7197 $abs_height += $rowheight[$i] ;
7200 // Make sure that the height is at least as high as mark size + ymargin
7201 $abs_height = max($abs_height,$this->mark_abs_size+$this->ymargin);
7202 $abs_height += 2*$this->ymargin;
7204 // Find out the maximum width in each column
7205 for( $i=$numcolumns; $i < $n; ++$i ) {
7206 $colwidth[$i % $numcolumns] = max(
7207 $aImg->GetTextWidth($this->txtcol[$i][0])+2*$this->xmargin+2*$this->mark_abs_size,
7208 $colwidth[$i % $numcolumns]);
7211 // Get the total width
7213 for( $i=0; $i < $numcolumns; ++$i ) {
7214 $mtw += $colwidth[$i] + $this->xmargin;
7217 // Find out maximum width we need for legend box
7218 $abs_width = $mtw+$this->xmargin;
7220 if( $this->xabspos === -1 && $this->yabspos === -1 ) {
7221 $this->xabspos = $this->xpos*$aImg->width ;
7222 $this->yabspos = $this->ypos*$aImg->height ;
7225 // Positioning of the legend box
7226 if( $this->halign=="left" )
7227 $xp = $this->xabspos;
7228 elseif( $this->halign=="center" )
7229 $xp = $this->xabspos - $abs_width/2;
7231 $xp = $aImg->width - $this->xabspos - $abs_width;
7234 if( $this->valign=="center" )
7236 elseif( $this->valign=="bottom" )
7239 // Stroke legend box
7240 $aImg->SetColor($this->color);
7241 $aImg->SetLineWeight($this->frameweight);
7244 $aImg->ShadowRectangle($xp,$yp,$xp+$abs_width+$this->shadow_width,
7245 $yp+$abs_height+$this->shadow_width,
7246 $this->fill_color,$this->shadow_width,$this->shadow_color);
7248 $aImg->SetColor($this->fill_color);
7249 $aImg->FilledRectangle($xp,$yp,$xp+$abs_width,$yp+$abs_height);
7250 $aImg->SetColor($this->color);
7251 $aImg->Rectangle($xp,$yp,$xp+$abs_width,$yp+$abs_height);
7254 // x1,y1 is the position for the legend mark
7255 $aImg->SetLineWeight($this->weight);
7256 $x1=$xp+$this->mark_abs_size+8;
7257 $y1=$yp + $this->mark_abs_size/2 + $this->ymargin/2;
7259 $f2 = round($aImg->GetTextHeight('X')/2);
7261 $grad = new Gradient($aImg);
7263 // Now stroke each legend in turn
7265 foreach($this->txtcol as $p) {
7266 $x1 = round($x1); $y1=round($y1);
7267 if ( $p[2] != "" && $p[2]->GetType() > -1 ) {
7268 // Make a plot mark legend
7269 $aImg->SetColor($p[1]);
7271 $aImg->SetLineStyle($p[3]);
7272 $aImg->StyleLine($x1-$this->mark_abs_size,$y1+$f2,$x1+$this->mark_abs_size,$y1+$f2);
7274 // Stroke a mark with the standard size
7275 // (As long as it is not an image mark )
7276 if( $p[2]->GetType() != MARK_IMG ) {
7277 $p[2]->iFormatCallback = '';
7279 // Since size for circles is specified as the radius
7280 // this means that we must half the size to make the total
7281 // width behave as the other marks
7283 if( $p[2]->GetType() == MARK_FILLEDCIRCLE ||
7284 $p[2]->GetType() == MARK_CIRCLE ) {
7285 $p[2]->SetSize($this->mark_abs_size/2);
7286 $p[2]->Stroke($aImg,$x1,$y1+$f2);
7290 $p[2]->SetSize($this->mark_abs_size);
7291 $p[2]->Stroke($aImg,$x1,$y1+$f2);
7295 elseif ( $p[2] != "" && (is_string($p[3]) || $p[3]>0 ) ) {
7296 // Draw a styled line
7297 $aImg->SetColor($p[1]);
7298 $aImg->SetLineStyle($p[3]);
7299 $aImg->StyleLine($x1-1,$y1+$f2,$x1+$this->mark_abs_size,$y1+$f2);
7300 $aImg->StyleLine($x1-1,$y1+$f2+1,$x1+$this->mark_abs_size,$y1+$f2+1);
7303 // Draw a colored box
7305 $ym = round($y1 + $f2 - $this->mark_abs_size/2);
7306 if( is_array($color) && count($color)==3 ) {
7307 // The client want a gradient color
7308 $grad->FilledRectangle($x1,$ym,
7309 $x1+$this->mark_abs_size,$ym+$this->mark_abs_size,
7310 $color[0],$color[1],$color[2]);
7313 $aImg->SetColor($p[1]);
7314 $aImg->FilledRectangle($x1,$ym,$x1+$this->mark_abs_size,$ym+$this->mark_abs_size);
7316 $aImg->SetColor($this->color);
7317 $aImg->SetLineWeight($this->weight);
7318 $aImg->Rectangle($x1,$ym,$x1+$this->mark_abs_size,$ym+$this->mark_abs_size);
7320 $aImg->SetColor($this->font_color);
7321 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
7322 $aImg->SetTextAlign("left","top");
7323 $aImg->StrokeText(round($x1+$this->mark_abs_size+$this->xmargin),$y1,$p[0]);
7325 // Add CSIM for Legend if defined
7327 $xe = $x1 + $this->xmargin+$this->mark_abs_size+$aImg->GetTextWidth($p[0]);
7328 $ye = $y1 + max($this->mark_abs_size,$aImg->GetTextHeight($p[0]));
7329 $coords = "$x1,$y1,$xe,$y1,$xe,$ye,$x1,$ye";
7330 if( ! empty($p[4]) ) {
7331 $this->csimareas .= "<area shape=\"poly\" coords=\"$coords\" href=\"".$p[4]."\"";
7332 if( !empty($p[5]) ) {
7333 $tmp=sprintf($p[5],$p[0]);
7334 $this->csimareas .= " alt=\"$tmp\" title=\"$tmp\"";
7336 $this->csimareas .= ">\n";
7339 if( $i >= $this->layout_n ) {
7340 $x1 = $xp+$this->mark_abs_size+8;
7341 //$y1 += max($aImg->GetTextHeight($p[0]),$this->mark_abs_size)+$this->ymargin;
7342 $y1 += $rowheight[$row++];
7346 $x1 += $colwidth[($i-1) % $numcolumns] + $this->xmargin;
7354 //===================================================
7355 // CLASS DisplayValue
7356 // Description: Used to print data values at data points
7357 //===================================================
7358 class DisplayValue {
7359 var $show=false,$format="%.1f",$negformat="";
7360 var $iFormCallback='';
7362 var $ff=FF_FONT1,$fs=FS_NORMAL,$fsize=10;
7363 var $color="navy",$negcolor="";
7364 var $margin=5,$valign="",$halign="center";
7365 var $iHideZero=false;
7367 function Show($aFlag=true) {
7371 function SetColor($aColor,$aNegcolor="") {
7372 $this->color = $aColor;
7373 $this->negcolor = $aNegcolor;
7376 function SetFont($aFontFamily,$aFontStyle=FS_NORMAL,$aFontSize=10) {
7377 $this->ff=$aFontFamily;
7378 $this->fs=$aFontStyle;
7379 $this->fsize=$aFontSize;
7382 function SetMargin($aMargin) {
7383 $this->margin = $aMargin;
7386 function SetAngle($aAngle) {
7387 $this->angle = $aAngle;
7390 function SetAlign($aHAlign,$aVAlign='') {
7391 $this->halign = $aHAlign;
7392 $this->valign = $aVAlign;
7395 function SetFormat($aFormat,$aNegFormat="") {
7396 $this->format= $aFormat;
7397 $this->negformat= $aNegFormat;
7400 function SetFormatCallback($aFunc) {
7401 $this->iFormCallback = $aFunc;
7404 function HideZero($aFlag=true) {
7405 $this->iHideZero=$aFlag;
7408 function Stroke($img,$aVal,$x,$y) {
7412 if( $this->negformat=="" ) $this->negformat=$this->format;
7413 if( $this->negcolor=="" ) $this->negcolor=$this->color;
7415 if( $aVal===NULL || (is_string($aVal) && ($aVal=="" || $aVal=="-" || $aVal=="x" ) ) )
7418 if( is_numeric($aVal) && $aVal==0 && $this->iHideZero ) {
7422 // Since the value is used in different cirumstances we need to check what
7423 // kind of formatting we shall use. For example, to display values in a line
7424 // graph we simply display the formatted value, but in the case where the user
7425 // has already specified a text string we don't fo anything.
7426 if( $this->iFormCallback != '' ) {
7427 $f = $this->iFormCallback;
7428 $sval = call_user_func($f,$aVal);
7430 elseif( is_numeric($aVal) ) {
7432 $sval=sprintf($this->format,$aVal);
7434 $sval=sprintf($this->negformat,$aVal);
7439 $y = $y-sign($aVal)*$this->margin;
7441 $txt = new Text($sval,$x,$y);
7442 $txt->SetFont($this->ff,$this->fs,$this->fsize);
7443 if( $this->valign == "" ) {
7450 $valign = $this->valign;
7451 $txt->Align($this->halign,$valign);
7453 $txt->SetOrientation($this->angle);
7455 $txt->SetColor($this->color);
7457 $txt->SetColor($this->negcolor);
7464 //===================================================
7466 // Description: Abstract base class for all concrete plot classes
7467 //===================================================
7470 var $coords=array();
7471 var $legend='',$hidelegend=false;
7472 var $csimtargets=array(); // Array of targets for CSIM
7473 var $csimareas=""; // Resultant CSIM area tags
7474 var $csimalts=null; // ALT:s for corresponding target
7480 var $legendcsimtarget='';
7481 var $legendcsimalt='';
7484 function Plot(&$aDatay,$aDatax=false) {
7485 $this->numpoints = count($aDatay);
7486 if( $this->numpoints==0 )
7487 JpGraphError::Raise(" Empty data array specified for plot. Must have at least one data point.");
7488 $this->coords[0]=$aDatay;
7489 if( is_array($aDatax) )
7490 $this->coords[1]=$aDatax;
7491 $this->value = new DisplayValue();
7498 // "virtual" function which must be implemented by
7500 function Stroke(&$aImg,&$aXScale,&$aYScale) {
7501 JpGraphError::Raise("JpGraph: Stroke() must be implemented by concrete subclass to class Plot");
7504 function HideLegend($f=true) {
7505 $this->hidelegend = $f;
7508 function DoLegend(&$graph) {
7509 if( !$this->hidelegend )
7510 $this->Legend($graph);
7513 function StrokeDataValue($img,$aVal,$x,$y) {
7514 $this->value->Stroke($img,$aVal,$x,$y);
7517 // Set href targets for CSIM
7518 function SetCSIMTargets($aTargets,$aAlts=null) {
7519 $this->csimtargets=$aTargets;
7520 $this->csimalts=$aAlts;
7523 // Get all created areas
7524 function GetCSIMareas() {
7525 return $this->csimareas;
7528 // "Virtual" function which gets called before any scale
7529 // or axis are stroked used to do any plot specific adjustment
7530 function PreStrokeAdjust(&$aGraph) {
7531 if( substr($aGraph->axtype,0,4) == "text" && (isset($this->coords[1])) )
7532 JpGraphError::Raise("JpGraph: You can't use a text X-scale with specified X-coords. Use a \"int\" or \"lin\" scale instead.");
7536 // Get minimum values in plot
7538 if( isset($this->coords[1]) )
7539 $x=$this->coords[1];
7542 if( $x != "" && count($x) > 0 )
7546 $y=$this->coords[0];
7547 if( count($y) > 0 ) {
7551 while( $i<$cnt && !is_numeric($ym=$y[$i]) )
7554 if( is_numeric($y[$i]) )
7555 $ym=min($ym,$y[$i]);
7561 return array($xm,$ym);
7564 // Get maximum value in plot
7566 if( isset($this->coords[1]) )
7567 $x=$this->coords[1];
7571 if( $x!="" && count($x) > 0 )
7574 //$xm=count($this->coords[0])-1; // We count from 0..(n-1)
7575 $xm = $this->numpoints-1;
7577 $y=$this->coords[0];
7578 if( count($y) > 0 ) {
7579 if( !isset($y[0]) ) {
7581 // Change in 1.5.1 Don't treat this as an error any more. Just silently concert to 0
7582 // JpGraphError::Raise(" You have not specified a y[0] value!!");
7586 while( $i<$cnt && !is_numeric($ym=$y[$i]) )
7588 while( $i < $cnt ) {
7589 if( is_numeric($y[$i]) ) $ym=max($ym,$y[$i]);
7595 return array($xm,$ym);
7598 function SetColor($aColor) {
7599 $this->color=$aColor;
7602 function SetLegend($aLegend,$aCSIM="",$aCSIMAlt="") {
7603 $this->legend = $aLegend;
7604 $this->legendcsimtarget = $aCSIM;
7605 $this->legendcsimalt = $aCSIMAlt;
7608 function SetWeight($aWeight) {
7609 $this->weight=$aWeight;
7612 function SetLineWeight($aWeight=1) {
7613 $this->line_weight=$aWeight;
7616 function SetCenter($aCenter=true) {
7617 $this->center = $aCenter;
7620 // This method gets called by Graph class to plot anything that should go
7621 // into the margin after the margin color has been set.
7622 function StrokeMargin(&$aImg) {
7626 // Framework function the chance for each plot class to set a legend
7627 function Legend(&$aGraph) {
7628 if( $this->legend != "" )
7629 $aGraph->legend->Add($this->legend,$this->color,"",0,$this->legendcsimtarget,$this->legendcsimalt);
7634 require_once "jpgraph_plotmark.inc" ;
7636 //==============================================================================
7637 // The following section contains classes to implement the "band" functionality
7638 //==============================================================================
7640 // Utility class to hold coordinates for a rectangle
7644 function Rectangle($aX,$aY,$aWidth,$aHeight) {
7649 $this->xe=$aX+$aWidth-1;
7650 $this->ye=$aY+$aHeight-1;
7654 //=====================================================================
7655 // Class RectPattern
7656 // Base class for pattern hierarchi that is used to display patterned
7657 // bands on the graph. Any subclass that doesn't override Stroke()
7658 // must at least implement method DoPattern(&$aImg) which is responsible
7659 // for drawing the pattern onto the graph.
7660 //=====================================================================
7666 var $linespacing; // Line spacing in pixels
7667 var $iBackgroundColor=-1; // Default is no background fill
7669 function RectPattern($aColor,$aWeight=1) {
7670 $this->color = $aColor;
7671 $this->weight = $aWeight;
7674 function SetBackground($aBackgroundColor) {
7675 $this->iBackgroundColor=$aBackgroundColor;
7678 function SetPos(&$aRect) {
7679 $this->rect = $aRect;
7682 function ShowFrame($aShow=true) {
7683 $this->doframe=$aShow;
7686 function SetDensity($aDens) {
7687 if( $aDens <1 || $aDens > 100 )
7688 JpGraphError::Raise(" Desity for pattern must be between 1 and 100. (You tried $aDens)");
7689 // 1% corresponds to linespacing=50
7690 // 100 % corresponds to linespacing 1
7691 $this->linespacing = floor(((100-$aDens)/100.0)*50)+1;
7695 function Stroke(&$aImg) {
7696 if( $this->rect == null )
7697 JpGraphError::Raise(" No positions specified for pattern.");
7699 if( !(is_numeric($this->iBackgroundColor) && $this->iBackgroundColor==-1) ) {
7700 $aImg->SetColor($this->iBackgroundColor);
7701 $aImg->FilledRectangle($this->rect->x,$this->rect->y,$this->rect->xe,$this->rect->ye);
7704 $aImg->SetColor($this->color);
7705 $aImg->SetLineWeight($this->weight);
7707 // Virtual function implemented by subclass
7708 $this->DoPattern($aImg);
7710 // Frame around the pattern area
7711 if( $this->doframe )
7712 $aImg->Rectangle($this->rect->x,$this->rect->y,$this->rect->xe,$this->rect->ye);
7718 //=====================================================================
7719 // Class RectPatternSolid
7720 // Implements a solid band
7721 //=====================================================================
7722 class RectPatternSolid extends RectPattern {
7724 function RectPatternSolid($aColor="black",$aWeight=1) {
7725 parent::RectPattern($aColor,$aWeight);
7728 function DoPattern(&$aImg) {
7729 $aImg->SetColor($this->color);
7730 $aImg->FilledRectangle($this->rect->x,$this->rect->y,
7731 $this->rect->xe,$this->rect->ye);
7735 //=====================================================================
7736 // Class RectPatternHor
7737 // Implements horizontal line pattern
7738 //=====================================================================
7739 class RectPatternHor extends RectPattern {
7741 function RectPatternHor($aColor="black",$aWeight=1,$aLineSpacing=7) {
7742 parent::RectPattern($aColor,$aWeight);
7743 $this->linespacing = $aLineSpacing;
7746 function DoPattern(&$aImg) {
7747 $x0 = $this->rect->x;
7748 $x1 = $this->rect->xe;
7749 $y = $this->rect->y;
7750 while( $y < $this->rect->ye ) {
7751 $aImg->Line($x0,$y,$x1,$y);
7752 $y += $this->linespacing;
7757 //=====================================================================
7758 // Class RectPatternVert
7759 // Implements vertical line pattern
7760 //=====================================================================
7761 class RectPatternVert extends RectPattern {
7762 var $linespacing=10; // Line spacing in pixels
7764 function RectPatternVert($aColor="black",$aWeight=1,$aLineSpacing=7) {
7765 parent::RectPattern($aColor,$aWeight);
7766 $this->linespacing = $aLineSpacing;
7769 //--------------------
7772 function DoPattern(&$aImg) {
7773 $x = $this->rect->x;
7774 $y0 = $this->rect->y;
7775 $y1 = $this->rect->ye;
7776 while( $x < $this->rect->xe ) {
7777 $aImg->Line($x,$y0,$x,$y1);
7778 $x += $this->linespacing;
7784 //=====================================================================
7785 // Class RectPatternRDiag
7786 // Implements right diagonal pattern
7787 //=====================================================================
7788 class RectPatternRDiag extends RectPattern {
7789 var $linespacing; // Line spacing in pixels
7791 function RectPatternRDiag($aColor="black",$aWeight=1,$aLineSpacing=12) {
7792 parent::RectPattern($aColor,$aWeight);
7793 $this->linespacing = $aLineSpacing;
7796 function DoPattern(&$aImg) {
7797 // --------------------
7801 // --------------------
7802 $xe = $this->rect->xe;
7803 $ye = $this->rect->ye;
7804 $x0 = $this->rect->x + round($this->linespacing/2);
7805 $y0 = $this->rect->y;
7806 $x1 = $this->rect->x;
7807 $y1 = $this->rect->y + round($this->linespacing/2);
7809 while($x0<=$xe && $y1<=$ye) {
7810 $aImg->Line($x0,$y0,$x1,$y1);
7811 $x0 += $this->linespacing;
7812 $y1 += $this->linespacing;
7815 if( $xe-$x1 > $ye-$y0 ) {
7816 // Width larger than height
7817 $x1 = $this->rect->x + ($y1-$ye);
7819 $y0 = $this->rect->y;
7820 while( $x0 <= $xe ) {
7821 $aImg->Line($x0,$y0,$x1,$y1);
7822 $x0 += $this->linespacing;
7823 $x1 += $this->linespacing;
7826 $y0=$this->rect->y + ($x0-$xe);
7830 // Height larger than width
7832 $y0 = $diff+$this->rect->y;
7834 $x1 = $this->rect->x;
7835 while( $y1 <= $ye ) {
7836 $aImg->Line($x0,$y0,$x1,$y1);
7837 $y1 += $this->linespacing;
7838 $y0 += $this->linespacing;
7843 $x1 = $diff + $this->rect->x;
7846 while( $y0 <= $ye ) {
7847 $aImg->Line($x0,$y0,$x1,$y1);
7848 $y0 += $this->linespacing;
7849 $x1 += $this->linespacing;
7854 //=====================================================================
7855 // Class RectPatternLDiag
7856 // Implements left diagonal pattern
7857 //=====================================================================
7858 class RectPatternLDiag extends RectPattern {
7859 var $linespacing; // Line spacing in pixels
7861 function RectPatternLDiag($aColor="black",$aWeight=1,$aLineSpacing=12) {
7862 $this->linespacing = $aLineSpacing;
7863 parent::RectPattern($aColor,$aWeight);
7866 function DoPattern(&$aImg) {
7867 // --------------------
7871 // |------------------|
7872 $xe = $this->rect->xe;
7873 $ye = $this->rect->ye;
7874 $x0 = $this->rect->x + round($this->linespacing/2);
7875 $y0 = $this->rect->ye;
7876 $x1 = $this->rect->x;
7877 $y1 = $this->rect->ye - round($this->linespacing/2);
7879 while($x0<=$xe && $y1>=$this->rect->y) {
7880 $aImg->Line($x0,$y0,$x1,$y1);
7881 $x0 += $this->linespacing;
7882 $y1 -= $this->linespacing;
7884 if( $xe-$x1 > $ye-$this->rect->y ) {
7885 // Width larger than height
7886 $x1 = $this->rect->x + ($this->rect->y-$y1);
7887 $y0=$ye; $y1=$this->rect->y;
7888 while( $x0 <= $xe ) {
7889 $aImg->Line($x0,$y0,$x1,$y1);
7890 $x0 += $this->linespacing;
7891 $x1 += $this->linespacing;
7894 $y0=$this->rect->ye - ($x0-$xe);
7898 // Height larger than width
7902 while( $y1 >= $this->rect->y ) {
7903 $aImg->Line($x0,$y0,$x1,$y1);
7904 $y0 -= $this->linespacing;
7905 $y1 -= $this->linespacing;
7907 $diff = $this->rect->y - $y1;
7908 $x1 = $this->rect->x + $diff;
7909 $y1 = $this->rect->y;
7911 while( $y0 >= $this->rect->y ) {
7912 $aImg->Line($x0,$y0,$x1,$y1);
7913 $y0 -= $this->linespacing;
7914 $x1 += $this->linespacing;
7919 //=====================================================================
7920 // Class RectPattern3DPlane
7921 // Implements "3D" plane pattern
7922 //=====================================================================
7923 class RectPattern3DPlane extends RectPattern {
7924 var $alpha=50; // Parameter that specifies the distance
7925 // to "simulated" horizon in pixel from the
7926 // top of the band. Specifies how fast the lines
7929 function RectPattern3DPlane($aColor="black",$aWeight=1) {
7930 parent::RectPattern($aColor,$aWeight);
7931 $this->SetDensity(10); // Slightly larger default
7934 function SetHorizon($aHorizon) {
7935 $this->alpha=$aHorizon;
7938 function DoPattern(&$aImg) {
7939 // "Fake" a nice 3D grid-effect.
7940 $x0 = $this->rect->x + $this->rect->w/2;
7941 $y0 = $this->rect->y;
7943 $y1 = $this->rect->ye;
7947 // BTW "apa" means monkey in Swedish but is really a shortform for
7948 // "alpha+a" which was the labels I used on paper when I derived the
7949 // geometric to get the 3D perspective right.
7950 // $apa is the height of the bounding rectangle plus the distance to the
7951 // artifical horizon (alpha)
7952 $apa = $this->rect->h + $this->alpha;
7954 // Three cases and three loops
7955 // 1) The endpoint of the line ends on the bottom line
7956 // 2) The endpoint ends on the side
7957 // 3) Horizontal lines
7959 // Endpoint falls on bottom line
7960 $middle=$this->rect->x + $this->rect->w/2;
7961 $dist=$this->linespacing;
7962 $factor=$this->alpha /($apa);
7963 while($x1>$this->rect->x) {
7964 $aImg->Line($x0,$y0,$x1,$y1);
7965 $aImg->Line($x0_right,$y0,$x1_right,$y1);
7966 $x1 = $middle - $dist;
7967 $x0 = $middle - $dist * $factor;
7968 $x1_right = $middle + $dist;
7969 $x0_right = $middle + $dist * $factor;
7970 $dist += $this->linespacing;
7973 // Endpoint falls on sides
7974 $dist -= $this->linespacing;
7975 $d=$this->rect->w/2;
7976 $c = $apa - $d*$apa/$dist;
7977 while( $x0>$this->rect->x ) {
7978 $aImg->Line($x0,$y0,$this->rect->x,$this->rect->ye-$c);
7979 $aImg->Line($x0_right,$y0,$this->rect->xe,$this->rect->ye-$c);
7980 $dist += $this->linespacing;
7981 $x0 = $middle - $dist * $factor;
7982 $x1 = $middle - $dist;
7983 $x0_right = $middle + $dist * $factor;
7984 $c = $apa - $d*$apa/$dist;
7988 // They need some serious consideration since they are a function
7989 // of perspective depth (alpha) and density (linespacing)
7991 $x1=$this->rect->xe;
7994 // The first line is drawn directly. Makes the loop below slightly
7996 $aImg->Line($x0,$y,$x1,$y);
7997 $hls = $this->linespacing;
7999 // A correction factor for vertical "brick" line spacing to account for
8000 // a) the difference in number of pixels hor vs vert
8001 // b) visual apperance to make the first layer of "bricks" look more
8003 $vls = $this->linespacing*0.6;
8005 $ds = $hls*($apa-$vls)/$apa;
8006 // Get the slope for the "perspective line" going from bottom right
8007 // corner to top left corner of the "first" brick.
8009 // Uncomment the following lines if you want to get a visual understanding
8010 // of what this helpline does. BTW this mimics the way you would get the
8011 // perspective right when drawing on paper.
8014 $y0 = $this->rect->ye;
8015 $len=floor(($this->rect->ye-$this->rect->y)/$vls);
8016 $x1 = $middle-round($len*$ds);
8017 $y1 = $this->rect->ye-$len*$vls;
8018 $aImg->PushColor("red");
8019 $aImg->Line($x0,$y0,$x1,$y1);
8024 $k=($this->rect->ye-($this->rect->ye-$vls))/($middle-($middle-$ds));
8026 while( $y>$this->rect->y ) {
8027 $aImg->Line($this->rect->x,$y,$this->rect->xe,$y);
8028 $adj = $k*$dist/(1+$dist*$k/$apa);
8029 if( $adj < 2 ) $adj=2;
8030 $y = $this->rect->ye - round($adj);
8036 //=====================================================================
8037 // Class RectPatternCross
8039 //=====================================================================
8040 class RectPatternCross extends RectPattern {
8043 function RectPatternCross($aColor="black",$aWeight=1) {
8044 parent::RectPattern($aColor,$aWeight);
8045 $this->vert = new RectPatternVert($aColor,$aWeight);
8046 $this->hor = new RectPatternHor($aColor,$aWeight);
8049 function SetOrder($aDepth) {
8050 $this->vert->SetOrder($aDepth);
8051 $this->hor->SetOrder($aDepth);
8054 function SetPos(&$aRect) {
8055 parent::SetPos($aRect);
8056 $this->vert->SetPos($aRect);
8057 $this->hor->SetPos($aRect);
8060 function SetDensity($aDens) {
8061 $this->vert->SetDensity($aDens);
8062 $this->hor->SetDensity($aDens);
8065 function DoPattern(&$aImg) {
8066 $this->vert->DoPattern($aImg);
8067 $this->hor->DoPattern($aImg);
8071 //=====================================================================
8072 // Class RectPatternDiagCross
8074 //=====================================================================
8076 class RectPatternDiagCross extends RectPattern {
8079 function RectPatternDiagCross($aColor="black",$aWeight=1) {
8080 parent::RectPattern($aColor,$aWeight);
8081 $this->right = new RectPatternRDiag($aColor,$aWeight);
8082 $this->left = new RectPatternLDiag($aColor,$aWeight);
8085 function SetOrder($aDepth) {
8086 $this->left->SetOrder($aDepth);
8087 $this->right->SetOrder($aDepth);
8090 function SetPos(&$aRect) {
8091 parent::SetPos($aRect);
8092 $this->left->SetPos($aRect);
8093 $this->right->SetPos($aRect);
8096 function SetDensity($aDens) {
8097 $this->left->SetDensity($aDens);
8098 $this->right->SetDensity($aDens);
8101 function DoPattern(&$aImg) {
8102 $this->left->DoPattern($aImg);
8103 $this->right->DoPattern($aImg);
8108 //=====================================================================
8109 // Class RectPatternFactory
8110 // Factory class for rectangular pattern
8111 //=====================================================================
8112 class RectPatternFactory {
8113 function RectPatternFactory() {
8116 function Create($aPattern,$aColor,$aWeight=1) {
8119 $obj = new RectPatternRDiag($aColor,$aWeight);
8122 $obj = new RectPatternLDiag($aColor,$aWeight);
8125 $obj = new RectPatternSolid($aColor,$aWeight);
8128 $obj = new RectPatternVert($aColor,$aWeight);
8131 $obj = new RectPatternHor($aColor,$aWeight);
8134 $obj = new RectPattern3DPlane($aColor,$aWeight);
8137 $obj = new RectPatternCross($aColor,$aWeight);
8139 case BAND_DIAGCROSS:
8140 $obj = new RectPatternDiagCross($aColor,$aWeight);
8143 JpGraphError::Raise(" Unknown pattern specification ($aPattern)");
8150 //=====================================================================
8152 // Factory class which is used by the client.
8153 // It is responsible for factoring the corresponding pattern
8155 //=====================================================================
8159 var $dir, $min, $max;
8161 function PlotBand($aDir,$aPattern,$aMin,$aMax,$aColor="black",$aWeight=1,$aDepth=DEPTH_BACK) {
8162 $f = new RectPatternFactory();
8163 $this->prect = $f->Create($aPattern,$aColor,$aWeight);
8164 if( is_numeric($aMin) && is_numeric($aMax) && ($aMin > $aMax) )
8165 JpGraphError::Raise('Min value for plotband is larger than specified max value. Please correct.');
8169 $this->depth=$aDepth;
8172 // Set position. aRect contains absolute image coordinates
8173 function SetPos(&$aRect) {
8174 assert( $this->prect != null ) ;
8175 $this->prect->SetPos($aRect);
8178 function ShowFrame($aFlag=true) {
8179 $this->prect->ShowFrame($aFlag);
8182 // Set z-order. In front of pplot or in the back
8183 function SetOrder($aDepth) {
8184 $this->depth=$aDepth;
8187 function SetDensity($aDens) {
8188 $this->prect->SetDensity($aDens);
8204 function Stroke(&$aImg,&$aXScale,&$aYScale) {
8205 assert( $this->prect != null ) ;
8206 if( $this->dir == HORIZONTAL ) {
8207 if( $this->min === 'min' ) $this->min = $aYScale->GetMinVal();
8208 if( $this->max === 'max' ) $this->max = $aYScale->GetMaxVal();
8210 // Only draw the bar if it actually appears in the range
8211 if ($this->min < $aYScale->GetMaxVal() && $this->max > $aYScale->GetMinVal()) {
8213 // Trucate to limit of axis
8214 $this->min = max($this->min, $aYScale->GetMinVal());
8215 $this->max = min($this->max, $aYScale->GetMaxVal());
8217 $x=$aXScale->scale_abs[0];
8218 $y=$aYScale->Translate($this->max);
8219 $width=$aXScale->scale_abs[1]-$aXScale->scale_abs[0]+1;
8220 $height=abs($y-$aYScale->Translate($this->min))+1;
8221 $this->prect->SetPos(new Rectangle($x,$y,$width,$height));
8222 $this->prect->Stroke($aImg);
8226 if( $this->min === 'min' ) $this->min = $aXScale->GetMinVal();
8227 if( $this->max === 'max' ) $this->max = $aXScale->GetMaxVal();
8229 // Only draw the bar if it actually appears in the range
8230 if ($this->min < $aXScale->GetMaxVal() && $this->max > $aXScale->GetMinVal()) {
8232 // Trucate to limit of axis
8233 $this->min = max($this->min, $aXScale->GetMinVal());
8234 $this->max = min($this->max, $aXScale->GetMaxVal());
8236 $y=$aYScale->scale_abs[1];
8237 $x=$aXScale->Translate($this->min);
8238 $height=abs($aYScale->scale_abs[1]-$aYScale->scale_abs[0]);
8239 $width=abs($x-$aXScale->Translate($this->max));
8240 $this->prect->SetPos(new Rectangle($x,$y,$width,$height));
8241 $this->prect->Stroke($aImg);
8247 //===================================================
8250 // Data container class to hold properties for a static
8251 // line that is drawn directly in the plot area.
8252 // Usefull to add static borders inside a plot to show
8253 // for example set-values
8254 //===================================================
8263 function PlotLine($aDir=HORIZONTAL,$aPos=0,$aColor="black",$aWeight=1) {
8264 $this->direction = $aDir;
8265 $this->color=$aColor;
8266 $this->weight=$aWeight;
8267 $this->scaleposition=$aPos;
8272 function SetPosition($aScalePosition) {
8273 $this->scaleposition=$aScalePosition;
8276 function SetDirection($aDir) {
8277 $this->direction = $aDir;
8280 function SetColor($aColor) {
8281 $this->color=$aColor;
8284 function SetWeight($aWeight) {
8285 $this->weight=$aWeight;
8288 function Stroke(&$aImg,&$aXScale,&$aYScale) {
8289 $aImg->SetColor($this->color);
8290 $aImg->SetLineWeight($this->weight);
8291 if( $this->direction == VERTICAL ) {
8292 $ymin_abs=$aYScale->Translate($aYScale->GetMinVal());
8293 $ymax_abs=$aYScale->Translate($aYScale->GetMaxVal());
8294 $xpos_abs=$aXScale->Translate($this->scaleposition);
8295 $aImg->Line($xpos_abs, $ymin_abs, $xpos_abs, $ymax_abs);
8297 elseif( $this->direction == HORIZONTAL ) {
8298 $xmin_abs=$aXScale->Translate($aXScale->GetMinVal());
8299 $xmax_abs=$aXScale->Translate($aXScale->GetMaxVal());
8300 $ypos_abs=$aYScale->Translate($this->scaleposition);
8301 $aImg->Line($xmin_abs, $ypos_abs, $xmax_abs, $ypos_abs);
8304 JpGraphError::Raise(" Illegal direction for static line");