]> git.llucax.com Git - mecon/meconlib.git/blob - lib/MECON/Graph/external/jpgraph/src/jpgraph.php
Se borran tags de xmi2code.
[mecon/meconlib.git] / lib / MECON / Graph / external / jpgraph / src / jpgraph.php
1 <?php 
2 //=======================================================================
3 // File:        JPGRAPH.PHP
4 // Description: PHP4 Graph Plotting library. Base module.
5 // Created:     2001-01-08
6 // Author:      Johan Persson (johanp@aditus.nu)
7 // Ver:         $Id: jpgraph.php,v 1.242.2.21 2003/08/22 23:56:11 aditus Exp $
8 //
9 // License:     This code is released under QPL 1.0 
10 // Copyright (C) 2001,2002,2003 Johan Persson 
11 //========================================================================
12
13 //------------------------------------------------------------------------
14 // Directories for cache and font directory.
15 // Leave them undefined to use default values. 
16 // 
17 // Default values used if these defines are left commented out are:
18 // 
19 // UNIX: 
20 //   CACHE_DIR = /tmp/jpgraph_cache/
21 //   TTF_DIR   = /usr/X11R6/lib/X11/fonts/truetype/
22 //
23 // WINDOWS:
24 //   CACHE_DIR = $SERVER_TEMP/jpgraph_cache/
25 //   TTF_DIR   = $SERVER_SYSTEMROOT/fonts/
26 //    
27 //
28 //------------------------------------------------------------------------
29
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. 
34 // Must end with '/'
35 // DEFINE("CACHE_DIR","/tmp/jpgraph_cache/");
36
37 // Directory for jpGraph TTF fonts. Must end with '/'
38 // DEFINE("TTF_DIR","/usr/X11R6/lib/X11/fonts/truetype/");
39
40
41 //-------------------------------------------------------------------------
42 // Cache directory specification for use with CSIM graphs that are
43 // using the cache.
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/");
54
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 //------------------------------------------------------------------------
60
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");
65
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);
77
78 // Specify what version of the GD library is installed.
79 // If this is set to 'auto' the version will be automatically 
80 // determined.
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');
86
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);
94
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);
101
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);
106
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);
112
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
121 // of colors.
122 DEFINE("USE_APPROX_COLORS",true);
123
124 // Special unicode cyrillic language support
125 DEFINE("LANGUAGE_CYRILLIC",false);
126
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);
131
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);
135
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
138 // generating graphs
139 DEFINE("BRAND_TIMING",false);
140
141 // What format should be used for the timing string?
142 DEFINE("BRAND_TIME_FORMAT","(%01.3fs)");
143
144 //------------------------------------------------------------------------
145 // The following constants should rarely have to be changed !
146 //------------------------------------------------------------------------
147
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");
154
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);
158
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);
164
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_");
169
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");
174
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");
179
180 // DON'T SET THIS FLAG YORSELF THIS IS ONLY FOR INTERNAL TESTING
181 // PURPOSES. ENABLING THIS FLAG WILL MAKE SOME OF YOUR SCRIPT 
182 // STOP WORKING
183 // Enable some extra debug information for CSIM etc to be shown. 
184 DEFINE("JPG_DEBUG",false);
185
186 // Version info
187 DEFINE('JPG_VERSION','1.12');
188
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');
197         }
198         else {
199            DEFINE('CACHE_DIR', $_SERVER['TEMP'] . '/');
200         }
201     } else {
202         DEFINE('CACHE_DIR','/tmp/jpgraph_cache/');
203     }
204 }
205
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');
210         }
211         else {
212           DEFINE('TTF_DIR', $_SERVER['SystemRoot'] . '/fonts/');
213         }
214     } else {
215         DEFINE('TTF_DIR','/usr/X11R6/lib/X11/fonts/truetype/');
216     }
217 }
218
219 //------------------------------------------------------------------
220 // Constants which are used as parameters for the method calls
221 //------------------------------------------------------------------
222
223 // TTF Font families
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);
231
232 // Chinese font
233 DEFINE("FF_SIMSUN",18);
234
235 // Gnome Vera font
236 // Available from http://www.gnome.org/fonts/
237 DEFINE("FF_VERA",19);
238 DEFINE("FF_VERAMONO",20);
239 DEFINE("FF_VERASERIF",21);
240
241
242 // Older deprecated fonts 
243 DEFINE("FF_BOOK",91);    // Deprecated fonts from 1.9
244 DEFINE("FF_HANDWRT",92); // Deprecated fonts from 1.9
245
246 // TTF Font styles
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);
252
253 //Definitions for internal font, new style
254 DEFINE("FF_FONT0",1);
255 DEFINE("FF_FONT1",2);
256 DEFINE("FF_FONT2",4);
257
258 //Definitions for internal font, old style
259 // (Only defined here to be able to generate an error mesage
260 // when used)
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
266
267 // Tick density
268 DEFINE("TICKD_DENSE",1);
269 DEFINE("TICKD_NORMAL",2);
270 DEFINE("TICKD_SPARSE",3);
271 DEFINE("TICKD_VERYSPARSE",4);
272
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);
278 DEFINE("SIDE_UP",1);
279 DEFINE("SIDE_TOP",1);
280
281 // Legend type stacked vertical or horizontal
282 DEFINE("LEGEND_VERT",0);
283 DEFINE("LEGEND_HOR",1);
284
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);
294 DEFINE("MARK_X",9);
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);
303
304 // Builtin images
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);
316
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);
328
329 // Inline defines
330 DEFINE("INLINE_YES",1);
331 DEFINE("INLINE_NO",0);
332
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);
338
339 // Depth of objects
340 DEFINE("DEPTH_BACK",0);
341 DEFINE("DEPTH_FRONT",1);
342
343 // Direction
344 DEFINE("VERTICAL",1);
345 DEFINE("HORIZONTAL",0);
346
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
356
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);
363
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);
375
376 // Width of tab titles
377 DEFINE('TABTITLE_WIDTHFIT',0);
378 DEFINE('TABTITLE_WIDTHFULL',-1);
379
380 //
381 // Get hold of gradient class (In Version 2.x)
382 // A client of the library has to manually include this
383 //
384 include "jpgraph_gradient.php";
385
386 //
387 // First of all set up a default error handler
388 //
389
390 //=============================================================
391 // The default trivial text error handler.
392 //=============================================================
393 class JpGraphErrObject {
394     function JpGraphErrObject() {
395         // Empty. Reserved for future use
396     }
397
398     // If aHalt is true then execution can't continue. Typical used for
399     // fatal errors
400     function Raise($aMsg,$aHalt=true) {
401         $aMsg = "<b>JpGraph Error:</b> ".$aMsg;
402         if( $aHalt )
403             die($aMsg);
404         else 
405             echo $aMsg."<p>";
406     }
407 }
408
409 //==============================================================
410 // An image based error handler
411 //==============================================================
412 class JpGraphErrObjectImg {
413
414     function Raise($aMsg,$aHalt=true) {
415         $img_iconerror = 
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==' ;
431         
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);                
436         }
437
438         // Create the error icon GD
439         $erricon = Image::CreateFromString(base64_decode($img_iconerror));   
440
441         // Create an image that contains the error text.
442         $w=380; $h=100;
443         $img = new Image($w,$h);
444
445
446         // Drop shadow
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);
451         
452         // Window background
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);
456
457         // Window border
458         $img->SetColor("black");
459         $img->Rectangle(1,1,$w-5,$h-5);
460         $img->Rectangle(0,0,$w-4,$h-4);
461         
462         // Window top row
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);
468         
469         // "Close button"
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);
474
475         // "White shadow"
476         $img->SetColor("white");
477         // "Button"
478         $img->Line($w-24,3,$w-24,15);
479         $img->Line($w-24,2,$w-7,2);
480
481         // Left window edge
482         $img->Line(2,2,2,$h-5);
483         $img->Line(2,2,$w-27,2);
484
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);
489
490         // Gray window shadow
491         $img->Line(2,$h-6,$w-5,$h-6);
492         $img->Line(3,$h-7,$w-5,$h-7);
493
494         // Window title
495         $m = floor($w/2-5);
496         $l = 100;
497         $img->SetColor("lightgray:1.3");
498         $img->FilledRectangle($m-$l,2,$m+$l,16);
499
500         // Stroke text
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");
508         $txt->Stroke($img);
509         $img->Headers();
510         $img->Stream();
511         die();
512     }
513 }
514
515 //
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
518 // in all methods.
519 //
520 class JpGraphError {
521     function Install($aErrObject) {
522         GLOBAL $__jpg_err;
523         $__jpg_err = $aErrObject;
524     }
525     function Raise($aMsg,$aHalt=true){
526         GLOBAL $__jpg_err;
527         $tmp = new $__jpg_err;
528         $tmp->Raise($aMsg,$aHalt);
529     }
530 }
531
532 //
533 // ... and install the default error handler
534 //
535 if( USE_IMAGE_ERROR_HANDLER ) {
536     JpGraphError::Install("JpGraphErrObjectImg");
537 }
538 else {
539     JpGraphError::Install("JpGraphErrObject");
540 }
541
542 //
543 // Setup PHP error handler
544 //
545
546 function _phpErrorHandler($errno,$errmsg,$filename, $linenum, $vars) {
547     JpGraphError::Raise('In '.basename($filename).'#'.$linenum."\n".$errmsg);
548 }
549
550 if( INSTALL_PHP_ERR_HANDLER ) {
551     set_error_handler("_phpErrorHandler");
552     error_reporting(1);
553 }
554
555 //
556 //Check if there were any warnings, perhaps some wrong includes by the
557 //user
558 //
559 if( isset($GLOBALS['php_errormsg']) ) {
560     JpGraphError::Raise("<b>General PHP error:</b><br>".$GLOBALS['php_errormsg']);
561 }
562
563
564 //
565 // Routine to determine if GD1 or GD2 is installed
566 //
567 function CheckGDVersion() {
568     ob_start();
569     phpinfo(8); // Just get the modules loaded
570     $a = ob_get_contents();
571     ob_end_clean();
572     if( preg_match('/.*GD Version.*(1\.).*/',$a,$m) ) {
573         $r=1;$v=$m[1];
574     }
575     elseif( preg_match('/.*GD Version.*(2\.).*/',$a,$m) ) {
576         $r=2;$v=$m[1];
577     }
578     else {
579         $r=0;$v=$m[1];
580     }
581     return $r;
582 }
583
584 //
585 // Check what version of the GD library is installed.
586 //
587 if( USE_LIBRARY_GD2 === 'auto' ) {
588     $gdversion = CheckGDVersion();
589     if( $gdversion == 2 ) {
590         $GLOBALS['gd2'] = true;
591         $GLOBALS['copyfunc'] = 'imagecopyresampled';
592     }
593     elseif( $gdversion == 1 ) {
594         $GLOBALS['gd2'] = false;
595         $GLOBALS['copyfunc'] = 'imagecopyresized';
596     }
597     else {
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.");
601     }
602 }
603 else {
604     $GLOBALS['gd2'] = USE_LIBRARY_GD2;
605     $GLOBALS['copyfunc'] = USE_LIBRARY_GD2 ? 'imagecopyresampled' : 'imagecopyresized';
606 }
607
608 // Usefull mathematical function
609 function sign($a) {return $a >= 0 ? 1 : -1;}
610
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 )
619         $img_format="png";
620     elseif( $supported & IMG_GIF )
621         $img_format="gif";
622     elseif( $supported & IMG_JPG )
623         $img_format="jpeg";
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;
630 }
631
632 class LanguageConv {
633     var $g2312 = null ;
634
635     function Convert($aTxt,$aFF) {
636         if( LANGUAGE_CYRILLIC ) {
637             if( CYRILLIC_FROM_WINDOWS ) {
638                 $aTxt = convert_cyr_string($aTxt, "w", "k"); 
639             }
640             $isostring = convert_cyr_string($aTxt, "k", "i");
641             $unistring = LanguageConv::iso2uni($isostring);
642             return $unistring;
643         }
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();
649             }
650             return $this->g2312->gb2utf8($aTxt);
651         }
652         else 
653             return $aTxt;
654     }
655
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;
662         }
663         return $uniline;
664     }
665 }
666
667 //===================================================
668 // CLASS JpgTimer
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 //===================================================
673 class JpgTimer {
674     var $start;
675     var $idx;   
676 //---------------
677 // CONSTRUCTOR
678     function JpgTimer() {
679         $this->idx=0;
680     }
681
682 //---------------
683 // PUBLIC METHODS       
684
685     // Push a new timer start on stack
686     function Push() {
687         list($ms,$s)=explode(" ",microtime());  
688         $this->start[$this->idx++]=floor($ms*1000) + 1000*$s;   
689     }
690
691     // Pop the latest timer start and return the diff with the
692     // current time
693     function Pop() {
694         assert($this->idx>0);
695         list($ms,$s)=explode(" ",microtime());  
696         $etime=floor($ms*1000) + (1000*$s);
697         $this->idx--;
698         return $etime-$this->start[$this->idx];
699     }
700 } // Class
701
702 $gJpgBrandTiming = BRAND_TIMING;
703 //===================================================
704 // CLASS DateLocale
705 // Description: Hold localized text used in dates
706 // ToDOo: Rewrite this to use the real local locale
707 // instead.
708 //===================================================
709 class DateLocale {
710  
711     var $iLocale = 'C'; // environmental locale be used by default
712
713     var $iDayAbb = null;
714     var $iShortDay = null;
715     var $iShortMonth = null;
716     var $iMonthName = null;
717
718 //---------------
719 // CONSTRUCTOR  
720     function DateLocale() {
721         settype($this->iDayAbb, 'array');
722         settype($this->iShortDay, 'array');
723         settype($this->iShortMonth, 'array');
724         settype($this->iMonthName, 'array');
725
726
727         $this->Set('C');
728     }
729
730 //---------------
731 // PUBLIC METHODS       
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!
736         }
737
738         $pLocale = setlocale(LC_TIME, 0); // get current locale for LC_TIME
739         $res = setlocale(LC_TIME, $aLocale);
740         if ( ! $res ){
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.");
742             return FALSE;
743         }
744  
745         $this->iLocale = $aLocale;
746
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;
752         }
753
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);
758         }
759         
760         
761         setlocale(LC_TIME, $pLocale);
762
763         return TRUE;
764     }
765
766
767     function GetDayAbb() {
768         return $this->iDayAbb[$this->iLocale];
769     }
770         
771     function GetShortDay() {
772         return $this->iShortDay[$this->iLocale];
773     }
774
775     function GetShortMonth() {
776         return $this->iShortMonth[$this->iLocale];
777     }
778         
779     function GetShortMonthName($aNbr) {
780         return $this->iShortMonth[$this->iLocale][$aNbr];
781     }
782
783     function GetLongMonthName($aNbr) {
784         return $this->iMonthName[$this->iLocale][$aNbr];
785     }
786
787     function GetMonth() {
788         return $this->iMonthName[$this->iLocale];
789     }
790 }
791
792 $gDateLocale = new DateLocale();
793 $gJpgDateLocale = new DateLocale();
794
795
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;
803         
804     function FuncGenerator($aFunc,$aXFunc='') {
805         $this->iFunc = $aFunc;
806         $this->iXFunc = $aXFunc;
807     }
808         
809     function E($aXMin,$aXMax,$aSteps=50) {
810         $this->iMin = $aXMin;
811         $this->iMax = $aXMax;
812         $this->iStepSize = ($aXMax-$aXMin)/$aSteps;
813
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;';
818         else
819             JpGraphError::Raise('FuncGenerator : No function specified. ');
820                         
821         @eval($t);
822                 
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 ');
827                                 
828         return array($xa,$ya);
829     }
830 }
831
832
833 //=======================================================
834 // CLASS Footer
835 // Description: Encapsulates the footer line in the Graph
836 //
837 //=======================================================
838 class Footer {
839     var $left,$center,$right;
840     var $iLeftMargin = 3;
841     var $iRightMargin = 3;
842     var $iBottomMargin = 3;
843
844     function Footer() {
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');
851     }
852
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);
858
859         $x = ($aImg->width - $this->iLeftMargin - $this->iRightMargin)/2;
860         $this->center->Align('center','bottom');
861         $this->center->Stroke($aImg,$x,$y);
862
863         $x = $aImg->width - $this->iRightMargin;
864         $this->right->Align('right','bottom');
865         $this->right->Stroke($aImg,$x,$y);
866     }
867 }
868
869     DEFINE('BGRAD_FRAME',1);
870     DEFINE('BGRAD_MARGIN',2);
871     DEFINE('BGRAD_PLOT',3);
872
873
874
875 //===================================================
876 // CLASS Graph
877 // Description: Main class to handle graphs
878 //===================================================
879 class Graph {
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;
906     var $inline;
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;
911     var $footer;
912     var $csimcachename = '', $csimcachetimeout = 0;
913     var $iDoClipping = false;
914     var $y2orderback=true;
915     var $tabtitle;
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';
930
931 //---------------
932 // CONSTRUCTOR
933
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 ) {
944             global $tim;
945             $tim = new JpgTimer();
946             $tim->Push();
947         }
948                 
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();
953                         
954         // Should the image be streamed back to the browser or only to the cache?
955         $this->inline=$aInline;
956                 
957         $this->img      = new RotImage($aWidth,$aHeight);
958
959         $this->cache    = new ImgStreamCache($this->img);
960         $this->cache->SetTimeOut($aTimeOut);
961
962         $this->title = new Text();
963         $this->title->ParagraphAlign('center');
964         $this->title->SetFont(FF_FONT2,FS_BOLD);
965         $this->title->SetMargin(3);
966
967         $this->subtitle = new Text();
968         $this->subtitle->ParagraphAlign('center');
969
970         $this->subsubtitle = new Text();
971         $this->subsubtitle->ParagraphAlign('center');
972
973         $this->legend = new Legend();
974         $this->footer = new Footer();
975
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) ) {
980                 exit();
981             }
982                                 
983         $this->cache_name = $aCachedName;
984         $this->SetTickDensity(); // Normal density
985
986         $this->tabtitle = new GraphTabTitle();
987     }
988 //---------------
989 // PUBLIC METHODS       
990
991     // Should the grid be in front or back of the plot?
992     function SetGridDepth($aDepth) {
993         $this->grid_depth=$aDepth;
994     }
995         
996     // Specify graph angle 0-360 degrees.
997     function SetAngle($aAngle) {
998         $this->img->SetAngle($aAngle);
999     }
1000
1001     function SetAlphaBlending($aFlg=true) {
1002         $this->img->SetAlphaBlending($aFlg);
1003     }
1004
1005     // Shortcut to image margin
1006     function SetMargin($lm,$rm,$tm,$bm) {
1007         $this->img->SetMargin($lm,$rm,$tm,$bm);
1008     }
1009
1010     function SetY2OrderBack($aBack=true) {
1011         $this->y2orderback = $aBack;
1012     }
1013
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 ;
1021
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');
1028     }
1029         
1030     function SetClipping($aFlg=true) {
1031         $this->iDoClipping = $aFlg ;
1032     }
1033
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]);
1040         else
1041             $cl = get_class($aPlot);
1042
1043         if( $cl == 'text' ) 
1044             $this->AddText($aPlot);
1045         elseif( $cl == 'plotline' )
1046             $this->AddLine($aPlot);
1047         elseif( $cl == 'plotband' )
1048             $this->AddBand($aPlot);
1049         else
1050             $this->plots[] = &$aPlot;
1051     }
1052
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.");   
1057
1058         if( is_array($aPlot) && count($aPlot) > 0 )
1059             $cl = get_class($aPlot[0]);
1060         else
1061             $cl = get_class($aPlot);
1062
1063         if( $cl == 'text' ) 
1064             $this->AddText($aPlot,true);
1065         elseif( $cl == 'plotline' )
1066             $this->AddLine($aPlot,true);
1067         elseif( $cl == 'plotband' )
1068             $this->AddBand($aPlot,true);
1069         else
1070             $this->y2plots[] = &$aPlot;
1071     }
1072         
1073     // Add text object to the graph
1074     function AddText(&$aTxt,$aToY2=false) {
1075         if( $aTxt == null )
1076             JpGraphError::Raise("<b></b> Graph::AddText() You tried to add a null text to the graph.");         
1077         if( $aToY2 ) {
1078             if( is_array($aTxt) ) {
1079                 for($i=0; $i < count($aTxt); ++$i )
1080                     $this->y2texts[]=&$aTxt[$i];
1081             }
1082             else
1083                 $this->y2texts[] = &$aTxt;
1084         }
1085         else {
1086             if( is_array($aTxt) ) {
1087                 for($i=0; $i < count($aTxt); ++$i )
1088                     $this->texts[]=&$aTxt[$i];
1089             }
1090             else
1091                 $this->texts[] = &$aTxt;
1092         }
1093     }
1094         
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."); 
1099
1100         if( $aToY2 ) {
1101             if( is_array($aLine) ) {
1102                 for($i=0; $i < count($aLine); ++$i )
1103                     $this->y2lines[]=&$aLine[$i];
1104             }
1105             else
1106                 $this->y2lines[] = &$aLine;
1107         }
1108         else {
1109             if( is_array($aLine) ) {
1110                 for($i=0; $i<count($aLine); ++$i )
1111                     $this->lines[]=&$aLine[$i];
1112             }
1113             else
1114                 $this->lines[] = &$aLine;
1115         }
1116     }
1117
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.");
1122
1123         if( $aToY2 ) {
1124             if( is_array($aBand) ) {
1125                 for($i=0; $i < count($aBand); ++$i )
1126                     $this->y2bands[] = &$aBand[$i];
1127             }
1128             else
1129                 $this->y2bands[] = &$aBand;
1130         }
1131         else {
1132             if( is_array($aBand) ) {
1133                 for($i=0; $i < count($aBand); ++$i )
1134                     $this->bands[] = &$aBand[$i];
1135             }
1136             else
1137                 $this->bands[] = &$aBand;
1138         }
1139     }
1140
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;
1146     } 
1147         
1148     // Specify a background image
1149     function SetBackgroundImage($aFileName,$aBgType=BGIMG_FILLPLOT,$aImgFormat="auto") {
1150
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.");
1153         }
1154
1155         // Get extension to determine image type
1156         if( $aImgFormat == "auto" ) {
1157             $e = explode('.',$aFileName);
1158             if( !$e ) {
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');
1160             }
1161
1162             $valid_formats = array('png', 'jpg', 'gif');
1163             $aImgFormat = strtolower($e[count($e)-1]);
1164             if ($aImgFormat == 'jpeg')  {
1165                 $aImgFormat = 'jpg';
1166             }
1167             elseif (!in_array($aImgFormat, $valid_formats) )  {
1168                 JpGraphError::Raise('Unknown file extension ($aImgFormat) in Graph::SetBackgroundImage() for filename: '.$aFileName);
1169             }    
1170         }
1171
1172         $this->background_image = $aFileName;
1173         $this->background_image_type=$aBgType;
1174         $this->background_image_format=$aImgFormat;
1175     }
1176         
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;
1182     }
1183         
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;
1189     }
1190
1191     // Specify axis style (boxed or single)
1192     function SetAxisStyle($aStyle) {
1193         $this->iAxisStyle = $aStyle ;
1194     }
1195         
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;
1201     }
1202         
1203     // Specify color for the plotarea (not the margins)
1204     function SetColor($aColor) {
1205         $this->plotarea_color=$aColor;
1206     }
1207         
1208     // Specify color for the margins (all areas outside the plotarea)
1209     function SetMarginColor($aColor) {
1210         $this->margin_color=$aColor;
1211     }
1212         
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;
1218     }
1219
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 ;
1227
1228         $this->doshadow = false ;
1229     }
1230
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;
1238     }
1239
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;
1244
1245         if( $aYMax < $aYMin || $aXMax < $aXMin )
1246             JpGraphError::Raise('Graph::SetScale(): Specified Max value must be larger than the specified Min value.');
1247
1248         $yt=substr($aAxisType,-3,3);
1249         if( $yt=="lin" )
1250             $this->yscale = new LinearScale($aYMin,$aYMax);
1251         elseif( $yt == "int" ) {
1252             $this->yscale = new LinearScale($aYMin,$aYMax);
1253             $this->yscale->SetIntScale();
1254         }
1255         elseif( $yt=="log" )
1256             $this->yscale = new LogScale($aYMin,$aYMax);
1257         else
1258             JpGraphError::Raise("Unknown scale specification for Y-scale. ($aAxisType)");
1259                         
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");
1264         }
1265         elseif( $xt == "int" ) {
1266             $this->xscale = new LinearScale($aXMin,$aXMax,"x");
1267             $this->xscale->SetIntScale();
1268         }
1269         elseif( $xt == "log" )
1270             $this->xscale = new LogScale($aXMin,$aXMax,"x");
1271         else
1272             JpGraphError::Raise(" Unknown scale specification for X-scale. ($aAxisType)");
1273
1274         $this->xscale->Init($this->img);
1275         $this->yscale->Init($this->img);                                                
1276                                         
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();                   
1282     }
1283         
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();
1291         }
1292         elseif( $aAxisType=="log" ) {
1293             $this->y2scale = new LogScale($aY2Min,$aY2Max);
1294         }
1295         else JpGraphError::Raise("JpGraph: Unsupported Y2 axis type: $axtype<br>");
1296                         
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); 
1301                 
1302         // Deafult position is the max x-value
1303         $this->y2grid = new Grid($this->y2axis);                                                        
1304     }
1305         
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 ) {
1313             case TICKD_DENSE:
1314                 $this->ytick_factor=12;                 
1315                 break;
1316             case TICKD_NORMAL:
1317                 $this->ytick_factor=25;                 
1318                 break;
1319             case TICKD_SPARSE:
1320                 $this->ytick_factor=40;                 
1321                 break;
1322             case TICKD_VERYSPARSE:
1323                 $this->ytick_factor=100;                        
1324                 break;          
1325             default:
1326                 JpGraphError::Raise("JpGraph: Unsupported Tick density: $densy");
1327         }
1328         switch( $aXDensity ) {
1329             case TICKD_DENSE:
1330                 $this->xtick_factor=15;                                                 
1331                 break;
1332             case TICKD_NORMAL:
1333                 $this->xtick_factor=30;                 
1334                 break;
1335             case TICKD_SPARSE:
1336                 $this->xtick_factor=45;                                 
1337                 break;
1338             case TICKD_VERYSPARSE:
1339                 $this->xtick_factor=60;                                                         
1340                 break;          
1341             default:
1342                 JpGraphError::Raise("JpGraph: Unsupported Tick density: $densx");
1343         }               
1344     }
1345         
1346
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();
1352
1353         $n = count($this->plots);
1354         for( $i=0; $i<$n; ++$i ) 
1355             $csim .= $this->plots[$i]->GetCSIMareas();
1356
1357         $n = count($this->y2plots);
1358         for( $i=0; $i<$n; ++$i ) 
1359             $csim .= $this->y2plots[$i]->GetCSIMareas();
1360
1361         return $csim;
1362     }
1363         
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();
1368         $im .= "</MAP>"; 
1369         return $im;
1370     }
1371
1372     function CheckCSIMCache($aCacheName,$aTimeOut=60) {
1373         global $HTTP_SERVER_VARS;
1374
1375         if( $aCacheName=='auto' )
1376             $aCacheName=basename($HTTP_SERVER_VARS['PHP_SELF']);
1377
1378         $this->csimcachename = CSIMCACHE_DIR.$aCacheName;
1379         $this->csimcachetimeout = $aTimeOut;
1380
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;
1394
1395             $timedout=false;
1396                 
1397             // Does it exist at all ?
1398             
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) ) {
1403                     $timedout=true;
1404                     @unlink($basecsim);
1405                     @unlink($baseimg);
1406                 }
1407                 else {
1408                     if ($fh = @fopen($basecsim, "r")) {
1409                         fpassthru($fh);
1410                         exit();
1411                     }
1412                     else
1413                         JpGraphError::Raise(" Can't open cached CSIM \"$basecsim\" for reading.");
1414                 }
1415             }
1416         }
1417         return false;
1418     }
1419
1420     function StrokeCSIM($aScriptName='',$aCSIMName='',$aBorder=0) {
1421         GLOBAL $HTTP_GET_VARS;
1422
1423         if( $aCSIMName=='' ) {
1424             // create a random map name
1425             srand ((double) microtime() * 1000000);
1426             $r = rand(0,100000);
1427             $aCSIMName='__mapname'.$r.'__';
1428         }
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;
1443
1444                 // Check that apache can write to directory specified
1445
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.');
1448                 }
1449                 
1450                 // Make sure directory exists
1451                 $this->cache->MakeDirs($dir);
1452
1453                 // Write the image file
1454                 $this->Stroke(CSIMCACHE_DIR.$baseimg);
1455
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);
1461                     fclose($fh);
1462                     echo $htmlwrap;
1463                 }
1464                 else
1465                     JpGraphError::Raise(" Can't write CSIM \"$basecsim\" for writing. Check free space and permissions.");
1466             }
1467             else {
1468
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().');
1471                     exit();
1472                 }
1473
1474                 // Construct the HTML wrapper page
1475                 // Get all user defined URL arguments
1476                 reset($HTTP_GET_VARS);
1477                 
1478                 // This is a JPGRAPH internal defined that prevents
1479                 // us from recursively coming here again
1480                 $urlarg='?'._CSIM_DISPLAY.'=1';
1481
1482                 while( list($key,$value) = each($HTTP_GET_VARS) ) {
1483                     if( is_array($value) ) {
1484                         $n = count($value);
1485                         for( $i=0; $i < $n; ++$i ) {
1486                             $urlarg .= '&'.$key.'%5B%5D='.urlencode($value[$i]);
1487                         }
1488                     }
1489                     else {
1490                         $urlarg .= '&'.$key.'='.urlencode($value);
1491                     }
1492                 }
1493                 
1494                 echo $this->GetHTMLImageMap($aCSIMName);
1495
1496                 echo "<img src='".$aScriptName.$urlarg."' ISMAP USEMAP='#".$aCSIMName."' border=$aBorder>";
1497             }
1498         }
1499         else {
1500             $this->Stroke();
1501         }
1502     }
1503
1504     function GetTextsYMinMax($aY2=false) {
1505         if( $aY2 ) 
1506             $txts = $this->y2texts;
1507         else
1508             $txts = $this->texts;
1509         $n = count($txts);
1510         $min=null;
1511         $max=null;
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 ;
1517                 }
1518                 else {
1519                     $min = min($min,$txts[$i]->iScalePosY);
1520                     $max = max($max,$txts[$i]->iScalePosY);
1521                 }
1522             }
1523         }
1524         if( $min !== null ) {
1525             return array($min,$max);
1526         }
1527         else
1528             return null;
1529     }
1530
1531     function GetTextsXMinMax($aY2=false) {
1532         if( $aY2 ) 
1533             $txts = $this->y2texts;
1534         else
1535             $txts = $this->texts;
1536         $n = count($txts);
1537         $min=null;
1538         $max=null;
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 ;
1544                 }
1545                 else {
1546                     $min = min($min,$txts[$i]->iScalePosX);
1547                     $max = max($max,$txts[$i]->iScalePosX);
1548                 }
1549             }
1550         }
1551         if( $min !== null ) {
1552             return array($min,$max);
1553         }
1554         else
1555             return null;
1556     }
1557
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);
1566         }
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);
1573             }               
1574         }
1575         return array($min,$max);
1576     }
1577
1578     function AdjustMarginsForTitles() {
1579         $totrequired = 
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 ) ;
1586         
1587
1588         $btotrequired = 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 ;
1594                 else
1595                     $btotrequired = $this->xaxis->title->GetTextHeight($this->img) + 5 ;
1596             }
1597             else
1598                 $btotrequired = 0;
1599             
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);
1604             }
1605             else {
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);
1609             }
1610             
1611             $btotrequired += $lh + 5;
1612         }
1613
1614         if( $this->img->a == 90 ) {
1615             // DO Nothing. It gets too messy to do this properly for 90 deg...
1616         }
1617         else{
1618             if( $this->img->top_margin < $totrequired ) {
1619                 $this->SetMargin($this->img->left_margin,$this->img->right_margin,
1620                                  $totrequired,$this->img->bottom_margin);
1621             }
1622             if( $this->img->bottom_margin < $btotrequired ) {
1623                 $this->SetMargin($this->img->left_margin,$this->img->right_margin,
1624                                  $this->img->top_margin,$btotrequired);
1625             }
1626         }
1627     }
1628
1629     // Stroke the graph
1630     // $aStrokeFileName If != "" the image will be written to this file and NOT
1631     // streamed back to the browser
1632     function Stroke($aStrokeFileName="") {              
1633
1634         // Start by adjusting the margin so that potential titles will fit.
1635         $this->AdjustMarginsForTitles();
1636
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
1643         // code below.
1644         $_csim = ($aStrokeFileName===_CSIM_SPECIALFILE);
1645
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;
1651
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);
1657         }
1658                 
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);
1664             }
1665         }
1666                 
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);
1678         }
1679                 
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>");
1683
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);
1688             if( $lres ) {
1689                 list($linmin,$linmax) = $lres ;
1690                 $min = min($min,$linmin);
1691                 $max = max($max,$linmax);
1692             }
1693             $tres = $this->GetTextsYMinMax();
1694             if( $tres ) {
1695                 list($tmin,$tmax) = $tres ;
1696                 $min = min($min,$tmin);
1697                 $max = max($max,$tmax);
1698             }
1699             $this->yscale->AutoScale($this->img,$min,$max,
1700                                      $this->img->plotheight/$this->ytick_factor);
1701         }
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);
1714         }
1715
1716         if( $this->y2scale != null) {
1717             if( !$this->y2scale->IsSpecified() && count($this->y2plots)>0 ) {
1718                 list($min,$max) = $this->GetPlotsYMinMax($this->y2plots);
1719
1720                 $lres = $this->GetLinesYMinMax($this->y2lines);
1721                 if( $lres ) {
1722                     list($linmin,$linmax) = $lres ;
1723                     $min = min($min,$linmin);
1724                     $max = max($max,$linmax);
1725                 }
1726                 $tres = $this->GetTextsYMinMax(true);
1727                 if( $tres ) {
1728                     list($tmin,$tmax) = $tres ;
1729                     $min = min($min,$tmin);
1730                     $max = max($max,$tmax);
1731                 }
1732                 $this->y2scale->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor);
1733             }                   
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);
1746             }
1747         }
1748                                 
1749         //Check if we should autoscale x-axis
1750         if( !$this->xscale->IsSpecified() ) {
1751             if( substr($this->axtype,0,4) == "text" ) {
1752                 $max=0;
1753                 foreach( $this->plots as $p ) {
1754                     $max=max($max,$p->numpoints-1);
1755                 }
1756                 $min=0;
1757                 if( $this->y2axis != null ) {
1758                     foreach( $this->y2plots as $p ) {
1759                         $max=max($max,$p->numpoints-1);
1760                     }               
1761                 }
1762                 $this->xscale->Update($this->img,$min,$max);
1763                 $this->xscale->ticks->Set($this->xaxis->tick_step,1);
1764                 $this->xscale->ticks->SupressMinorTickMarks();
1765             }
1766             else {
1767                 list($min,$max) = $this->GetXMinMax();
1768                 $lres = $this->GetLinesXMinMax($this->lines);
1769                 if( $lres ) {
1770                     list($linmin,$linmax) = $lres ;
1771                     $min = min($min,$linmin);
1772                     $max = max($max,$linmax);
1773                 }
1774
1775                 $lres = $this->GetLinesXMinMax($this->y2lines);
1776                 if( $lres ) {
1777                     list($linmin,$linmax) = $lres ;
1778                     $min = min($min,$linmin);
1779                     $max = max($max,$linmax);
1780                 }
1781
1782                 $tres = $this->GetTextsXMinMax();
1783                 if( $tres ) {
1784                     list($tmin,$tmax) = $tres ;
1785                     $min = min($min,$tmin);
1786                     $max = max($max,$tmax);
1787                 }
1788
1789                 $tres = $this->GetTextsXMinMax(true);
1790                 if( $tres ) {
1791                     list($tmin,$tmax) = $tres ;
1792                     $min = min($min,$tmin);
1793                     $max = max($max,$tmax);
1794                 }
1795
1796                 $this->xscale->AutoScale($this->img,$min,$max,$this->img->plotwidth/$this->xtick_factor);
1797             }
1798                         
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);
1806             }
1807         }       
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,
1819                                      false);
1820
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);
1825             }
1826
1827         }
1828                 
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
1835         // is undefined.
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 
1838         // for 'text'   
1839
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" ) {
1844
1845             //$this->yscale->ticks->SupressZeroLabel(false);
1846             $this->xscale->ticks->SupressFirst();
1847             if( $this->y2axis != null ) {
1848                 $this->xscale->ticks->SupressLast();
1849             }
1850         }
1851         elseif( !is_numeric($this->yaxis->pos) && $this->yaxis->pos=='max' ) {
1852             $this->xscale->ticks->SupressLast();
1853         }
1854         
1855
1856         if( !$_csim ) {
1857             $this->StrokePlotArea();
1858             $this->StrokeAxis();
1859         }
1860
1861         // Stroke bands
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);
1867             }
1868
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);
1874             }
1875
1876
1877         if( $this->grid_depth == DEPTH_BACK && !$_csim) {
1878             $this->ygrid->Stroke();
1879             $this->xgrid->Stroke();
1880         }
1881                                 
1882         // Stroke Y2-axis
1883         if( $this->y2axis != null && !$_csim) {         
1884             $this->y2axis->Stroke($this->xscale);                               
1885             $this->y2grid->Stroke();
1886         }
1887                 
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);
1892         }
1893
1894         if( $this->iDoClipping ) {
1895             $oldimage = $this->img->CloneCanvasH();
1896         }
1897
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);
1903             }                                           
1904         }
1905
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);
1910             }           
1911
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);
1917             }                                           
1918         }
1919
1920
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);
1928             }
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);
1935             }
1936             else {
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.');
1938             }
1939             $this->img->Destroy();
1940             $this->img->SetCanvasH($oldimage);
1941         }
1942
1943         $this->xscale->off=$oldoff;
1944                 
1945         if( $this->grid_depth == DEPTH_FRONT && !$_csim ) {
1946             $this->ygrid->Stroke();
1947             $this->xgrid->Stroke();
1948         }
1949
1950         // Stroke bands
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);
1956             }
1957
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);
1963             }
1964
1965
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);
1970             }
1971         }
1972
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);
1976             }
1977         }
1978
1979                 
1980         // Finally draw the axis again since some plots may have nagged
1981         // the axis in the edges.
1982         if( !$_csim )
1983             $this->StrokeAxis();
1984
1985         if( $this->y2scale != null && !$_csim ) 
1986             $this->y2axis->Stroke($this->xscale);       
1987                 
1988         if( !$_csim ) {
1989             $this->StrokePlotBox();
1990         }
1991                 
1992         if( !$_csim ) {
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);
1998         }
1999
2000         $this->legend->Stroke($this->img);              
2001
2002         if( !$_csim ) {
2003
2004             $this->StrokeTexts();       
2005             $this->img->SetAngle($aa);  
2006                         
2007             // Draw an outline around the image map     
2008             if(JPG_DEBUG)
2009                 $this->DisplayClientSideaImageMapAreas();               
2010             
2011             // Adjust the appearance of the image
2012             $this->AdjustSaturationBrightnessContrast();
2013
2014             // If the filename is given as the special "__handle"
2015             // then the image handler is returned and the image is NOT
2016             // streamed back
2017             if( $aStrokeFileName == _IMG_HANDLER ) {
2018                 return $this->img->img;
2019             }
2020             else {
2021                 // Finally stream the generated picture                                 
2022                 $this->cache->PutAndStream($this->img,$this->cache_name,$this->inline,
2023                                            $aStrokeFileName);           
2024             }
2025         }
2026     }
2027
2028 //---------------
2029 // PRIVATE METHODS      
2030     function StrokeAxis() {
2031                 
2032         // Stroke axis
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;
2040                     break;
2041                 case AXSTYLE_BOXOUT :
2042                     $toppos = SIDE_UP;
2043                     $bottompos = SIDE_DOWN;         
2044                     $leftpos = SIDE_LEFT;
2045                     $rightpos = SIDE_RIGHT;
2046                     break;
2047                 case AXSTYLE_YBOXIN:
2048                     $toppos = -100;
2049                     $bottompos = SIDE_UP;
2050                     $leftpos = SIDE_RIGHT;
2051                     $rightpos = SIDE_LEFT;
2052                     break;
2053                 case AXSTYLE_YBOXOUT:
2054                     $toppos = -100;
2055                     $bottompos = SIDE_DOWN;         
2056                     $leftpos = SIDE_LEFT;
2057                     $rightpos = SIDE_RIGHT;
2058                     break;
2059                 default:
2060                     JpGRaphError::Raise('Unknown AxisStyle() : '.$this->iAxisStyle);
2061                     break;
2062             }
2063             $this->xaxis->SetPos('min');
2064             
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);
2070             
2071             $this->xaxis->SetLabelSide(SIDE_DOWN);
2072             $this->xaxis->scale->ticks->SetSide($bottompos);
2073             $this->xaxis->Stroke($this->yscale);
2074
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);
2083             }
2084
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);
2090
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);
2098             
2099         }
2100         else {
2101             $this->xaxis->Stroke($this->yscale);
2102             $this->yaxis->Stroke($this->xscale);                
2103         }
2104     }
2105
2106
2107     // Private helper function for backgound image
2108     function LoadBkgImage($aImgFormat='',$aFile='') {
2109         if( $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")  {
2116             $ext = "jpg";
2117         }
2118         
2119         if( trim($ext) == '' ) 
2120             $ext = 'png';  // Assume PNG if no extension specified
2121
2122         if( $aImgFormat == '' )
2123             $imgtag = $ext;
2124         else
2125             $imgtag = $aImgFormat;
2126
2127         if( $imgtag == "jpg" || $imgtag == "jpeg")
2128         {
2129             $f = "imagecreatefromjpeg";
2130             $imgtag = "jpg";
2131         }
2132         else
2133         {
2134             $f = "imagecreatefrom".$imgtag;
2135         }
2136
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);
2143         }
2144
2145         $img = @$f($aFile);
2146         if( !$img ) {
2147             JpGraphError::Raise(" Can't read background image: '".$aFile."'");   
2148         }
2149         return $img;
2150     }   
2151
2152     function StrokeBackgroundGrad() {
2153         if( $this->bkg_gradtype < 0  ) 
2154             return;
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 ;
2161         }
2162         else {
2163             $xl = 0;
2164             $yt = 0;
2165             $xr = $xl + $this->img->width - 1;
2166             $yb = $yt + $this->img->height - 1;
2167         }
2168         if( $this->doshadow  ) {
2169             $xr -= $this->shadow_width; 
2170             $yb -= $this->shadow_width; 
2171         }
2172         if( $this->doframe ) {
2173             
2174             $xl += $this->frame_weight;
2175             $xr -= $this->frame_weight;
2176         }
2177         $grad->FilledRectangle($xl,$yt,$xr,$yb,
2178                                $this->bkg_gradfrom,$this->bkg_gradto,
2179                                $this->bkg_gradtype);
2180     }
2181
2182     function StrokeFrameBackground() {
2183         if( $this->background_image == "" ) 
2184             return;
2185
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);
2192
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);
2196                 
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,
2203                                      $bw,$bh);
2204                 break;
2205             case BGIMG_FILLFRAME: // Fill the whole area from upper left corner, resize to just fit
2206                 $GLOBALS['copyfunc']($this->img->img,$bkgimg,
2207                                      0,0,0,0,
2208                                      $this->img->width,$this->img->height,
2209                                      $bw,$bh);
2210                 $this->StrokeFrame();
2211                 break;
2212             case BGIMG_COPY: // Just copy the image from left corner, no resizing
2213                 $GLOBALS['copyfunc']($this->img->img,$bkgimg,
2214                                      0,0,0,0,
2215                                      $bw,$bh,
2216                                      $bw,$bh);
2217                 $this->StrokeFrame();
2218                 break;
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,
2223                                      $centerx,$centery,
2224                                      0,0,
2225                                      $bw,$bh,
2226                                      $bw,$bh);
2227                 $this->StrokeFrame();
2228                 break;
2229             default:
2230                 JpGraphError::Raise(" Unknown background image layout");
2231         }                       
2232         $this->img->SetAngle($aa);              
2233     }
2234
2235     // Private
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;
2243         else
2244             $c = false;
2245         
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);
2250         }
2251         elseif( $this->framebevel ) {
2252             if( $c ) {
2253                 $this->img->SetColor($this->margin_color);
2254                 $this->img->FilledRectangle(0,0,$this->img->width-1,$this->img->height-1); 
2255             }
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);
2262             }
2263         }
2264         else {
2265             $this->img->SetLineWeight($this->frame_weight);
2266             if( $c ) {
2267                 $this->img->SetColor($this->margin_color);
2268                 $this->img->FilledRectangle(0,0,$this->img->width-1,$this->img->height-1); 
2269             }
2270             $this->img->SetColor($this->frame_color);
2271             $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);                
2272         }
2273     }
2274
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 ;
2285
2286         if( $this->background_image != "" ) {
2287             $this->StrokeFrameBackground();
2288         }
2289         else {
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();
2302             }
2303         }       
2304     }   
2305         
2306         
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);
2317             }
2318         }                                               
2319     }           
2320
2321     function SetTitleBackgroundFillStyle($aStyle,$aColor1='black',$aColor2='white') {
2322         $this->titlebkg_fillstyle = $aStyle;
2323         $this->titlebkg_scolor1 = $aColor1;
2324         $this->titlebkg_scolor2 = $aColor2;
2325     }
2326
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 ;
2335     }
2336
2337
2338     function StrokeTitles() {
2339
2340         $margin=3;
2341
2342         if( $this->titlebackground ) {
2343
2344             // Find out height
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;
2350             }
2351             if( $this->subsubtitle->t != "" && !$this->subsubtitle->hide ) {
2352                 $h += $this->subsubtitle->GetTextHeight($this->img)+$margin+
2353                     $this->subsubtitle->margin;
2354             }
2355             $this->img->PushColor($this->titlebackground_color);
2356             if( $this->titlebackground_style === 1 ) {
2357                 // Inside the frame
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 ;
2362                     $h += $y1 ;
2363                 }
2364                 else {
2365                     $x1 = $y1 = $this->frame_weight;
2366                     $x2 = $this->img->width - 2*$x1;
2367                 }
2368             }
2369             elseif( $this->titlebackground_style === 2 ) {
2370                 // Cover the frame as well
2371                 $x1 = $y1 = 0;
2372                 $x2 = $this->img->width - 1 ;
2373             }
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)
2378                 $x1 = $y1 = 0;
2379                 $x2 = $this->img->width - 1 ;
2380                 $h += $this->framebeveldepth ;
2381                 $this->title->margin += $this->framebeveldepth ;
2382             }
2383             else {
2384                 JpGraphError::Raise('Unknown title background style.');
2385             }
2386
2387             if( $this->titlebackground_framestyle === 3 ) {
2388                 $h += $this->titlebackground_bevelheight*2 + 1  ;
2389                 $this->title->margin += $this->titlebackground_bevelheight ;
2390             }
2391
2392             if( $this->doshadow ) {
2393                 $x2 -= $this->shadow_width ;
2394             }
2395             
2396             if( $this->titlebkg_fillstyle==TITLEBKG_FILLSTYLE_HSTRIPED ) {
2397                 $this->img->FilledRectangle2($x1,$y1,$x2,$h,
2398                                              $this->titlebkg_scolor1,
2399                                              $this->titlebkg_scolor2);
2400             }
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);
2405             }
2406             else {
2407                 // Solid fill
2408                 $this->img->FilledRectangle($x1,$y1,$x2,$h);
2409             }
2410             $this->img->PopColor();
2411
2412             $this->img->PushColor($this->titlebackground_framecolor);
2413             $this->img->SetLineWeight($this->titlebackground_frameweight);
2414             if( $this->titlebackground_framestyle == 1 ) {
2415                 // Frame background
2416                 $this->img->Rectangle($x1,$y1,$x2,$h);
2417             }
2418             elseif( $this->titlebackground_framestyle == 2 ) {
2419                 // Bottom line only
2420                 $this->img->Line($x1,$h,$x2,$h);
2421             }
2422             elseif( $this->titlebackground_framestyle == 3 ) {
2423                 $this->img->Bevel($x1,$y1,$x2,$h,$this->titlebackground_bevelheight);
2424             }
2425             $this->img->PopColor();
2426
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);
2437                 }
2438             }
2439         }
2440
2441         // Stroke title
2442         $y = $this->title->margin; 
2443         $this->title->Center(0,$this->img->width,$y);
2444         $this->title->Stroke($this->img);
2445                 
2446         // ... and subtitle
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);
2450
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);
2455
2456         // ... and fancy title
2457         $this->tabtitle->Stroke($this->img);
2458
2459     }
2460
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);
2466             }
2467         }
2468
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);
2472             }
2473         }
2474
2475     }
2476
2477     function DisplayClientSideaImageMapAreas() {
2478         // Debug stuff - display the outline of the image map areas
2479         foreach ($this->plots as $p) {
2480             $csim.= $p->GetCSIMareas();
2481         }
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]);
2491                     }
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]);                                        
2499                 }
2500             }
2501         }
2502     }
2503
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);
2510     }
2511
2512     // Text scale offset in world coordinates
2513     function SetTextScaleOff($aOff) {
2514         $this->text_scale_off = $aOff;
2515         $this->xscale->text_scale_off = $aOff;
2516     }
2517
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 ;
2523         $max = $min ;
2524         $flg = false;
2525         for( $i=0; $i < $n; ++$i ) {
2526             if( $aLines[$i]->direction == HORIZONTAL ) {
2527                 $flg = true ;
2528                 $v = $aLines[$i]->scaleposition ;
2529                 if( $min > $v ) $min = $v ;
2530                 if( $max < $v ) $max = $v ;
2531             }
2532         }
2533         return $flg ? array($min,$max) : false ;
2534     }
2535
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 ;
2541         $max = $min ;
2542         $flg = false;
2543         for( $i=0; $i < $n; ++$i ) {
2544             if( $aLines[$i]->direction == VERTICAL ) {
2545                 $flg = true ;
2546                 $v = $aLines[$i]->scaleposition ;
2547                 if( $min > $v ) $min = $v ;
2548                 if( $max < $v ) $max = $v ;
2549             }
2550         }
2551         return $flg ? array($min,$max) : false ;
2552     }
2553
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);
2563         }
2564         if( $min == "" ) $min = 0;
2565         if( $max == "" ) $max = 0;
2566         if( $min == 0 && $max == 0 ) {
2567             // Special case if all values are 0
2568             $min=0;$max=1;                      
2569         }
2570         return array($min,$max);
2571     }
2572
2573 } // Class
2574
2575
2576 //===================================================
2577 // CLASS TTF
2578 // Description: Handle TTF font names
2579 //===================================================
2580 class TTF {
2581     var $font_files,$style_names;
2582 //---------------
2583 // CONSTRUCTOR
2584     function TTF() {
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=>'' )    
2599 );
2600     }
2601
2602 //---------------
2603 // PUBLIC METHODS       
2604     // Create the TTF file from the font specification
2605     function File($family,$style=FS_NORMAL) {
2606         
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/');
2609         }
2610
2611         $fam = @$this->font_files[$family];
2612         if( !$fam ) {
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/");
2618         }
2619         $f = @$fam[$style];
2620
2621         if( $f==='' )
2622             JpGraphError::Raise('Style "'.$this->style_names[$style].'" is not available for font family '.$this->font_files[$family][FS_NORMAL].'.');
2623         if( !$f )
2624             JpGraphError::Raise("Unknown font style specification [$fam].");
2625         $f = TTF_DIR.$f;
2626         if( file_exists($f) === false || is_readable($f) === false ) {
2627             JpGraphError::Raise("Font file \"$f\" is not readable or does not exist.");
2628         }
2629         return $f;
2630     }
2631 } // Class
2632
2633 //===================================================
2634 // CLASS LineProperty
2635 // Description: Holds properties for a line
2636 //===================================================
2637 class LineProperty {
2638     var $iWeight=1, $iColor="black",$iStyle="solid";
2639     var $iShow=true;
2640         
2641 //---------------
2642 // PUBLIC METHODS       
2643     function SetColor($aColor) {
2644         $this->iColor = $aColor;
2645     }
2646         
2647     function SetWeight($aWeight) {
2648         $this->iWeight = $aWeight;
2649     }
2650         
2651     function SetStyle($aStyle) {
2652         $this->iStyle = $aStyle;
2653     }
2654                 
2655     function Show($aShow=true) {
2656         $this->iShow=$aShow;
2657     }
2658         
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);
2665         }
2666     }
2667 }
2668
2669
2670 //===================================================
2671 // CLASS Text
2672 // Description: Arbitrary text object that can be added to the graph
2673 //===================================================
2674 class Text {
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";
2680     var $margin;
2681     var $icornerradius=0,$ishadowwidth=3;
2682     var $iScalePosY=null,$iScalePosX=null;
2683
2684 //---------------
2685 // CONSTRUCTOR
2686
2687     // Create new text at absolute pixel coordinates
2688     function Text($aTxt="",$aXAbsPos=0,$aYAbsPos=0) {
2689         $this->t = $aTxt;
2690         $this->x = round($aXAbsPos);
2691         $this->y = round($aYAbsPos);
2692         $this->margin = 0;
2693     }
2694 //---------------
2695 // PUBLIC METHODS       
2696     // Set the string in the text object
2697     function Set($aTxt) {
2698         $this->t = $aTxt;
2699     }
2700         
2701     // Alias for Pos()
2702     function SetPos($aXAbsPos=0,$aYAbsPos=0,$aHAlign="left",$aVAlign="top") {
2703         $this->Pos($aXAbsPos,$aYAbsPos,$aHAlign,$aVAlign);
2704     }
2705     
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;
2712     }
2713
2714     function SetScalePos($aX,$aY) {
2715         $this->iScalePosX = $aX;
2716         $this->iScalePosY = $aY;
2717     }
2718         
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;
2725     }           
2726     
2727     // Alias
2728     function SetAlign($aHAlign,$aVAlign="top",$aParagraphAlign="") {
2729         $this->Align($aHAlign,$aVAlign,$aParagraphAlign);
2730     }
2731
2732     // Specifies the alignment for a multi line text
2733     function ParagraphAlign($aAlign) {
2734         $this->paragraph_align = $aAlign;
2735     }
2736
2737     function SetShadow($aShadowColor='gray',$aShadowWidth=3) {
2738         $this->ishadowwidth=$aShadowWidth;
2739         $this->shadow=$aShadowColor;
2740         $this->boxed=true;
2741     }
2742         
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 )
2747             $this->boxed=false;
2748         else
2749             $this->boxed=true;
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;
2758     }
2759         
2760     // Hide the text
2761     function Hide($aHide=true) {
2762         $this->hide=$aHide;
2763     }
2764         
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) 
2768     // jpgraph_gantt
2769     function Show($aShow=true) {
2770         $this->hide=!$aShow;
2771     }
2772         
2773     // Specify font
2774     function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
2775         $this->font_family=$aFamily;
2776         $this->font_style=$aStyle;
2777         $this->font_size=$aSize;
2778     }
2779                         
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;               
2786     }
2787         
2788     // Set text color
2789     function SetColor($aColor) {
2790         $this->color = $aColor;
2791     }
2792         
2793     function SetAngle($aAngle) {
2794         $this->SetOrientation($aAngle);
2795     }
2796         
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" )
2802             $this->dir = 0;
2803         elseif( $aDirection=="v" )
2804             $this->dir = 90;
2805         else JpGraphError::Raise(" Invalid direction specified for text.");
2806     }
2807         
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);
2812         return $w;      
2813     }
2814         
2815     // Hight of font
2816     function GetFontHeight($aImg) {
2817         $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
2818         $h = $aImg->GetFontHeight();
2819         return $h;
2820
2821     }
2822
2823     function GetTextHeight($aImg) {
2824         $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);  
2825         $h = $aImg->GetTextHeight($this->t,$this->dir);
2826         return $h;
2827     }
2828
2829     // Set the margin which will be interpretated differently depending
2830     // on the context.
2831     function SetMargin($aMarg) {
2832         $this->margin = $aMarg;
2833     }
2834
2835     function StrokeWithScale($aImg,$axscale,$ayscale) {
2836         if( $this->iScalePosX === null ||
2837             $this->iScalePosY === null ) {
2838             $this->Stroke($aImg);
2839         }
2840         else {
2841             $this->Stroke($aImg,
2842                           round($axscale->Translate($this->iScalePosX)),
2843                           round($ayscale->Translate($this->iScalePosY)));
2844         }
2845     }
2846         
2847     // Display text in image
2848     function Stroke($aImg,$x=null,$y=null) {
2849
2850         if( !empty($x) ) $this->x = round($x);
2851         if( !empty($y) ) $this->y = round($y);
2852
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;
2857
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);
2869         }
2870         else {
2871             $aImg->StrokeText($this->x,$this->y,$this->t,$this->dir,
2872                               $this->paragraph_align);
2873         }
2874         $aImg->PopColor($this->color);  
2875     }
2876 } // Class
2877
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() {
2883         $this->t = '';
2884         $this->font_style = FS_BOLD;
2885         $this->hide = true;
2886     }
2887
2888     function SetColor($aTxtColor,$aFillColor='lightyellow',$aBorderColor='black') {
2889         $this->color = $aTxtColor;
2890         $this->fillcolor = $aFillColor;
2891         $this->bordercolor = $aBorderColor;
2892     }
2893
2894     function SetFillColor($aFillColor) {
2895         $this->fillcolor = $aFillColor;
2896     }
2897
2898     function SetTabAlign($aAlign) {
2899         // Synonym for SetPos
2900         $this->align = $aAlign;
2901     }
2902
2903     function SetPos($aAlign) {
2904         $this->align = $aAlign;
2905     }
2906     
2907     function SetWidth($aWidth) {
2908         $this->width = $aWidth ;
2909     }
2910
2911     function Set($t) {
2912         $this->t = $t;
2913         $this->hide = false;
2914     }
2915
2916     function SetCorner($aD) {
2917         $this->corner = $aD ;
2918     }
2919
2920     function Stroke($aImg) {
2921         if( $this->hide ) 
2922             return;
2923         $this->boxed = false;
2924         $w = $this->GetWidth($aImg) + 2*$this->posx;
2925         $h = $this->GetTextHeight($aImg) + 2*$this->posy;
2926
2927         $x = $aImg->left_margin;
2928         $y = $aImg->top_margin;
2929
2930         if( $this->width === TABTITLE_WIDTHFIT ) {
2931             if( $this->align == 'left' ) {
2932                 $p = array($x,                $y,
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,
2937                            $x + $w, $y);
2938             }
2939             elseif( $this->align == 'center' ) {
2940                 $x += round($aImg->plotwidth/2) - round($w/2);
2941                 $p = array($x, $y,
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,
2946                            $x + $w, $y);
2947             }
2948             else {
2949                 $x += $aImg->plotwidth -$w;
2950                 $p = array($x, $y,
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,
2955                            $x + $w, $y);
2956             }
2957         }
2958         else {
2959             if( $this->width === TABTITLE_WIDTHFULL )
2960                 $w = $aImg->plotwidth ;
2961             else
2962                 $w = $this->width ;
2963
2964             // Make the tab fit the width of the plot area
2965             $p = array($x,                $y,
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,
2970                        $x + $w, $y);
2971             
2972         }
2973         $aImg->SetTextAlign('left','bottom');
2974         $x += $this->posx;
2975         $y -= $this->posy;
2976
2977         $aImg->SetColor($this->fillcolor);
2978         $aImg->FilledPolygon($p);
2979
2980         $aImg->SetColor($this->bordercolor);
2981         $aImg->Polygon($p,true);
2982         
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');
2986     }
2987
2988 }
2989
2990 //===================================================
2991 // CLASS SuperScriptText
2992 // Description: Format a superscript text
2993 //===================================================
2994 class SuperScriptText extends Text {
2995     var $iSuper="";
2996     var $sfont_family="",$sfont_style="",$sfont_size=8;
2997     var $iSuperMargin=2,$iVertOverlap=4,$iSuperScale=0.65;
2998     var $iSDir=0;
2999     var $iSimple=false;
3000
3001     function SuperScriptText($aTxt="",$aSuper="",$aXAbsPos=0,$aYAbsPos=0) {
3002         parent::Text($aTxt,$aXAbsPos,$aYAbsPos);
3003         $this->iSuper = $aSuper;
3004     }
3005
3006     function FromReal($aVal,$aPrecision=2) {
3007         // Convert a floating point number to scientific notation
3008         $neg=1.0;
3009         if( $aVal < 0 ) {
3010             $neg = -1.0;
3011             $aVal = -$aVal;
3012         }
3013                 
3014         $l = floor(log10($aVal));
3015         $a = sprintf("%0.".$aPrecision."f",round($aVal / pow(10,$l),$aPrecision));
3016         $a *= $neg;
3017         if( $this->iSimple && ($a == 1 || $a==-1) ) $a = '';
3018         
3019         if( $a != '' )
3020             $this->t = $a.' * 10';
3021         else {
3022             if( $neg == 1 )
3023                 $this->t = '10';
3024             else
3025                 $this->t = '-10';
3026         }
3027         $this->iSuper = $l;
3028     }
3029
3030     function Set($aTxt,$aSuper="") {
3031         $this->t = $aTxt;
3032         $this->iSuper = $aSuper;
3033     }
3034
3035     function SetSuperFont($aFontFam,$aFontStyle=FS_NORMAL,$aFontSize=8) {
3036         $this->sfont_family = $aFontFam;
3037         $this->sfont_style = $aFontStyle;
3038         $this->sfont_size = $aFontSize;
3039     }
3040
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;
3048         return $w;
3049     }
3050         
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();
3057         return $h;
3058     }
3059
3060     // Hight of text
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);
3066         return $h;
3067     }
3068
3069     function Stroke($aImg,$ax=-1,$ay=-1) {
3070         
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
3074         
3075         $w = parent::GetWidth($aImg);
3076         $h = parent::GetTextHeight($aImg);
3077         switch( $this->valign ) {
3078             case 'top':
3079                 $sy = $this->y;
3080                 break;
3081             case 'center':
3082                 $sy = $this->y - $h/2;
3083                 break;
3084             case 'bottom':
3085                 $sy = $this->y - $h;
3086                 break;
3087             default:
3088                 JpGraphError::Raise('PANIC: Internal error in SuperScript::Stroke(). Unknown vertical alignment for text');
3089                 exit();
3090         }
3091
3092         switch( $this->halign ) {
3093             case 'left':
3094                 $sx = $this->x + $w;
3095                 break;
3096             case 'center':
3097                 $sx = $this->x + $w/2;
3098                 break;
3099             case 'right':
3100                 $sx = $this->x;
3101                 break;
3102             default:
3103                 JpGraphError::Raise('PANIC: Internal error in SuperScript::Stroke(). Unknown horizontal alignment for text');
3104                 exit();
3105         }
3106
3107         $sx += $this->iSuperMargin;
3108         $sy += $this->iVertOverlap;
3109
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 ) {
3115                     $sff = FF_FONT0;
3116                 }
3117                 elseif( $this->font_family == FF_FONT1 ) {
3118                     if( $this->font_style == FS_NORMAL )
3119                         $sff = FF_FONT0;
3120                     else
3121                         $sff = FF_FONT1;
3122                 }
3123                 else {
3124                     $sff = FF_FONT1;
3125                 }
3126                 $sfs = $this->font_style;
3127                 $sfz = $this->font_size;
3128             }
3129             else {
3130                 // TTF fonts
3131                 $sff = $this->font_family;
3132                 $sfs = $this->font_style;
3133                 $sfz = floor($this->font_size*$this->iSuperScale);              
3134                 if( $sfz < 8 ) $sfz = 8;
3135             }       
3136             $this->sfont_family = $sff;
3137             $this->sfont_style = $sfs;
3138             $this->sfont_size = $sfz;       
3139         } 
3140         else {
3141             $sff = $this->sfont_family;
3142             $sfs = $this->sfont_style;
3143             $sfz = $this->sfont_size;       
3144         }
3145
3146         parent::Stroke($aImg,$ax,$ay);
3147
3148
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 ) {
3153             $sx -= 2;
3154             $sy += 3;
3155         }
3156
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');
3161         $aImg->PopColor();      
3162     }
3163 }
3164
3165
3166 //===================================================
3167 // CLASS Grid
3168 // Description: responsible for drawing grid lines in graph
3169 //===================================================
3170 class Grid {
3171     var $img;
3172     var $scale;
3173     var $grid_color='#DDDDDD',$grid_mincolor='#DDDDDD';
3174     var $type="solid";
3175     var $show=false, $showMinor=false,$weight=1;
3176     var $fill=false,$fillcolor=array('#EFEFEF','#BBCCFF');
3177 //---------------
3178 // CONSTRUCTOR
3179     function Grid(&$aAxis) {
3180         $this->scale = &$aAxis->scale;
3181         $this->img = &$aAxis->img;
3182     }
3183 //---------------
3184 // PUBLIC METHODS
3185     function SetColor($aMajColor,$aMinColor=false) {
3186         $this->grid_color=$aMajColor;
3187         if( $aMinColor === false ) 
3188             $aMinColor = $aMajColor ;
3189         $this->grid_mincolor = $aMinColor;
3190     }
3191         
3192     function SetWeight($aWeight) {
3193         $this->weight=$aWeight;
3194     }
3195         
3196     // Specify if grid should be dashed, dotted or solid
3197     function SetLineStyle($aType) {
3198         $this->type = $aType;
3199     }
3200         
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;
3205     }
3206     
3207     function SetFill($aFlg=true,$aColor1='lightgray',$aColor2='lightblue') {
3208         $this->fill = $aFlg;
3209         $this->fillcolor = array( $aColor1, $aColor2 );
3210     }
3211         
3212     // Display the grid
3213     function Stroke() {
3214         if( $this->showMinor ) {
3215             $tmp = $this->grid_color;
3216             $this->grid_color = $this->grid_mincolor;
3217             $this->DoStroke($this->scale->ticks->ticks_pos);
3218
3219             $this->grid_color = $tmp;
3220             $this->DoStroke($this->scale->ticks->maj_ticks_pos);
3221         }
3222         else {
3223             $this->DoStroke($this->scale->ticks->maj_ticks_pos);
3224         }
3225     }
3226         
3227 //--------------
3228 // Private methods      
3229     // Draw the grid
3230     function DoStroke(&$aTicksPos) {
3231         if( !$this->show )
3232             return;     
3233         $nbrgrids = count($aTicksPos);  
3234
3235         if( $this->scale->type=="y" ) {
3236             $xl=$this->img->left_margin;
3237             $xr=$this->img->width-$this->img->right_margin;
3238             
3239             if( $this->fill ) {
3240                 // Draw filled areas
3241                 $y2 = $aTicksPos[0];
3242                 $i=1;
3243                 while( $i < $nbrgrids ) {
3244                     $y1 = $y2;
3245                     $y2 = $aTicksPos[$i++];
3246                     $this->img->SetColor($this->fillcolor[$i & 1]);
3247                     $this->img->FilledRectangle($xl,$y1,$xr,$y2);
3248                 }
3249             }
3250
3251             $this->img->SetColor($this->grid_color);
3252             $this->img->SetLineWeight($this->weight);
3253
3254             // Draw grid lines
3255             for($i=0; $i<$nbrgrids; ++$i) {
3256                 $y=$aTicksPos[$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);
3265             }
3266         }
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;
3271
3272             if( $this->fill ) {
3273                 // Draw filled areas
3274                 $x2 = $aTicksPos[0];
3275                 $i=1;
3276                 while( $i < $nbrgrids ) {
3277                     $x1 = $x2;
3278                     $x2 = min($aTicksPos[$i++],$limit) ;
3279                     $this->img->SetColor($this->fillcolor[$i & 1]);
3280                     $this->img->FilledRectangle($x1,$yu,$x2,$yl);
3281                 }
3282             }
3283
3284             $this->img->SetColor($this->grid_color);
3285             $this->img->SetLineWeight($this->weight);
3286
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
3291             $i=0;
3292             $x=$aTicksPos[$i];      
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);  
3302                 ++$i;  
3303             }
3304         }       
3305         else {
3306             JpGraphError::Raise('Internal error: Unknown grid axis ['.$this->scale->type.']');
3307         }
3308         return true;
3309     }
3310 } // Class
3311
3312 //===================================================
3313 // CLASS Axis
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
3318 // follow. 
3319 //===================================================
3320 class Axis {
3321     var $pos = false;
3322     var $weight=1;
3323     var $color=array(0,0,0),$label_color=array(0,0,0);
3324     var $img=null,$scale=null; 
3325     var $hide=false;
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.
3330     var $tick_step=1;
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;
3338
3339 //---------------
3340 // CONSTRUCTOR
3341     function Axis(&$img,&$aScale,$color=array(0,0,0)) {
3342         $this->img = &$img;
3343         $this->scale = &$aScale;
3344         $this->color = $color;
3345         $this->title=new Text("");
3346                 
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');
3354         }
3355         else {
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');
3362         }
3363     }
3364 //---------------
3365 // PUBLIC METHODS       
3366         
3367     function SetLabelFormat($aFormStr) {
3368         $this->scale->ticks->SetLabelFormat($aFormStr);
3369     }
3370         
3371     function SetLabelFormatString($aFormStr) {
3372         $this->scale->ticks->SetLabelFormat($aFormStr);
3373     }
3374         
3375     function SetLabelFormatCallback($aFuncName) {
3376         $this->scale->ticks->SetFormatCallback($aFuncName);
3377     }
3378
3379     function SetLabelAlign($aHAlign,$aVAlign="top",$aParagraphAlign='left') {
3380         $this->label_halign = $aHAlign;
3381         $this->label_valign = $aVAlign;
3382         $this->label_para_align = $aParagraphAlign;
3383     }           
3384
3385     // Don't display the first label
3386     function HideFirstTickLabel($aShow=false) {
3387         $this->show_first_label=$aShow;
3388     }
3389
3390     function HideLastTickLabel($aShow=false) {
3391         $this->show_last_label=$aShow;
3392     }
3393
3394     function HideTicks($aHideMinor=true,$aHideMajor=true) {
3395         $this->scale->ticks->SupressMinorTickMarks($aHideMinor);
3396         $this->scale->ticks->SupressTickMarks($aHideMajor);
3397     }
3398
3399     // Hide zero label
3400     function HideZeroLabel($aFlag=true) {
3401         $this->scale->ticks->SupressZeroLabel();
3402         //$this->hide_zero_label = $aFlag;
3403     }
3404         
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;
3415     }
3416         
3417     // Hide the axis
3418     function Hide($aHide=true) {
3419         $this->hide=$aHide;
3420     }
3421
3422     // Hide the actual axis-line, but still print the labels
3423     function HideLine($aHide=true) {
3424         $this->hide_line = $aHide;
3425     }
3426
3427     function HideLabels($aHide=true) {
3428         $this->hide_labels = $aHide;
3429     }
3430     
3431
3432     // Weight of axis
3433     function SetWeight($aWeight) {
3434         $this->weight = $aWeight;
3435     }
3436
3437     // Axis color
3438     function SetColor($aColor,$aLabelColor=false) {
3439         $this->color = $aColor;
3440         if( !$aLabelColor ) $this->label_color = $aColor;
3441         else $this->label_color = $aLabelColor;
3442     }
3443         
3444     // Title on axis
3445     function SetTitle($aTitle,$aAdjustAlign="high") {
3446         $this->title->Set($aTitle);
3447         $this->title_adjust=$aAdjustAlign;
3448     }
3449         
3450     // Specify distance from the axis
3451     function SetTitleMargin($aMargin) {
3452         $this->title_margin=$aMargin;
3453     }
3454         
3455     // Which side of the axis should the axis title be?
3456     function SetTitleSide($aSideOfAxis) {
3457         $this->title_side = $aSideOfAxis;
3458     }
3459
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);
3466     }
3467     
3468     function SetTickSide($aDir) {
3469         $this->scale->ticks->SetSide($aDir);
3470     }
3471         
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;
3476     }
3477         
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;
3483     }
3484
3485     function SetLabelMargin($aMargin) {
3486         $this->tick_label_margin=$aMargin;
3487     }
3488         
3489     // Specify that every $step of the ticks should be displayed starting
3490     // at $start
3491     // DEPRECATED FUNCTION: USE SetTextTickInterval() INSTEAD
3492     function SetTextTicks($step,$start=0) {
3493         JpGraphError::Raise(" SetTextTicks() is deprecated. Use SetTextTickInterval() instead.");               
3494     }
3495
3496     // Specify that every $step of the ticks should be displayed starting
3497     // at $start        
3498     function SetTextTickInterval($aStep,$aStart=0) {
3499         $this->scale->ticks->SetTextLabelStart($aStart);
3500         $this->tick_step=$aStep;
3501     }
3502          
3503     // Specify that every $step tick mark should have a label 
3504     // should be displayed starting
3505     function SetTextLabelInterval($aStep) {
3506         if( $aStep < 1 )
3507             JpGraphError::Raise(" Text label interval must be specified >= 1.");
3508         $this->label_step=$aStep;
3509     }
3510         
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;
3517     }
3518     
3519     function SetLabelSide($aSidePos) {
3520         $this->labelPos=$aSidePos;
3521     }
3522
3523     // Set the font
3524     function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
3525         $this->font_family = $aFamily;
3526         $this->font_style = $aStyle;
3527         $this->font_size = $aSize;
3528     }
3529
3530     // Position for axis line on the "other" scale
3531     function SetPos($aPosOnOtherScale) {
3532         $this->pos=$aPosOnOtherScale;
3533     }
3534         
3535     // Specify the angle for the tick labels
3536     function SetLabelAngle($aAngle) {
3537         $this->label_angle = $aAngle;
3538     }   
3539         
3540     // Stroke the axis.
3541     function Stroke($aOtherAxisScale) {         
3542         if( $this->hide ) return;               
3543         if( is_numeric($this->pos) ) {
3544             $pos=$aOtherAxisScale->Translate($this->pos);
3545         }
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];
3549             }
3550             elseif($this->pos == "max") {
3551                 $pos = $aOtherAxisScale->scale_abs[1];
3552             }
3553             else { // If negative set x-axis at 0
3554                 $this->pos=0;
3555                 $pos=$aOtherAxisScale->Translate(0);
3556             }
3557         }       
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");
3572             else {      
3573                 JpGraphError::Raise('Unknown alignment specified for X-axis title. ('.$this->title_adjust.')');
3574             }
3575         }
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);
3582             $x=$pos ;
3583             if( $this->title_side == SIDE_LEFT ) {
3584                 $x -= $this->title_margin;
3585                 $x -= $this->title->margin;
3586                 $halign="right";
3587             }
3588             else {
3589                 $x += $this->title_margin;
3590                 $x += $this->title->margin;
3591                 $halign="left";
3592             }
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");
3606             else        
3607                 JpGraphError::Raise('Unknown alignment specified for Y-axis title. ('.$this->title_adjust.')');
3608                 
3609         }
3610         $this->scale->ticks->Stroke($this->img,$this->scale,$pos);
3611         if( !$this->hide_labels ) {
3612             $this->StrokeLabels($pos);
3613         }
3614         $this->title->Stroke($this->img);
3615     }
3616
3617 //---------------
3618 // PRIVATE METHODS      
3619     // Draw all the tick labels on major tick marks
3620     function StrokeLabels($aPos,$aMinor=false,$aAbsLabel=false) {
3621
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;
3625
3626         // Only draw labels at major tick marks
3627         $nbr = count($this->scale->ticks->maj_ticks_label);
3628
3629         // We have the option to not-display the very first mark
3630         // (Usefull when the first label might interfere with another
3631         // axis.)
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
3635         // of the scale.        
3636         $ncolor=0;
3637         if( isset($this->ticks_label_colors) )
3638             $ncolor=count($this->ticks_label_colors);
3639         
3640         while( $i<$nbr ) {
3641             // $tpos holds the absolute text position for the label
3642             $tpos=$this->scale->ticks->maj_ticklabels_pos[$i];
3643
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 ) {
3648                 return; 
3649             }
3650             // we only draw every $label_step label
3651             if( ($i % $this->label_step)==0 ) {
3652
3653                 // Set specific label color if specified
3654                 if( $ncolor > 0 )
3655                     $this->img->SetColor($this->ticks_label_colors[$i % $ncolor]);
3656                 
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();
3660                                 
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];
3667                 else {
3668                     if( $aAbsLabel ) 
3669                         $label=abs($this->scale->ticks->maj_ticks_label[$i]);
3670                     else
3671                         $label=$this->scale->ticks->maj_ticks_label[$i];
3672                     if( $this->scale->textscale && $this->scale->ticks->label_formfunc == '' ) {
3673                         ++$label;
3674                     }
3675                 }
3676                                         
3677                 //if( $this->hide_zero_label && $label==0.0 ) {
3678                 //      ++$i;
3679                 //      continue;
3680                 //}                                     
3681                                         
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');
3687                             else
3688                                 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
3689                             
3690                         }
3691                         else {
3692                             if( $this->label_halign=='' && $this->label_valign=='')
3693                                 $this->img->SetTextAlign("right","top");
3694                             else
3695                                 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
3696                         }
3697
3698                         $this->img->StrokeText($tpos,$aPos+$this->tick_label_margin,$label,
3699                                                $this->label_angle,$this->label_para_align);
3700                     }
3701                     else {
3702                         if( $this->label_angle==0 || $this->label_angle==90 ) {
3703                             if( $this->label_halign=='' && $this->label_valign=='')
3704                                 $this->img->SetTextAlign("center","bottom");
3705                             else
3706                                 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
3707                         }
3708                         else {
3709                             if( $this->label_halign=='' && $this->label_valign=='')
3710                                 $this->img->SetTextAlign("right","bottom");
3711                             else
3712                                 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
3713                         }
3714                         $this->img->StrokeText($tpos,$aPos-$this->tick_label_margin,$label,
3715                                                $this->label_angle,$this->label_para_align);
3716                     }
3717                 }
3718                 else {
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");
3725                         else
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); 
3728                     }
3729                     else { // To the right of the y-axis
3730                         if( $this->label_halign=='' && $this->label_valign=='') 
3731                             $this->img->SetTextAlign("left","center");
3732                         else
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); 
3735                     }
3736                 }
3737             }
3738             ++$i;       
3739         }                                                               
3740     }                   
3741
3742 } // Class
3743
3744 //===================================================
3745 // CLASS Ticks
3746 // Description: Abstract base class for drawing linear and logarithmic
3747 // tick marks on axis
3748 //===================================================
3749 class Ticks {
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)?
3752     var $scale;
3753     var $is_set=false;
3754     var $precision;
3755     var $supress_zerolabel=false,$supress_first=false;
3756     var $supress_last=false,$supress_tickmarks=false,$supress_minor_tickmarks=false;
3757     var $mincolor="",$majcolor="";
3758     var $weight=1;
3759     var $label_formatstr='';   // C-style format string to use for labels
3760     var $label_formfunc='';
3761
3762
3763 //---------------
3764 // CONSTRUCTOR
3765     function Ticks(&$aScale) {
3766         $this->scale=&$aScale;
3767         $this->precision = -1;
3768     }
3769
3770 //---------------
3771 // PUBLIC METHODS       
3772     // Set format string for automatic labels
3773     function SetLabelFormat($aFormatString) {
3774         $this->label_formatstr=$aFormatString;
3775     }
3776         
3777     function SetFormatCallback($aCallbackFuncName) {
3778         $this->label_formfunc = $aCallbackFuncName;
3779     }
3780         
3781     // Don't display the first zero label
3782     function SupressZeroLabel($aFlag=true) {
3783         $this->supress_zerolabel=$aFlag;
3784     }
3785         
3786     // Don't display minor tick marks
3787     function SupressMinorTickMarks($aHide=true) {
3788         $this->supress_minor_tickmarks=$aHide;
3789     }
3790         
3791     // Don't display major tick marks
3792     function SupressTickMarks($aHide=true) {
3793         $this->supress_tickmarks=$aHide;
3794     }
3795         
3796     // Hide the first tick mark
3797     function SupressFirst($aHide=true) {
3798         $this->supress_first=$aHide;
3799     }
3800         
3801     // Hide the last tick mark
3802     function SupressLast($aHide=true) {
3803         $this->supress_last=$aHide;
3804     }
3805
3806     // Size (in pixels) of minor tick marks
3807     function GetMinTickAbsSize() {
3808         return $this->minor_abs_size;
3809     }
3810         
3811     // Size (in pixels) of major tick marks
3812     function GetMajTickAbsSize() {
3813         return $this->major_abs_size;           
3814     }
3815         
3816     function SetSize($aMajSize,$aMinSize=3) {
3817         $this->major_abs_size = $aMajSize;              
3818         $this->minor_abs_size = $aMinSize;              
3819     }
3820
3821     // Have the ticks been specified
3822     function IsSpecified() {
3823         return $this->is_set;
3824     }
3825         
3826     // Set the distance between major and minor tick marks
3827     function Set($aMaj,$aMin) {
3828         // "Virtual method"
3829         // Should be implemented by the concrete subclass
3830         // if any action is wanted.
3831     }
3832         
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;
3839     }
3840
3841     function SetSide($aSide) {
3842         $this->direction=$aSide;
3843     }
3844         
3845     // Which side of the axis should the ticks be on
3846     function SetDirection($aSide=SIDE_RIGHT) {
3847         $this->direction=$aSide;
3848     }
3849         
3850     // Set colors for major and minor tick marks
3851     function SetMarkColor($aMajorColor,$aMinorColor="") {
3852         $this->SetColor($aMajorColor,$aMinorColor);
3853     }
3854     
3855     function SetColor($aMajorColor,$aMinorColor="") {
3856         $this->majcolor=$aMajorColor;
3857                 
3858         // If not specified use same as major
3859         if( $aMinorColor=="" ) 
3860             $this->mincolor=$aMajorColor;
3861         else
3862             $this->mincolor=$aMinorColor;
3863     }
3864         
3865     function SetWeight($aWeight) {
3866         $this->weight=$aWeight;
3867     }
3868         
3869 } // Class
3870
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;
3881 //---------------
3882 // CONSTRUCTOR
3883     function LinearTicks() {
3884         $this->precision = -1;
3885     }
3886
3887 //---------------
3888 // PUBLIC METHODS       
3889         
3890         
3891     // Return major step size in world coordinates
3892     function GetMajor() {
3893         return $this->major_step;
3894     }
3895         
3896     // Return minor step size in world coordinates
3897     function GetMinor() {
3898         return $this->minor_step;
3899     }
3900         
3901     // Set Minor and Major ticks (in world coordinates)
3902     function Set($aMajStep,$aMinStep=false) {
3903         if( $aMinStep==false ) 
3904             $aMinStep=$aMajStep;
3905         
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
3911                                 problem.");
3912         }
3913                 
3914         $this->major_step=$aMajStep;
3915         $this->minor_step=$aMinStep;
3916         $this->is_set = true;
3917     }
3918
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;         
3923
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;
3928         $first=0;
3929                 
3930         // If precision hasn't been specified set it to a sensible value
3931         if( $this->precision==-1 ) { 
3932             $t = log10($this->minor_step);
3933             if( $t > 0 )
3934                 $precision = 0;
3935             else
3936                 $precision = -floor($t);
3937         }
3938         else
3939             $precision = $this->precision;
3940                         
3941         $img->SetLineWeight($this->weight);                     
3942                 
3943         // Handle ticks on X-axis
3944         if( $scale->type == "x" ) {
3945
3946             // Draw the minor tick marks
3947                         
3948             $yu = $pos - $this->direction*$this->GetMinTickAbsSize();
3949             $label = $scale->GetMinVal();           
3950             $x=$scale->scale_abs[0];
3951             $i=0;
3952             $j=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();
3962                 }
3963
3964                 if( $i % $step == 0 ) {
3965                     $this->maj_ticks_pos[$j]=round($x);//$xtick;                                
3966                     ++$j;
3967                 }
3968
3969                 ++$i;
3970                 $x += $min_step_abs;
3971                 
3972             }
3973             $this->maj_ticks_pos[$j]=$x;
3974
3975             // Draw the major tick marks
3976                         
3977             $yu = $pos - $this->direction*$this->GetMajTickAbsSize();
3978                         
3979             // TODO: Add logic to set label_offset for text labels
3980             $label = (float)$scale->GetMinVal()+$this->text_label_start+$this->label_offset;    
3981                         
3982             $start_abs=$scale->scale_factor*$this->text_label_start;
3983                         
3984             $nbrmajticks=ceil(($scale->GetMaxVal()-$scale->GetMinVal()-$this->text_label_start )/$this->major_step)+1;  
3985             
3986             
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 ) {
3989
3990                 // Apply format
3991                 if( $this->label_formfunc != "" ) {
3992                     $f=$this->label_formfunc;
3993                     $l = call_user_func($f,$label);
3994                 }       
3995                 elseif( $this->label_formatstr != '' ) 
3996                     $l = sprintf($this->label_formatstr,$label);
3997                 else {
3998                     $v = round($label,$precision);
3999                     $l = sprintf("%01.".$precision."f",$v);
4000                 }
4001                                         
4002                 if( ($this->supress_zerolabel && $l==0) || 
4003                     ($this->supress_first && $i==0) ||
4004                     ($this->supress_last  && $i==$nbrmajticks-1) ) {
4005                     $l="";
4006                 }
4007
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];
4012
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();
4023                 }
4024
4025                 $x += $maj_step_abs;
4026             }
4027
4028
4029         }
4030         elseif( $scale->type == "y" ) {
4031
4032             // Draw the major tick marks
4033             $xr = $pos + $this->direction*$this->GetMajTickAbsSize();
4034             $label = $scale->GetMinVal();
4035             
4036             $tmpmaj=array();
4037             $tmpmin=array();
4038
4039             for( $i=0; $i<$nbrmajticks; ++$i) {
4040                 $y=$scale->scale_abs[0]+$i*$maj_step_abs;                               
4041
4042                 $tmpmaj[]=$y;
4043
4044         
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;
4053                 
4054                 if( $this->label_formfunc != "" ) {
4055                     $f=$this->label_formfunc;
4056                     $l = call_user_func($f,$label);
4057                 }       
4058                 elseif( $this->label_formatstr != "" ) 
4059                     $l = sprintf($this->label_formatstr,$label);
4060                 else
4061                     $l = sprintf("%01.".$precision."f",round($label,$precision));
4062                                                                 
4063                 if( ($this->supress_zerolabel && ($l + 0)==0) ||  ($this->supress_first && $i==0) ||
4064                     ($this->supress_last  && $i==$nbrmajticks-1) ) {
4065                     $l="";
4066                 }
4067                 
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();
4074                 }
4075             }
4076
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; ) {
4081
4082                 $tmpmin[]=$y;
4083
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();
4091                 }
4092                 ++$i;
4093                 $y=$scale->scale_abs[0]+$i*$min_step_abs;
4094             }   
4095         }       
4096     }
4097 //---------------
4098 // PRIVATE METHODS
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;
4109         else
4110             $this->xtick_offset=$aTickOff;
4111         if( $aLabelOff>0 )
4112             $this->SupressLast();       // The last tick wont fit
4113     }
4114
4115     // Which tick label should we start with?
4116     function SetTextLabelStart($aTextLabelOff) {
4117         $this->text_label_start=$aTextLabelOff;
4118     }
4119         
4120 } // Class
4121
4122 //===================================================
4123 // CLASS LinearScale
4124 // Description: Handle linear scaling between screen and world 
4125 //===================================================
4126 class LinearScale {
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?
4147     var $name = 'lin';
4148 //---------------
4149 // CONSTRUCTOR
4150     function LinearScale($aMin=0,$aMax=0,$aType="y") {
4151         assert($aType=="x" || $aType=="y" );
4152         assert($aMin<=$aMax);
4153                 
4154         $this->type=$aType;
4155         $this->scale=array($aMin,$aMax);                
4156         $this->world_size=$aMax-$aMin;  
4157         $this->ticks = new LinearTicks();
4158     }
4159
4160 //---------------
4161 // PUBLIC METHODS       
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...)
4171                 
4172         // Add us as an observer to class Image
4173         $aImg->AddObserver("InitConstants",$this);
4174     }
4175         
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
4180             return false;
4181         }
4182         return true;
4183     }
4184         
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;
4190     }
4191
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;
4197     }
4198
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;
4203     }
4204
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;
4211     }
4212         
4213     // Get the minimum value in the scale
4214     function GetMinVal() {
4215         return $this->scale[0];
4216     }
4217         
4218     // get maximum value for scale
4219     function GetMaxVal() {
4220         return $this->scale[1];
4221     }
4222                 
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);                                    
4228     }
4229         
4230     // Translate between world and screen
4231     function Translate($aCoord) {
4232         return $this->off+($aCoord - $this->GetMinVal()) * $this->scale_factor; 
4233     }
4234         
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; 
4239     }
4240         
4241     // Restrict autoscaling to only use integers
4242     function SetIntScale($aIntScale=true) {
4243         $this->intscale=$aIntScale;
4244     }
4245         
4246     // Calculate an integer autoscale
4247     function IntAutoScale(&$img,$min,$max,$maxsteps,$majend=true) {
4248         // Make sure limits are integers
4249         $min=floor($min);
4250         $max=ceil($max);
4251         if( abs($min-$max)==0 ) {
4252             --$min; ++$max;
4253         }
4254         $maxsteps = floor($maxsteps);
4255                 
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.');
4262                 die();
4263             }
4264         }
4265
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.');
4270                 die();
4271             }
4272         }
4273
4274         if( abs($min-$max ) == 0 ) {
4275             ++$max;
4276             --$min;
4277         }
4278                         
4279         $min -= $gracebottom;
4280         $max += $gracetop;              
4281
4282         // First get tickmarks as multiples of 1, 10, ...       
4283         if( $majend ) {
4284             list($num1steps,$adj1min,$adj1max,$maj1step) = 
4285                 $this->IntCalcTicks($maxsteps,$min,$max,1);
4286         }
4287         else {
4288             $adj1min = $min;
4289             $adj1max = $max;
4290             list($num1steps,$maj1step) = 
4291                 $this->IntCalcTicksFreeze($maxsteps,$min,$max,1);
4292         }
4293
4294         if( abs($min-$max) > 2 ) {
4295             // Then get tick marks as 2:s 2, 20, ...
4296             if( $majend ) {
4297                 list($num2steps,$adj2min,$adj2max,$maj2step) = 
4298                     $this->IntCalcTicks($maxsteps,$min,$max,5);
4299             }
4300             else {
4301                 $adj2min = $min;
4302                 $adj2max = $max;
4303                 list($num2steps,$maj2step) = 
4304                     $this->IntCalcTicksFreeze($maxsteps,$min,$max,5);
4305             }
4306         }
4307         else {
4308             $num2steps = 10000; // Dummy high value so we don't choose this
4309         }
4310         
4311         if( abs($min-$max) > 5 ) {      
4312             // Then get tickmarks as 5:s 5, 50, 500, ...
4313             if( $majend ) {
4314                 list($num5steps,$adj5min,$adj5max,$maj5step) = 
4315                     $this->IntCalcTicks($maxsteps,$min,$max,2);
4316             }
4317             else {
4318                 $adj5min = $min;
4319                 $adj5max = $max;
4320                 list($num5steps,$maj5step) = 
4321                     $this->IntCalcTicksFreeze($maxsteps,$min,$max,2);
4322             }
4323         }
4324         else {
4325             $num5steps = 10000; // Dummy high value so we don't choose this             
4326         }
4327         
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);
4334         else
4335             $match5=10000;      // Dummy high value 
4336                 
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 )
4341                 $r=1;                   
4342             else 
4343                 $r=3;
4344         }
4345         else {
4346             if( $match2 < $match5 )
4347                 $r=2;                   
4348             else 
4349                 $r=3;           
4350         }       
4351         // Minsteps are always the same as maxsteps for integer scale
4352         switch( $r ) {
4353             case 1:
4354                 $this->Update($img,$adj1min,$adj1max);
4355                 $this->ticks->Set($maj1step,$maj1step);
4356                 break;                  
4357             case 2:
4358                 $this->Update($img,$adj2min,$adj2max);          
4359                 $this->ticks->Set($maj2step,$maj2step);
4360                 break;                                                                  
4361             case 3:
4362                 $this->Update($img,$adj5min,$adj5max);
4363                 $this->ticks->Set($maj5step,$maj2step);         
4364                 break;                  
4365         }               
4366     }
4367         
4368         
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);
4374             return;
4375         }
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
4380                 $min=-1; $max=1;
4381             }
4382             else {
4383                 $delta = (abs($max)+abs($min))*0.005;
4384                 $min -= $delta;
4385                 $max += $delta;
4386             }
4387         }
4388                 
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.');
4395                 die();
4396             }
4397             if( abs($min-$max ) < 0.00001 )
4398                 $max *= 1.2;
4399         }
4400
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.');
4405                 die();
4406             }
4407             if( abs($min-$max ) < 0.00001 )
4408                 $min *= 0.8;
4409         }
4410
4411         
4412         $min -= $gracebottom;
4413         $max += $gracetop;
4414
4415         // First get tickmarks as multiples of 0.1, 1, 10, ...  
4416         if( $majend ) {
4417             list($num1steps,$adj1min,$adj1max,$min1step,$maj1step) = 
4418                 $this->CalcTicks($maxsteps,$min,$max,1,2);
4419         }
4420         else {
4421             $adj1min=$min;
4422             $adj1max=$max;
4423             list($num1steps,$min1step,$maj1step) = 
4424                 $this->CalcTicksFreeze($maxsteps,$min,$max,1,2,false);
4425         }
4426                 
4427         // Then get tick marks as 2:s 0.2, 2, 20, ...
4428         if( $majend ) {
4429             list($num2steps,$adj2min,$adj2max,$min2step,$maj2step) = 
4430                 $this->CalcTicks($maxsteps,$min,$max,5,2);
4431         }
4432         else {
4433             $adj2min=$min;
4434             $adj2max=$max;
4435             list($num2steps,$min2step,$maj2step) = 
4436                 $this->CalcTicksFreeze($maxsteps,$min,$max,5,2,false);
4437         }
4438                 
4439         // Then get tickmarks as 5:s 0.05, 0.5, 5, 50, ...
4440         if( $majend ) {
4441             list($num5steps,$adj5min,$adj5max,$min5step,$maj5step) = 
4442                 $this->CalcTicks($maxsteps,$min,$max,2,5);              
4443         }
4444         else {
4445             $adj5min=$min;
4446             $adj5max=$max;
4447             list($num5steps,$min5step,$maj5step) = 
4448                 $this->CalcTicksFreeze($maxsteps,$min,$max,2,5,false);
4449         }
4450
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);
4459         switch( $r ) {
4460             case 1:
4461                 $this->Update($img,$adj1min,$adj1max);
4462                 $this->ticks->Set($maj1step,$min1step);
4463                 break;                  
4464             case 2:
4465                 $this->Update($img,$adj2min,$adj2max);          
4466                 $this->ticks->Set($maj2step,$min2step);
4467                 break;                                                                  
4468             case 3:
4469                 $this->Update($img,$adj5min,$adj5max);
4470                 $this->ticks->Set($maj5step,$min5step);         
4471                 break;                  
4472         }
4473     }
4474
4475 //---------------
4476 // PRIVATE METHODS      
4477
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);
4489         }
4490         else { // y scale
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);     
4496         }
4497         $size = $this->world_size * $this->scale_factor;
4498         $this->scale_abs=array($this->off,$this->off + $size);  
4499     }
4500         
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;
4509         $this->off=$aStart;
4510                 
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.");
4521         }
4522                 
4523         // scale_factor = number of pixels per world unit
4524         $this->scale_factor=$this->world_abs_size/($this->world_size*1.0);
4525                 
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);            
4528     }
4529         
4530         
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) {
4541         $diff=$max-$min; 
4542         if( $diff==0 )
4543             $ld=0;
4544         else
4545             $ld=floor(log10($diff));
4546
4547         // Gravitate min towards zero if we are close           
4548         if( $min>0 && $min < pow(10,$ld) ) $min=0;
4549                 
4550         //$majstep=pow(10,$ld-1)/$a; 
4551         $majstep=pow(10,$ld)/$a; 
4552         $minstep=$majstep/$b;
4553         
4554         $adjmax=ceil($max/$minstep)*$minstep;
4555         $adjmin=floor($min/$minstep)*$minstep;  
4556         $adjdiff = $adjmax-$adjmin;
4557         $numsteps=$adjdiff/$majstep; 
4558         
4559         while( $numsteps>$maxsteps ) {
4560             $majstep=pow(10,$ld)/$a; 
4561             $numsteps=$adjdiff/$majstep;
4562             ++$ld;
4563         }
4564
4565         $minstep=$majstep/$b;
4566         $adjmin=floor($min/$minstep)*$minstep;  
4567         $adjdiff = $adjmax-$adjmin;             
4568         if( $majend ) {
4569             $adjmin = floor($min/$majstep)*$majstep;    
4570             $adjdiff = $adjmax-$adjmin;         
4571             $adjmax = ceil($adjdiff/$majstep)*$majstep+$adjmin;
4572         }
4573         else
4574             $adjmax=ceil($max/$minstep)*$minstep;
4575
4576         return array($numsteps,$adjmin,$adjmax,$minstep,$majstep);
4577     }
4578
4579     function CalcTicksFreeze($maxsteps,$min,$max,$a,$b) {
4580         // Same as CalcTicks but don't adjust min/max values
4581         $diff=$max-$min; 
4582         if( $diff==0 )
4583             $ld=0;
4584         else
4585             $ld=floor(log10($diff));
4586
4587         //$majstep=pow(10,$ld-1)/$a; 
4588         $majstep=pow(10,$ld)/$a; 
4589         $minstep=$majstep/$b;
4590         $numsteps=floor($diff/$majstep); 
4591         
4592         while( $numsteps > $maxsteps ) {
4593             $majstep=pow(10,$ld)/$a; 
4594             $numsteps=floor($diff/$majstep);
4595             ++$ld;
4596         }
4597         $minstep=$majstep/$b;
4598         return array($numsteps,$minstep,$majstep);
4599     }
4600
4601         
4602     function IntCalcTicks($maxsteps,$min,$max,$a,$majend=true) {
4603         $diff=$max-$min; 
4604         if( $diff==0 )
4605             JpGraphError::Raise('Can\'t automatically determine ticks since min==max.');
4606         else
4607             $ld=floor(log10($diff));
4608                 
4609         // Gravitate min towards zero if we are close           
4610         if( $min>0 && $min < pow(10,$ld) ) $min=0;
4611                 
4612         if( $ld == 0 ) $ld=1;
4613         
4614         if( $a == 1 ) 
4615             $majstep = 1;
4616         else
4617             $majstep=pow(10,$ld)/$a; 
4618         $adjmax=ceil($max/$majstep)*$majstep;
4619
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;
4626             ++$ld;
4627         }
4628                 
4629         $adjmin=floor($min/$majstep)*$majstep;  
4630         $adjdiff = $adjmax-$adjmin;             
4631         if( $majend ) {
4632             $adjmin = floor($min/$majstep)*$majstep;    
4633             $adjdiff = $adjmax-$adjmin;         
4634             $adjmax = ceil($adjdiff/$majstep)*$majstep+$adjmin;
4635         }
4636         else
4637             $adjmax=ceil($max/$majstep)*$majstep;
4638                         
4639         return array($numsteps,$adjmin,$adjmax,$majstep);               
4640     }
4641
4642
4643     function IntCalcTicksFreeze($maxsteps,$min,$max,$a) {
4644         // Same as IntCalcTick but don't change min/max values
4645         $diff=$max-$min; 
4646         if( $diff==0 )
4647             JpGraphError::Raise('Can\'t automatically determine ticks since min==max.');
4648         else
4649             $ld=floor(log10($diff));
4650                 
4651         if( $ld == 0 ) $ld=1;
4652         
4653         if( $a == 1 ) 
4654             $majstep = 1;
4655         else
4656             $majstep=pow(10,$ld)/$a; 
4657
4658         $numsteps=floor($diff/$majstep); 
4659         while( $numsteps > $maxsteps ) {
4660             $majstep=pow(10,$ld)/$a; 
4661             $numsteps=floor($diff/$majstep);
4662             ++$ld;
4663         }
4664                                         
4665         return array($numsteps,$majstep);               
4666     }
4667
4668
4669         
4670     // Determine the minimum of three values witha  weight for last value
4671     function MatchMin3($a,$b,$c,$weight) {
4672         if( $a < $b ) {
4673             if( $a < ($c*$weight) ) 
4674                 return 1; // $a smallest
4675             else 
4676                 return 3; // $c smallest
4677         }
4678         elseif( $b < ($c*$weight) ) 
4679             return 2; // $b smallest
4680         return 3; // $c smallest
4681     }
4682 } // Class
4683
4684 //===================================================
4685 // CLASS RGB
4686 // Description: Color definitions as RGB triples
4687 //===================================================
4688 class RGB {
4689     var $rgb_table;
4690     var $img;
4691     function RGB($aImg=null) {
4692         $this->img = $aImg;
4693                 
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));          
5132     }
5133 //----------------
5134 // PUBLIC METHODS
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 
5140     // RGB triple.
5141     function Color($aColor) {
5142         if (is_string($aColor)) {
5143             // Strip of any alpha factor
5144             $pos = strpos($aColor,'@');
5145             if( $pos === false ) {
5146                 $alpha = 0;
5147             }
5148             else {
5149                 $pos2 = strpos($aColor,':');
5150                 if( $pos2===false ) 
5151                     $pos2 = $pos-1; // Sentinel
5152                 if( $pos > $pos2 ) {
5153                     $alpha = substr($aColor,$pos+1);
5154                     $aColor = substr($aColor,0,$pos);
5155                 }
5156                 else {
5157                     $alpha = substr($aColor,$pos+1,$pos2-$pos-1);
5158                     $aColor = substr($aColor,0,$pos).substr($aColor,$pos2);
5159                 }
5160             }
5161
5162             // Extract potential adjustment figure at end of color
5163             // specification
5164             $pos = strpos($aColor,":");
5165             if( $pos === false ) {
5166                 $adj = 1.0;
5167             }
5168             else {
5169                 $adj = 0.0 + substr($aColor,$pos+1);
5170                 $aColor = substr($aColor,0,$pos);
5171             }
5172             if( $adj < 0 )
5173                 JpGraphError::Raise('Adjustment factor for color must be > 0');
5174
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));
5179             } else {
5180                 if(!isset($this->rgb_table[$aColor]) )
5181                     JpGraphError::Raise(" Unknown color: <strong>$aColor</strong>");
5182                 $tmp=$this->rgb_table[$aColor];
5183                 $r = $tmp[0];
5184                 $g = $tmp[1];
5185                 $b = $tmp[2];
5186             }
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.
5190             if( $adj > 1 ) {
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);
5193             }
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);
5197             }
5198             else {
5199                 return array($r,$g,$b,$alpha);
5200             }
5201
5202         } elseif( is_array($aColor) ) {
5203             if( count($aColor)==3 ) {
5204                 $aColor[3]=0;
5205                 return $aColor;
5206             }
5207             else
5208                 return $aColor;
5209         }
5210         else
5211             JpGraphError::Raise(" Unknown color specification: $aColor , size=".count($aColor));
5212     }
5213         
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] )
5220             return true;
5221         else
5222             return false;
5223     }
5224         
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
5231         if( $a > 0 )
5232             $aAlpha = $a;
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');
5236                 exit(1);
5237             }
5238             return imagecolorresolvealpha($this->img, $r, $g, $b, round($aAlpha * 127));
5239         } else {
5240             $index = imagecolorexact($this->img, $r, $g, $b);
5241             if ($index == -1) {
5242                 $index = imagecolorallocate($this->img, $r, $g, $b);
5243                 if( USE_APPROX_COLORS && $index == -1 )
5244                     $index = imagecolorresolve($this->img, $r, $g, $b);
5245             } 
5246             return $index;
5247         }
5248     }
5249 } // Class
5250
5251         
5252 //===================================================
5253 // CLASS Image
5254 // Description: Wrapper class with some goodies to form the
5255 // Interface to low level image drawing routines.
5256 //===================================================
5257 class Image {
5258     var $img_format;
5259     var $expired=true;
5260     var $img;
5261     var $left_margin=30,$right_margin=30,$top_margin=20,$bottom_margin=30;
5262     var $plotwidth=0,$plotheight=0;
5263     var $rgb=null;
5264     var $current_color,$current_color_name;
5265     var $lastx=0, $lasty=0;
5266     var $width, $height;
5267     var $line_weight=1;
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;
5271     var $font_file='';
5272     var $text_halign="left",$text_valign="bottom";
5273     var $ttf=null;
5274     var $use_anti_aliasing=false;
5275     var $quality=null;
5276     var $colorstack=array(),$colorstackidx=0;
5277     var $canvascolor = 'white' ;
5278     var $langconv = null ;
5279
5280     //---------------
5281     // CONSTRUCTOR
5282     function Image($aWidth,$aHeight,$aFormat=DEFAULT_GFORMAT) {
5283         $this->CreateImgCanvas($aWidth,$aHeight);
5284         $this->SetAutoMargin();         
5285
5286         if( !$this->SetImgFormat($aFormat) ) {
5287             JpGraphError::Raise("JpGraph: Selected graphic format is either not supported or unknown [$aFormat]");
5288         }
5289         $this->ttf = new TTF();
5290         $this->langconv = new LanguageConv();
5291     }
5292
5293     // Should we use anti-aliasing. Note: This really slows down graphics!
5294     function SetAntiAliasing() {
5295         $this->use_anti_aliasing=true;
5296     }
5297
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.");
5303             }
5304             $this->SetAlphaBlending();
5305         } else {
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.");
5309             }
5310         }
5311         if( $this->rgb != null ) 
5312             $this->rgb->img = $this->img ;
5313         else
5314             $this->rgb = new RGB($this->img);                           
5315     }
5316
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);
5321         return $oldimage;
5322     }
5323     
5324     function CreateImgCanvas($aWidth=0,$aHeight=0) {
5325
5326         $old = array($this->img,$this->width,$this->height);
5327         
5328         $aWidth = round($aWidth);
5329         $aHeight = round($aHeight);
5330
5331         $this->width=$aWidth;
5332         $this->height=$aHeight;         
5333
5334         
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.
5339             $this->img = null;
5340             $this->rgb = null;
5341             return $old;
5342         }
5343         
5344         $this->CreateRawCanvas($aWidth,$aHeight);
5345                 
5346         // Set canvas color (will also be the background color for a 
5347         // a pallett image
5348         $this->SetColor($this->canvascolor);    
5349         $this->FilledRectangle(0,0,$aWidth,$aHeight);
5350
5351         return $old ;
5352     }
5353         
5354
5355     function CopyCanvasH($aToHdl,$aFromHdl,$aToX,$aToY,$aFromX,$aFromY,$aWidth,$aHeight,$aw=-1,$ah=-1) {
5356         if( $aw === -1 ) {
5357             $aw = $aWidth;
5358             $ah = $aHeight;
5359             $f = 'imagecopyresized';
5360         }
5361         else {
5362             $f = $GLOBALS['copyfunc'] ;
5363         }
5364         $f($aToHdl,$aFromHdl,
5365            $aToX,$aToY,$aFromX,$aFromY, $aWidth,$aHeight,$aw,$ah);
5366     }
5367
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);
5372     }
5373
5374     function GetWidth($aImg=null) {
5375         if( $aImg === null ) 
5376             $aImg = $this->img;
5377         return imagesx($aImg);
5378     }
5379
5380     function GetHeight($aImg=null) {
5381         if( $aImg === null ) 
5382             $aImg = $this->img;
5383         return imagesy($aImg);
5384     }
5385     
5386     function CreateFromString($aStr) {
5387         return imagecreatefromstring($aStr);
5388     }
5389
5390     function SetCanvasH($aHdl) {
5391         $this->img = $aHdl;
5392         $this->rgb->img = $aHdl;
5393     }
5394
5395     function SetCanvasColor($aColor) {
5396         $this->canvascolor = $aColor ;
5397     }
5398
5399     function SetAlphaBlending($aFlg=true) {
5400         if( $GLOBALS['gd2'] )
5401             ImageAlphaBlending($this->img,$aFlg);
5402         else 
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.');
5404     }
5405
5406         
5407     function SetAutoMargin() {  
5408         GLOBAL $gJpgBrandTiming;
5409         $min_bm=0;
5410         /*
5411         if( $gJpgBrandTiming )
5412             $min_bm=15;         
5413         */
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);              
5419     }
5420
5421                                 
5422     //---------------
5423     // PUBLIC METHODS   
5424
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);
5429     }
5430         
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);
5439         }
5440     }   
5441         
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.");
5445         
5446         
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;
5453         }
5454         if( $this->font_family > FF_FONT2+1 ) { // A TTF font so get the font file
5455
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.');
5459                 exit();
5460             }
5461             $this->font_file = $this->ttf->File($this->font_family,$this->font_style);
5462         }
5463     }
5464
5465     // Get the specific height for a text string
5466     function GetTextHeight($txt="",$angle=0) {
5467         $tmp = split("\n",$txt);
5468         $n = count($tmp);
5469         $m=0;
5470         for($i=0; $i< $n; ++$i)
5471             $m = max($m,strlen($tmp[$i]));
5472
5473         if( $this->font_family <= FF_FONT2+1 ) {
5474             if( $angle==0 )
5475                 return $n*imagefontheight($this->font_family);
5476             else 
5477                 return $m*imagefontwidth($this->font_family);
5478         }
5479         else {
5480             $bbox = $this->GetTTFBBox($txt,$angle);
5481             return $bbox[1]-$bbox[5];
5482         }
5483     }
5484         
5485     // Estimate font height
5486     function GetFontHeight($angle=0) {
5487         $txt = "XOMg";
5488         return $this->GetTextHeight($txt,$angle);
5489     }
5490         
5491     // Approximate font width with width of letter "O"
5492     function GetFontWidth($angle=0) {
5493         $txt = 'O';
5494         return $this->GetTextWidth($txt,$angle);
5495     }
5496         
5497     // Get actual width of text in absolute pixels
5498     function GetTextWidth($txt,$angle=0) {
5499
5500         $tmp = split("\n",$txt);
5501         $n = count($tmp);
5502         if( $this->font_family <= FF_FONT2+1 ) {
5503
5504             $m=0;
5505             for($i=0; $i < $n; ++$i) {
5506                 $l=strlen($tmp[$i]);
5507                 if( $l > $m ) {
5508                     $m = $l;
5509                 }
5510             }
5511
5512             if( $angle==0 ) {
5513                 $width=$m*imagefontwidth($this->font_family);
5514                 return $width;
5515             }
5516             else {
5517                 // 90 degrees internal so height becomes width
5518                 return $n*imagefontheight($this->font_family); 
5519             }
5520         }
5521         else {
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
5524             // paragraph
5525             $m=0;
5526             for( $i=0; $i < $n; ++$i ) {
5527                 $bbox = $this->GetTTFBBox($tmp[$i],$angle);
5528                 $mm =  $bbox[2] - $bbox[0];
5529                 if( $mm > $m ) 
5530                     $m = $mm;
5531             }
5532             return $m;
5533         }
5534     }
5535         
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) {
5540
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]");
5545         }
5546                 
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) ;
5550         }
5551         else {
5552             $width=$this->GetBBoxWidth($txt,$dir) ;
5553             $height=$this->GetBBoxHeight($txt,$dir) ;
5554         }
5555         
5556         $height += 2*$ymarg;
5557         $width  += 2*$xmarg;
5558
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;
5563         
5564         if( $shadowcolor ) {
5565             $this->PushColor($shadowcolor);
5566             $this->FilledRoundedRectangle($x-$xmarg+$dropwidth,$y-$ymarg+$dropwidth,
5567                                           $x+$width+$dropwidth,$y+$height+$dropwidth,
5568                                           $cornerradius);
5569             $this->PopColor();
5570             $this->PushColor($fcolor);
5571             $this->FilledRoundedRectangle($x-$xmarg,$y-$ymarg,$x+$width,$y+$height,$cornerradius);              
5572             $this->PopColor();
5573             $this->PushColor($bcolor);
5574             $this->RoundedRectangle($x-$xmarg,$y-$ymarg,$x+$width,$y+$height,$cornerradius);
5575             $this->PopColor();
5576         }
5577         else {
5578             if( $fcolor ) {
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;
5583             }
5584             if( $bcolor ) {
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;                       
5589             }
5590         }
5591                 
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);
5597     }
5598
5599     // Set text alignment       
5600     function SetTextAlign($halign,$valign="bottom") {
5601         $this->text_halign=$halign;
5602         $this->text_valign=$valign;
5603     }
5604         
5605
5606     function _StrokeBuiltinFont($x,$y,$txt,$dir=0,$paragraph_align="left") {
5607
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.");
5610         
5611         $h=$this->GetTextHeight($txt);
5612         $fh=$this->GetFontHeight();
5613         $w=$this->GetTextWidth($txt);
5614         
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;
5621         }
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;
5626         
5627         if( $dir==90 )
5628             imagestringup($this->img,$this->font_family,$x,$y,$txt,$this->current_color);
5629         else {
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);
5636                     }
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);
5640                     }
5641                     else {
5642                         imagestring($this->img,$this->font_family,$x+$w/2-$w1/2,
5643                                     $y-$h+1+$i*$fh,$tmp[$i],$this->current_color);
5644                     }
5645                 }
5646             } 
5647             else {
5648                 //Put the text
5649                 imagestring($this->img,$this->font_family,$x,$y-$h+1,$txt,$this->current_color);
5650             }
5651         }
5652     }
5653
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);
5660         $n = count($e);
5661         for($i=0; $i<$n; ++$i) {
5662             $e[$i]=str_replace("\r","",$e[$i]);
5663         }
5664         return implode("\n\r",$e);
5665     }
5666
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.");
5671         }
5672         return $bbox;
5673     }
5674
5675     function GetBBoxTTF($aTxt,$aAngle=0) {
5676         // Normalize the bounding box to become a minimum
5677         // enscribing rectangle
5678
5679         $aTxt = $this->AddTxtCR($aTxt);
5680
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.');
5683         }
5684         $bbox = $this->GetTTFBBox($aTxt,$aAngle);
5685
5686         if( $aAngle==0 ) 
5687             return $bbox;
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]);
5692             }
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]);
5696             }
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]);
5700             }
5701             else {
5702                 $bbox = array($bbox[0],$bbox[3],$bbox[4],$bbox[3],
5703                               $bbox[4],$bbox[7],$bbox[0],$bbox[7]);
5704             }
5705         }
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]);
5710             }
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]);
5714             }
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]);
5718             }
5719             else {
5720                 $bbox = array($bbox[0],$bbox[3],$bbox[4],$bbox[3],
5721                               $bbox[4],$bbox[7],$bbox[0],$bbox[7]);
5722             }
5723         }       
5724         return $bbox;
5725     }
5726
5727     function GetBBoxHeight($aTxt,$aAngle=0) {
5728         $box = $this->GetBBoxTTF($aTxt,$aAngle);
5729         return $box[1]-$box[7]+1;
5730     }
5731
5732     function GetBBoxWidth($aTxt,$aAngle=0) {
5733         $box = $this->GetBBoxTTF($aTxt,$aAngle);
5734         return $box[2]-$box[0]+1;       
5735     }
5736
5737     function _StrokeTTF($x,$y,$txt,$dir=0,$paragraph_align="left",$debug=false) {
5738
5739         // Remember the anchor point before adjustment
5740         if( $debug ) {
5741             $ox=$x;
5742             $oy=$y;
5743         }
5744
5745         if( !ereg("\n",$txt) || ($dir>0 && ereg("\n",$txt)) ) {
5746             // Format a single line
5747
5748             $txt = $this->AddTxtCR($txt);
5749
5750             $bbox=$this->GetBBoxTTF($txt,$dir);
5751             
5752             // Align x,y ot lower left corner of bbox
5753             $x -= $bbox[0];
5754             $y -= $bbox[1];
5755
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; 
5761             
5762             if( $this->text_valign=="top" ) $y += abs($bbox[5])+$bbox[1];
5763             elseif( $this->text_valign=="center" ) $y -= ($bbox[5]-$bbox[1])/2; 
5764
5765             ImageTTFText ($this->img, $this->font_size, $dir, $x, $y, 
5766                           $this->current_color,$this->font_file,$txt); 
5767
5768             if( $debug ) {
5769                 // Draw the bounding rectangle and the bounding box
5770                 $box=ImageTTFBBox($this->font_size,$dir,$this->font_file,$txt);
5771                 $p = array();
5772                 $p1 = array();
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;
5778                 }
5779
5780                 // Draw bounding box
5781                 $this->PushColor('green');
5782                 $this->Polygon($p1,true);
5783                 $this->PopColor();
5784                 
5785                 // Draw bounding rectangle
5786                 $this->PushColor('darkgreen');
5787                 $this->Polygon($p,true);
5788                 $this->PopColor();
5789                 
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);
5794                 $this->PopColor();
5795             }
5796         }
5797         else {
5798             // Format a text paragraph
5799             $fh=$this->GetFontHeight();
5800
5801             // Line margin is 15% of font height
5802             $linemargin=round($fh*0.15);
5803             $fh += $linemargin;
5804             $w=$this->GetTextWidth($txt);
5805
5806             $y -= $linemargin/2;
5807             $tmp = split("\n",$txt);
5808             $nl = count($tmp);
5809             $h = $nl * $fh;
5810
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;
5815             }
5816             
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;
5821
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" ) {
5838                     $xl = $x; 
5839                 }
5840                 elseif( $paragraph_align=="right" ) {
5841                     $xl = $x + ($w-$wl);
5842                 }
5843                 else {
5844                     // Center
5845                     $xl = $x + $w/2 - $wl/2 ;
5846                 }
5847
5848                 $xl -= $bbox[0];
5849                 $yl = $y  - $yadj; 
5850                 $xl = $xl  - $xadj; 
5851                 ImageTTFText ($this->img, $this->font_size, $dir, 
5852                               $xl, $yl-($h-$fh)+$fh*$i,
5853                               $this->current_color,$this->font_file,$tmp[$i]); 
5854
5855
5856                 if( $debug  ) {
5857                     // Draw the bounding rectangle around each line
5858                     $box=ImageTTFBBox($this->font_size,$dir,$this->font_file,$tmp[$i]);
5859                     $p = array();
5860                     for($j=0; $j < 4; ++$j) {
5861                         $p[] = $bbox[$j*2]+$xl;
5862                         $p[] = $bbox[$j*2+1]+$yl-($h-$fh)+$fh*$i;
5863                     }
5864                     
5865                     // Draw bounding rectangle
5866                     $this->PushColor('darkgreen');
5867                     $this->Polygon($p,true);
5868                     $this->PopColor();
5869                 }
5870             }
5871
5872             if( $debug ) {
5873                 
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);
5878                 $this->PopColor();
5879             }
5880
5881         }
5882     }
5883         
5884     function StrokeText($x,$y,$txt,$dir=0,$paragraph_align="left",$debug=false) {
5885
5886         $x = round($x);
5887         $y = round($y);
5888
5889         // Do special language encoding
5890         $txt = $this->langconv->Convert($txt,$this->font_family);
5891
5892         if( !is_numeric($dir) )
5893             JpGraphError::Raise(" Direction for text most be given as an angle between 0 and 90.");
5894                         
5895         if( $this->font_family >= FF_FONT0 && $this->font_family <= FF_FONT2+1) {       
5896             $this->_StrokeBuiltinFont($x,$y,$txt,$dir,$paragraph_align,$debug);
5897         }
5898         elseif($this->font_family >= FF_COURIER && $this->font_family <= FF_BOOK)  { 
5899             $this->_StrokeTTF($x,$y,$txt,$dir,$paragraph_align,$debug);
5900         }
5901         else
5902             JpGraphError::Raise(" Unknown font font family specification. ");
5903     }
5904         
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();
5915     }
5916
5917     function SetTransparent($color) {
5918         imagecolortransparent ($this->img,$this->rgb->allocate($color));
5919     }
5920         
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
5931                                 anti-aliasing.<p>
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 
5937 <pre>
5938 DEFINE(\"USE_APPROX_COLORS\",true);
5939 </pre>
5940                                 in jpgraph.php This will use approximative colors
5941                                 when the palette is full.
5942                                 <p>
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."); 
5946         }
5947         return $this->current_color;
5948     }
5949         
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);
5956         }
5957         else {
5958             JpGraphError::Raise("Color specified as empty string in PushColor().");
5959         }
5960     }
5961         
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];
5967     }
5968         
5969         
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);        }       
5973         
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]);
5983         }
5984     }
5985         
5986     function AdjBrightContrast($bright,$contr=0) {
5987         $this->_AdjBrightContrast($this->img,$bright,$contr);
5988     }
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);
5999         }
6000     }
6001         
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.
6006     // 
6007     // Tip: To get a grayscale picture set sat=-100, values <-100 changes the colors
6008     // to it's complement.
6009     // 
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
6015     // RGB axis.
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
6020         // Grey vector
6021         $v=array(1,1,1);
6022
6023         // Dot product
6024         $dot = $rgb[0]*$v[0]+$rgb[1]*$v[1]+$rgb[2]*$v[2];
6025
6026         // Normalize dot product
6027         $normdot = $dot/3;      // dot/|v|^2
6028
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];
6032
6033         // Adjustment factor so that sat==1 sets the highest RGB value to 255
6034         if( $sat > 0 ) {
6035             $m=0;
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]);
6039             }
6040             $tadj=$m;
6041         }
6042         else
6043             $tadj=1;
6044                 
6045         $tadj = $tadj*$sat;     
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
6050         }               
6051         return $un;     
6052     }   
6053
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
6058         if( $contr <= 0 ) {
6059             // Decrease contrast
6060             $adj = abs($rgb-128) * (-$contr);
6061             if( $rgb < 128 ) $rgb += $adj;
6062             else $rgb -= $adj;
6063         }
6064         else { // $contr > 0
6065             // Increase contrast
6066             if( $rgb < 128 ) $rgb = $rgb - ($rgb * $contr);
6067             else $rgb = $rgb + ((255-$rgb) * $contr);
6068         }
6069         
6070         // Add (or remove) various amount of white
6071         $rgb += $bright*255;    
6072         $rgb=min($rgb,255);
6073         $rgb=max($rgb,0);
6074         return $rgb;    
6075     }
6076         
6077     function SetLineWeight($weight) {
6078         $this->line_weight = $weight;
6079     }
6080         
6081     function SetStartPoint($x,$y) {
6082         $this->lastx=round($x);
6083         $this->lasty=round($y);
6084     }
6085         
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;
6090         
6091         imagearc($this->img,round($cx),round($cy),round($w),round($h),
6092                  $s,$e,$this->current_color);
6093     }
6094     
6095     function FilledArc($xc,$yc,$w,$h,$s,$e,$style="") {
6096
6097         if( $GLOBALS['gd2'] ) {
6098             while( $s < 0 ) $s += 360;
6099             while( $e < 0 ) $e += 360;
6100             if( $style=="" ) 
6101                 $style=IMG_ARC_PIE;
6102             imagefilledarc($this->img,round($xc),round($yc),round($w),round($h),
6103                            round($s),round($e),$this->current_color,$style);
6104             return;
6105         }
6106
6107
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;
6113
6114         $w /= 2; // We use radius in our calculations instead
6115         $h /= 2;
6116
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....
6119
6120         $s = 360-$s;
6121         $e = 360-$e;
6122
6123         if( $e > $s ) {
6124             $e = $e - 360;
6125             $da = $s - $e; 
6126         }
6127         $da = $s-$e;
6128
6129         // We use radians
6130         $s *= M_PI/180;
6131         $e *= M_PI/180;
6132         $da *= M_PI/180;
6133
6134         // Calculate a polygon approximation
6135         $p[0] = $xc;
6136         $p[1] = $yc;
6137
6138         // Heuristic on how many polygons we need to make the
6139         // arc look good
6140         $numsteps = round(8 * abs($da) * ($w+$h)*($w+$h)/1500);
6141
6142         if( $numsteps == 0 ) return;
6143         if( $numsteps < 7 ) $numsteps=7;
6144         $delta = abs($da)/$numsteps;
6145         
6146         $pa=array();
6147         $a = $s;
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; 
6152             $a -= $delta; 
6153             $pa[2*($i-1)] = $p[2*$i];
6154             $pa[2*($i-1)+1] = $p[2*$i+1];
6155         }
6156
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];
6163         $i++;
6164
6165         $p[2*$i] = $xc;
6166         $p[2*$i+1] = $yc;
6167         if( $fillcolor != "" ) {
6168             $this->PushColor($fillcolor);
6169             imagefilledpolygon($this->img,$p,count($p)/2,$this->current_color);
6170             $this->PopColor();
6171         }
6172     }
6173
6174     function FilledCakeSlice($cx,$cy,$w,$h,$s,$e) {
6175         $this->CakeSlice($cx,$cy,$w,$h,$s,$e,$this->current_color_name);
6176     }
6177
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);
6184         $this->PopColor();
6185         if( $arccolor != "" ) {
6186             $this->PushColor($arccolor);
6187             // We add 2 pixels to make the Arc() better aligned with
6188             // the filled arc. 
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 ) ;
6192             }
6193             else {
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);
6201             }
6202             $this->PopColor();
6203         }
6204     }
6205
6206     function Ellipse($xc,$yc,$w,$h) {
6207         $this->Arc($xc,$yc,$w,$h,0,360);
6208     }
6209         
6210     // Breseham circle gives visually better result then using GD
6211     // built in arc(). It takes some more time but gives better
6212     // accuracy.
6213     function BresenhamCircle($xc,$yc,$r) {
6214         $d = 3-2*$r;
6215         $x = 0;
6216         $y = $r;
6217         while($x<=$y) {
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);
6222                         
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);
6227                         
6228             if( $d<0 ) $d += 4*$x+6;
6229             else {
6230                 $d += 4*($x-$y)+10;             
6231                 --$y;
6232             }
6233             ++$x;
6234         }
6235     }
6236                         
6237     function Circle($xc,$yc,$r) {
6238         if( USE_BRESENHAM )
6239             $this->BresenhamCircle($xc,$yc,$r);
6240         else {
6241
6242             /*
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
6246             $end = 2*M_PI;
6247             $l = $r/10;
6248             if( $l < 3 ) $l=3;
6249             $step_size = 2*M_PI/(2*$r*M_PI/$l);
6250             $pts = array();
6251             $pts[] = $r + $xc;
6252             $pts[] = $yc;
6253             for( $a=$step_size; $a <= $end; $a += $step_size ) {
6254                 $pts[] = round($xc + $r*cos($a));
6255                 $pts[] = round($yc - $r*sin($a));
6256             }
6257             imagepolygon($this->img,$pts,count($pts)/2,$this->current_color);
6258             */
6259
6260             $this->Arc($xc,$yc,$r*2,$r*2,0,360);                
6261
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);
6264         }
6265     }
6266         
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);
6271         }
6272         else {
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);
6279             }
6280         }       
6281     }
6282         
6283     // Linear Color InterPolation
6284     function lip($f,$t,$p) {
6285         $p = round($p,1);
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);
6290     }
6291
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"]);
6298
6299         $dx = $x2-$x1;
6300         $dy = $y2-$y1;
6301         
6302         if( abs($dx) > abs($dy) ) {
6303             if( $dx<0 ) {
6304                 $dx = -$dx;$dy = -$dy;
6305                 $tmp=$x2;$x2=$x1;$x1=$tmp;
6306                 $tmp=$y2;$y2=$y1;$y1=$tmp;
6307             }
6308             $x=$x1<<16; $y=$y1<<16;
6309             $yinc = ($dy*65535)/$dx;
6310             while( ($x >> 16) < $x2 ) {
6311                                 
6312                 $bc = @imagecolorsforindex($this->img,imagecolorat($this->img,$x>>16,$y>>16));
6313                 if( $bc <= 0 ) {
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.');
6315                 }
6316                 $bc=array($bc["red"],$bc["green"],$bc["blue"]);
6317                                 
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;
6323             }
6324         }
6325         else {
6326             if( $dy<0 ) {
6327                 $dx = -$dx;$dy = -$dy;
6328                 $tmp=$x2;$x2=$x1;$x1=$tmp;
6329                 $tmp=$y2;$y2=$y1;$y1=$tmp;
6330             }
6331             $x=$x1<<16; $y=$y1<<16;
6332             $xinc = ($dx*65535)/$dy;    
6333             while( ($y >> 16) < $y2 ) {
6334                                 
6335                 $bc = @imagecolorsforindex($this->img,imagecolorat($this->img,$x>>16,$y>>16));
6336                 if( $bc <= 0 ) {
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.');
6338
6339                 }
6340
6341                 $bc=array($bc["red"],$bc["green"],$bc["blue"]);                         
6342                                 
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;
6348             }
6349         }
6350         $this->SetColor($lc);
6351         imagesetpixel($this->img,$x2,$y2,$this->current_color);         
6352         imagesetpixel($this->img,$x1,$y1,$this->current_color);                 
6353     }
6354
6355     // Set line style dashed, dotted etc
6356     function SetLineStyle($s) {
6357         if( is_numeric($s) ) {
6358             if( $s<1 || $s>4 ) 
6359                 JpGraphError::Raise(" Illegal numeric argument to SetLineStyle(): ($s)");
6360         }
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");
6367         }
6368         else JpGraphError::Raise(" Illegal argument to SetLineStyle $s");
6369         $this->line_style=$s;
6370     }
6371         
6372     // Same as Line but take the line_style into account
6373     function StyleLine($x1,$y1,$x2,$y2) {
6374         switch( $this->line_style ) {
6375             case 1:// Solid
6376                 $this->Line($x1,$y1,$x2,$y2);
6377                 break;
6378             case 2: // Dotted
6379                 $this->DashedLine($x1,$y1,$x2,$y2,1,6);
6380                 break;
6381             case 3: // Dashed
6382                 $this->DashedLine($x1,$y1,$x2,$y2,2,4);
6383                 break;
6384             case 4: // Longdashes
6385                 $this->DashedLine($x1,$y1,$x2,$y2,8,6);
6386                 break;
6387             default:
6388                 JpGraphError::Raise(" Unknown line style: $this->line_style ");
6389                 break;
6390         }
6391     }
6392
6393     function Line($x1,$y1,$x2,$y2) {
6394
6395         $x1 = round($x1);
6396         $x2 = round($x2);
6397         $y1 = round($y1);
6398         $y2 = round($y2);
6399
6400         if( $this->line_weight==0 ) return;
6401         if( $this->use_anti_aliasing ) {
6402             $dx = $x2-$x1;
6403             $dy = $y2-$y1;
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);
6407                 return;
6408             }
6409         }
6410         if( $this->line_weight==1 ) {
6411             imageline($this->img,$x1,$y1,$x2,$y2,$this->current_color);
6412         }
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);
6421         }
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);               
6430         }
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.
6437                         
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);
6442
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);
6445         }               
6446         $this->lastx=$x2; $this->lasty=$y2;             
6447     }
6448
6449     function Polygon($p,$closed=FALSE) {
6450         if( $this->line_weight==0 ) return;
6451         $n=count($p);
6452         $oldx = $p[0];
6453         $oldy = $p[1];
6454         for( $i=2; $i < $n; $i+=2 ) {
6455             $this->Line($oldx,$oldy,$p[$i],$p[$i+1]);
6456             $oldx = $p[$i];
6457             $oldy = $p[$i+1];
6458         }
6459         if( $closed )
6460             $this->Line($oldx,$oldy,$p[0],$p[1]);
6461     }
6462         
6463     function FilledPolygon($pts) {
6464         $n=count($pts);
6465         if( $n == 0 ) 
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);
6470     }
6471         
6472     function Rectangle($xl,$yu,$xr,$yl) {
6473         $this->Polygon(array($xl,$yu,$xr,$yu,$xr,$yl,$xl,$yl,$xl,$yu));
6474     }
6475         
6476     function FilledRectangle($xl,$yu,$xr,$yl) {
6477         $this->FilledPolygon(array($xl,$yu,$xr,$yu,$xr,$yl,$xl,$yl));
6478     }
6479
6480     function FilledRectangle2($xl,$yu,$xr,$yl,$color1,$color2,$style=1) {
6481         // Fill a rectangle with lines of two colors
6482         if( $style===1 ) {
6483             // Horizontal stripe
6484             if( $yl < $yu ) {
6485                 $t = $yl; $yl=$yu; $yu=$t;
6486             }
6487             for( $y=$yu; $y <= $yl; ++$y) {
6488                 $this->SetColor($color1);
6489                 $this->Line($xl,$y,$xr,$y);
6490                 ++$y;
6491                 $this->SetColor($color2);
6492                 $this->Line($xl,$y,$xr,$y);
6493             }
6494         }
6495         else {
6496             if( $xl < $xl ) {
6497                 $t = $xl; $xl=$xr; $xr=$t;
6498             }
6499             for( $x=$xl; $x <= $xr; ++$x) {
6500                 $this->SetColor($color1);
6501                 $this->Line($x,$yu,$x,$yl);
6502                 ++$x;
6503                 $this->SetColor($color2);
6504                 $this->Line($x,$yu,$x,$yl);
6505             }
6506         }
6507     }
6508
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);
6516         $this->PopColor();
6517         if( $fcolor==false )
6518             $this->Rectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1);
6519         else {          
6520             $this->PushColor($fcolor);
6521             $this->FilledRectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1);
6522             $this->PopColor();
6523             $this->Rectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1);
6524         }
6525     }
6526
6527     function FilledRoundedRectangle($xt,$yt,$xr,$yl,$r=5) {
6528         if( $r==0 ) {
6529             $this->FilledRectangle($xt,$yt,$xr,$yl);
6530             return;
6531         }
6532
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.
6536         
6537         // Center square
6538         $this->FilledRectangle($xt+$r,$yt+$r,$xr-$r,$yl-$r);
6539         // Top band
6540         $this->FilledRectangle($xt+$r,$yt,$xr-$r,$yt+$r-1);
6541         // Bottom band
6542         $this->FilledRectangle($xt+$r,$yl-$r+1,$xr-$r,$yl);
6543         // Left band
6544         $this->FilledRectangle($xt,$yt+$r+1,$xt+$r-1,$yl-$r);
6545         // Right band
6546         $this->FilledRectangle($xr-$r+1,$yt+$r,$xr,$yl-$r);
6547
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);
6551
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);
6555
6556     }
6557
6558     function RoundedRectangle($xt,$yt,$xr,$yl,$r=5) {    
6559
6560         if( $r==0 ) {
6561             $this->Rectangle($xt,$yt,$xr,$yl);
6562             return;
6563         }
6564
6565         // Top & Bottom line
6566         $this->Line($xt+$r,$yt,$xr-$r,$yt);
6567         $this->Line($xt+$r,$yl,$xr-$r,$yl);
6568
6569         // Left & Right line
6570         $this->Line($xt,$yt+$r,$xt,$yl-$r);
6571         $this->Line($xr,$yt+$r,$xr,$yl-$r);
6572
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);
6576
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);
6580     }
6581
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);
6585     }
6586
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);
6592         }
6593         $this->PopColor();
6594         
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);
6599         }
6600         $this->PopColor();
6601     }
6602
6603     function StyleLineTo($x,$y) {
6604         $this->StyleLine($this->lastx,$this->lasty,$x,$y);
6605         $this->lastx=$x;
6606         $this->lasty=$y;
6607     }
6608         
6609     function LineTo($x,$y) {
6610         $this->Line($this->lastx,$this->lasty,$x,$y);
6611         $this->lastx=$x;
6612         $this->lasty=$y;
6613     }
6614         
6615     function Point($x,$y) {
6616         imagesetpixel($this->img,round($x),round($y),$this->current_color);
6617     }
6618         
6619     function Fill($x,$y) {
6620         imagefill($this->img,round($x),round($y),$this->current_color);
6621     }
6622
6623     function FillToBorder($x,$y,$aBordColor) {
6624         $bc = $this->rgb->allocate($aBordColor);
6625         if( $bc == -1 ) {
6626             JpGraphError::Raise('Image::FillToBorder : Can not allocate more colors');
6627             exit();
6628         }
6629         imagefilltoborder($this->img,round($x),round($y),$bc,$this->current_color);
6630     }
6631         
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;
6645                         
6646             // The last section might overshoot so we must take a computational hit
6647             // and check this.
6648             if( $x>$xmax ) $x=$xmax;
6649             if( $y>$ymax ) $y=$ymax;
6650                         
6651             if( $x<$xmin ) $x=$xmin;
6652             if( $y<$ymin ) $y=$ymin;
6653
6654             $this->Line($lastx,$lasty,$x,$y);
6655             $lastx = $x + ($dash_space * $dx);
6656             $lasty = $y + ($dash_space * $dy);
6657         } 
6658     } 
6659
6660     function SetExpired($aFlg=true) {
6661         $this->expired = $aFlg;
6662     }
6663         
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");
6671         }
6672         header("Content-type: image/$this->img_format");
6673     }
6674
6675     // Adjust image quality for formats that allow this
6676     function SetQuality($q) {
6677         $this->quality = $q;
6678     }
6679         
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);
6685         }
6686         else {
6687             if( $aFile != "" ) {
6688                 $res = @$func($this->img,$aFile);
6689             }
6690             else
6691                 $res = @$func($this->img);
6692         }
6693         if( !$res )
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.");
6695     }
6696                 
6697     // Clear resource tide up by image
6698     function Destroy() {
6699         imagedestroy($this->img);
6700     }
6701         
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);
6706         $tst = true;
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";
6715             else
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.");
6719                                 
6720             return true;
6721         }
6722         else {
6723             if( $aFormat=="jpeg" || $aFormat=="png" || $aFormat=="gif" ) {
6724                 if( $aFormat=="jpeg" && !($supported & IMG_JPG) )
6725                     $tst=false;
6726                 elseif( $aFormat=="png" && !($supported & IMG_PNG) ) 
6727                     $tst=false;
6728                 elseif( $aFormat=="gif" && !($supported & IMG_GIF) )    
6729                     $tst=false;
6730                 else {
6731                     $this->img_format=$aFormat;
6732                     return true;
6733                 }
6734             }
6735             else 
6736                 $tst=false;
6737             if( !$tst )
6738                 JpGraphError::Raise(" Your PHP installation does not support the chosen graphic format: $aFormat");
6739         }
6740     }   
6741 } // CLASS
6742
6743 //===================================================
6744 // CLASS RotImage
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 {
6749     var $m=array();
6750     var $a=0;
6751     var $dx=0,$dy=0,$transx=0,$transy=0; 
6752         
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);    
6758     }
6759         
6760     function SetCenter($dx,$dy) {
6761         $old_dx = $this->dx;
6762         $old_dy = $this->dy;
6763         $this->dx=$dx;
6764         $this->dy=$dy;
6765         $this->SetAngle($this->a);
6766         return array($old_dx,$old_dy);
6767     }
6768         
6769     function SetTranslation($dx,$dy) {
6770         $old = array($this->transx,$this->transy);
6771         $this->transx = $dx;
6772         $this->transy = $dy;
6773         return $old;
6774     }
6775
6776     function UpdateRotMatrice()  {
6777         $a = $this->a;
6778         $a *= M_PI/180;
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 ;
6787     }
6788
6789     function SetAngle($a) {
6790         $tmp = $this->a;
6791         $this->a = $a;
6792         $this->UpdateRotMatrice();
6793         return $tmp;
6794     }
6795
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);
6800     }
6801
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
6805         // call.
6806         if( $GLOBALS['gd2'] ) {
6807             list($xc,$yc) = $this->Rotate($xc,$yc);
6808         }
6809         parent::FilledCircle($xc,$yc,$r);
6810     }
6811
6812         
6813     function Arc($xc,$yc,$w,$h,$s,$e) {
6814         list($xc,$yc) = $this->Rotate($xc,$yc);
6815         $s += $this->a;
6816         $e += $this->a;
6817         parent::Arc($xc,$yc,$w,$h,$s,$e);
6818     }
6819
6820     function FilledArc($xc,$yc,$w,$h,$s,$e) {
6821         list($xc,$yc) = $this->Rotate($xc,$yc);
6822         $s += $this->a;
6823         $e += $this->a;
6824         parent::FilledArc($xc,$yc,$w,$h,$s,$e);
6825     }
6826
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();
6832     }
6833         
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 );
6838         }
6839         else {
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);
6843         }
6844     }
6845         
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]);
6849         return $pnts;
6850     }
6851         
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);
6856     }
6857
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);
6861     }
6862         
6863     function FilledRectangle($x1,$y1,$x2,$y2) {
6864         if( $y1==$y2 || $x1==$x2 )
6865             $this->Line($x1,$y1,$x2,$y2);
6866         else 
6867             $this->FilledPolygon(array($x1,$y1,$x2,$y1,$x2,$y2,$x1,$y2));
6868     }
6869         
6870     function Polygon($pnts,$closed=FALSE) {
6871         //Polygon uses Line() so it will be rotated through that call
6872         parent::Polygon($pnts,$closed);
6873     }
6874         
6875     function FilledPolygon($pnts) {
6876         parent::FilledPolygon($this->ArrRotate($pnts));
6877     }
6878         
6879     function Point($x,$y) {
6880         list($xp,$yp) = $this->Rotate($x,$y);
6881         parent::Point($xp,$yp);
6882     }
6883         
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);
6887     }
6888 }
6889
6890 //===================================================
6891 // CLASS ImgStreamCache
6892 // Description: Handle caching of graphs to files
6893 //===================================================
6894 class ImgStreamCache {
6895     var $cache_dir;
6896     var $img=null;
6897     var $timeout=0;     // Infinite timeout
6898     //---------------
6899     // CONSTRUCTOR
6900     function ImgStreamCache(&$aImg, $aCacheDir=CACHE_DIR) {
6901         $this->img = &$aImg;
6902         $this->cache_dir = $aCacheDir;
6903     }
6904
6905 //---------------
6906 // PUBLIC METHODS       
6907
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;       
6914     }
6915         
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
6919         // used
6920         GLOBAL $gJpgBrandTiming;
6921         
6922         if( $gJpgBrandTiming ) {
6923             global $tim;
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);                       
6928         }
6929
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?");
6938             }
6939             $aImage->Stream($aStrokeFileName);
6940             return;
6941         }
6942
6943         if( $aCacheFileName != "" && USE_CACHE) {
6944
6945             $aCacheFileName = $this->cache_dir . $aCacheFileName;
6946             if( file_exists($aCacheFileName) ) {
6947                 if( !$aInline ) {
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);
6952                     if( $diff < 0 )
6953                         JpGraphError::Raise(" Cached imagefile ($aCacheFileName) has file date in the future!!");
6954                     if( $this->timeout>0 && ($diff <= $this->timeout*60) ) 
6955                         return;         
6956                 }                       
6957                 if( !@unlink($aCacheFileName) )
6958                     JpGraphError::Raise(" Can't delete cached image $aStrokeFileName. Permission problem?");
6959                 $aImage->Stream($aCacheFileName);       
6960             }
6961             else {
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.');
6965                 }
6966                 $aImage->Stream($aCacheFileName);
6967             }
6968                         
6969             $res=true;
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);
6975             if( !$res )
6976                 JpGraphError::Raise(" Can't set permission for cached image $aStrokeFileName. Permission problem?");
6977                         
6978             $aImage->Destroy();
6979             if( $aInline ) {
6980                 if ($fh = @fopen($aCacheFileName, "rb") ) {
6981                     $this->img->Headers();
6982                     fpassthru($fh);
6983                     return;
6984                 }
6985                 else
6986                     JpGraphError::Raise(" Cant open file from cache [$aFile]"); 
6987             }
6988         }
6989         elseif( $aInline ) {
6990             $this->img->Headers();                      
6991             $aImage->Stream();  
6992             return;
6993         }
6994     }
6995         
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) ) {
7004                 return false;           
7005             }
7006             else {
7007                 if ($fh = @fopen($aCacheFileName, "rb")) {
7008                     $this->img->Headers();
7009                     fpassthru($fh);
7010                     return true;
7011                 }
7012                 else
7013                     JpGraphError::Raise(" Can't open cached image \"$aCacheFileName\" for reading.");
7014             }
7015         } 
7016         return false;
7017     }
7018         
7019     //---------------
7020     // PRIVATE METHODS  
7021     // Create all necessary directories in a path
7022     function MakeDirs($aFile) {
7023         $dirs = array();
7024         while ( !(file_exists($aFile)) ) {
7025             $dirs[] = $aFile;
7026             $aFile = dirname($aFile);
7027         }
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 != "" ) {
7036                 $res=true;
7037                 $res =@chgrp($dirs[$i],CACHE_FILE_GROUP);
7038                 $res &= @chmod($dirs[$i],0777);
7039                 if( !$res )
7040                     JpGraphError::Raise(" Can't set permissions for $aFile. Permission problems?");
7041             }
7042         }
7043         return true;
7044     }   
7045 } // CLASS Cache
7046         
7047 //===================================================
7048 // CLASS Legend
7049 // Description: Responsible for drawing the box containing
7050 // all the legend text for the graph
7051 //===================================================
7052 DEFINE('_DEFAULT_LPM_SIZE',8);
7053 class Legend {
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;
7067     var $csimareas='';
7068     var $reverse = false ;
7069 //---------------
7070 // CONSTRUCTOR
7071     function Legend() {
7072         // Empty
7073     }
7074 //---------------
7075 // PUBLIC METHODS       
7076     function Hide($aHide=true) {
7077         $this->hide=$aHide;
7078     }
7079         
7080     function SetShadow($aShow='gray',$aWidth=2) {
7081         if( is_string($aShow) ) {
7082             $this->shadow_color = $aShow;
7083             $this->shadow=true;
7084         }
7085         else
7086             $this->shadow=$aShow;
7087         $this->shadow_width=$aWidth;
7088     }
7089
7090     function SetMarkAbsSize($aSize) {
7091         $this->mark_abs_size = $aSize ;
7092     }
7093
7094     function SetLineWeight($aWeight) {
7095         $this->weight = $aWeight;
7096     }
7097
7098     function SetFrameWeight($aWeight) {
7099         $this->frameweight = $aWeight;
7100     }
7101         
7102     function SetLayout($aDirection=LEGEND_VERT) {
7103         $this->layout_n = $aDirection==LEGEND_VERT ? 1 : 99 ;
7104     }
7105         
7106     function SetColumns($aCols) {
7107         $this->layout_n = $aCols ;
7108     }
7109
7110     function SetLineSpacing($aSpacing) {
7111         $this->ymargin = $aSpacing ;
7112     }
7113
7114     function SetReverse($f=true) {
7115         $this->reverse = $f ;
7116     }
7117
7118     // Set color on frame around box
7119     function SetColor($aFontColor,$aColor='black') {
7120         $this->font_color=$aFontColor;
7121         $this->color=$aColor;
7122     }
7123         
7124     function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
7125         $this->font_family = $aFamily;
7126         $this->font_style = $aStyle;
7127         $this->font_size = $aSize;
7128     }
7129         
7130     function SetPos($aX,$aY,$aHAlign="right",$aVAlign="top") {
7131         $this->Pos($aX,$aY,$aHAlign,$aVAlign);
7132     }
7133
7134     function SetAbsPos($aX,$aY,$aHAlign="right",$aVAlign="top") {
7135         $this->xabspos=$aX;
7136         $this->yabspos=$aY;
7137         $this->halign=$aHAlign;
7138         $this->valign=$aVAlign;
7139     }
7140
7141
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");
7145         $this->xpos=$aX;
7146         $this->ypos=$aY;
7147         $this->halign=$aHAlign;
7148         $this->valign=$aVAlign;
7149     }
7150
7151     function SetFillColor($aColor) {
7152         $this->fill_color=$aColor;
7153     }
7154         
7155     function Add($aTxt,$aColor,$aPlotmark="",$aLinestyle=0,$csimtarget="",$csimalt="") {
7156         $this->txtcol[]=array($aTxt,$aColor,$aPlotmark,$aLinestyle,$csimtarget,$csimalt);
7157     }
7158
7159     function GetCSIMAreas() {
7160         return $this->csimareas;
7161     }
7162         
7163     function Stroke(&$aImg) {
7164         if( $this->hide ) return;
7165
7166         $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);          
7167
7168         if( $this->reverse ) {
7169             $this->txtcol = array_reverse($this->txtcol);
7170         }
7171
7172         $n=count($this->txtcol);
7173         if( $n == 0 ) return;
7174
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;
7181             $colheight[$i] = 0;
7182         }
7183
7184         // Find our maximum height in each row
7185         $rows = -1 ;
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 ) {
7189                 $rows++;
7190                 $rowheight[$rows] = 0;
7191             }
7192             $rowheight[$rows] = max($rowheight[$rows],$h);
7193         }
7194
7195         $abs_height = 0;
7196         for( $i=0; $i <= $rows; ++$i ) {
7197             $abs_height += $rowheight[$i] ;
7198         }
7199
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;
7203                                                 
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]);
7209         }
7210         
7211         // Get the total width
7212         $mtw = 0;
7213         for( $i=0; $i < $numcolumns; ++$i ) {
7214             $mtw += $colwidth[$i] + $this->xmargin;
7215         }
7216
7217         // Find out maximum width we need for legend box
7218         $abs_width = $mtw+$this->xmargin;
7219
7220         if( $this->xabspos === -1  && $this->yabspos === -1 ) {
7221             $this->xabspos = $this->xpos*$aImg->width ;
7222             $this->yabspos = $this->ypos*$aImg->height ;
7223         }
7224
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; 
7230         else  
7231             $xp = $aImg->width - $this->xabspos - $abs_width;
7232
7233         $yp=$this->yabspos;
7234         if( $this->valign=="center" )
7235             $yp-=$abs_height/2;
7236         elseif( $this->valign=="bottom" )
7237             $yp-=$abs_height;
7238                         
7239         // Stroke legend box
7240         $aImg->SetColor($this->color);  
7241         $aImg->SetLineWeight($this->frameweight);
7242
7243         if( $this->shadow )
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);
7247         else {
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);
7252         }
7253
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; 
7258         
7259         $f2 =  round($aImg->GetTextHeight('X')/2);
7260
7261         $grad = new Gradient($aImg);
7262
7263         // Now stroke each legend in turn
7264         $i = 1 ; $row = 0;
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]);
7270                 if( $p[3] > 0 ) {
7271                     $aImg->SetLineStyle($p[3]);
7272                     $aImg->StyleLine($x1-$this->mark_abs_size,$y1+$f2,$x1+$this->mark_abs_size,$y1+$f2);
7273                 }
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 = '';
7278
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
7282
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);
7287                     
7288                     }
7289                     else {
7290                         $p[2]->SetSize($this->mark_abs_size);
7291                         $p[2]->Stroke($aImg,$x1,$y1+$f2);
7292                     }
7293                 }
7294             } 
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);
7301             } 
7302             else {
7303                 // Draw a colored box
7304                 $color = $p[1] ;
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]);
7311                 }
7312                 else {
7313                     $aImg->SetColor($p[1]);
7314                     $aImg->FilledRectangle($x1,$ym,$x1+$this->mark_abs_size,$ym+$this->mark_abs_size);
7315                 }
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);
7319             }
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]);
7324
7325             // Add CSIM for Legend if defined
7326             if( $p[4] != "" ) {
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\"";
7335                     }
7336                     $this->csimareas .= ">\n";
7337                 }
7338             }
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++];
7343                 $i = 1;
7344             }
7345             else {
7346                 $x1 += $colwidth[($i-1) % $numcolumns] + $this->xmargin;
7347                 ++$i;
7348             }
7349         }       
7350     }
7351 } // Class
7352         
7353
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='';
7361     var $angle=0;
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;
7366
7367     function Show($aFlag=true) {
7368         $this->show=$aFlag;
7369     }
7370
7371     function SetColor($aColor,$aNegcolor="") {
7372         $this->color = $aColor;
7373         $this->negcolor = $aNegcolor;
7374     }
7375
7376     function SetFont($aFontFamily,$aFontStyle=FS_NORMAL,$aFontSize=10) {
7377         $this->ff=$aFontFamily;
7378         $this->fs=$aFontStyle;
7379         $this->fsize=$aFontSize;
7380     }
7381
7382     function SetMargin($aMargin) {
7383         $this->margin = $aMargin;
7384     }
7385
7386     function SetAngle($aAngle) {
7387         $this->angle = $aAngle;
7388     }
7389
7390     function SetAlign($aHAlign,$aVAlign='') {
7391         $this->halign = $aHAlign;
7392         $this->valign = $aVAlign;
7393     }
7394
7395     function SetFormat($aFormat,$aNegFormat="") {
7396         $this->format= $aFormat;
7397         $this->negformat= $aNegFormat;
7398     }
7399
7400     function SetFormatCallback($aFunc) {
7401         $this->iFormCallback = $aFunc;
7402     }
7403
7404     function HideZero($aFlag=true) {
7405         $this->iHideZero=$aFlag;
7406     }
7407
7408     function Stroke($img,$aVal,$x,$y) {
7409         
7410         if( $this->show ) 
7411         {
7412             if( $this->negformat=="" ) $this->negformat=$this->format;
7413             if( $this->negcolor=="" ) $this->negcolor=$this->color;
7414
7415             if( $aVal===NULL || (is_string($aVal) && ($aVal=="" || $aVal=="-" || $aVal=="x" ) ) ) 
7416                 return;
7417
7418             if( is_numeric($aVal) && $aVal==0 && $this->iHideZero ) {
7419                 return;
7420             }
7421
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);
7429             }
7430             elseif( is_numeric($aVal) ) {
7431                 if( $aVal >= 0 )
7432                     $sval=sprintf($this->format,$aVal);
7433                 else
7434                     $sval=sprintf($this->negformat,$aVal);
7435             }
7436             else
7437                 $sval=$aVal;
7438
7439             $y = $y-sign($aVal)*$this->margin;
7440
7441             $txt = new Text($sval,$x,$y);
7442             $txt->SetFont($this->ff,$this->fs,$this->fsize);
7443             if( $this->valign == "" ) {
7444                 if( $aVal >= 0 )
7445                     $valign = "bottom";
7446                 else
7447                     $valign = "top";
7448             }
7449             else
7450                 $valign = $this->valign;
7451             $txt->Align($this->halign,$valign);
7452
7453             $txt->SetOrientation($this->angle);
7454             if( $aVal > 0 )
7455                 $txt->SetColor($this->color);
7456             else
7457                 $txt->SetColor($this->negcolor);
7458             $txt->Stroke($img);
7459         }
7460     }
7461 }
7462
7463
7464 //===================================================
7465 // CLASS Plot
7466 // Description: Abstract base class for all concrete plot classes
7467 //===================================================
7468 class Plot {
7469     var $line_weight=1;
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
7475     var $color="black";
7476     var $numpoints=0;
7477     var $weight=1;      
7478     var $value;
7479     var $center=false;
7480     var $legendcsimtarget='';
7481     var $legendcsimalt='';
7482 //---------------
7483 // CONSTRUCTOR
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();
7492     }
7493
7494 //---------------
7495 // PUBLIC METHODS       
7496
7497     // Stroke the plot
7498     // "virtual" function which must be implemented by
7499     // the subclasses
7500     function Stroke(&$aImg,&$aXScale,&$aYScale) {
7501         JpGraphError::Raise("JpGraph: Stroke() must be implemented by concrete subclass to class Plot");
7502     }
7503
7504     function HideLegend($f=true) {
7505         $this->hidelegend = $f;
7506     }
7507
7508     function DoLegend(&$graph) {
7509         if( !$this->hidelegend )
7510             $this->Legend($graph);
7511     }
7512
7513     function StrokeDataValue($img,$aVal,$x,$y) {
7514         $this->value->Stroke($img,$aVal,$x,$y);
7515     }
7516         
7517     // Set href targets for CSIM        
7518     function SetCSIMTargets($aTargets,$aAlts=null) {
7519         $this->csimtargets=$aTargets;
7520         $this->csimalts=$aAlts;         
7521     }
7522         
7523     // Get all created areas
7524     function GetCSIMareas() {
7525         return $this->csimareas;
7526     }   
7527         
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.");
7533         return true;    
7534     }
7535         
7536     // Get minimum values in plot
7537     function Min() {
7538         if( isset($this->coords[1]) )
7539             $x=$this->coords[1];
7540         else
7541             $x="";
7542         if( $x != "" && count($x) > 0 )
7543             $xm=min($x);
7544         else 
7545             $xm=0;
7546         $y=$this->coords[0];
7547         if( count($y) > 0 ) {
7548             $ym = $y[0];
7549             $cnt = count($y);
7550             $i=0;
7551             while( $i<$cnt && !is_numeric($ym=$y[$i]) )
7552                 $i++;
7553             while( $i < $cnt) {
7554                 if( is_numeric($y[$i]) ) 
7555                     $ym=min($ym,$y[$i]);
7556                 ++$i;
7557             }                   
7558         }
7559         else 
7560             $ym="";
7561         return array($xm,$ym);
7562     }
7563         
7564     // Get maximum value in plot
7565     function Max() {
7566         if( isset($this->coords[1]) )
7567             $x=$this->coords[1];
7568         else
7569             $x="";
7570
7571         if( $x!="" && count($x) > 0 )
7572             $xm=max($x);
7573         else {
7574             //$xm=count($this->coords[0])-1;    // We count from 0..(n-1)
7575             $xm = $this->numpoints-1;
7576         }
7577         $y=$this->coords[0];
7578         if( count($y) > 0 ) {
7579             if( !isset($y[0]) ) {
7580                 $y[0] = 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!!");
7583             }
7584             $cnt = count($y);
7585             $i=0;
7586             while( $i<$cnt && !is_numeric($ym=$y[$i]) )
7587                 $i++;                           
7588             while( $i < $cnt ) {
7589                 if( is_numeric($y[$i]) ) $ym=max($ym,$y[$i]);
7590                 ++$i;
7591             }
7592         }
7593         else 
7594             $ym="";
7595         return array($xm,$ym);
7596     }
7597         
7598     function SetColor($aColor) {
7599         $this->color=$aColor;
7600     }
7601         
7602     function SetLegend($aLegend,$aCSIM="",$aCSIMAlt="") {
7603         $this->legend = $aLegend;
7604         $this->legendcsimtarget = $aCSIM;
7605         $this->legendcsimalt = $aCSIMAlt;
7606     }
7607
7608     function SetWeight($aWeight) {
7609         $this->weight=$aWeight;
7610     }
7611                 
7612     function SetLineWeight($aWeight=1) {
7613         $this->line_weight=$aWeight;
7614     }
7615         
7616     function SetCenter($aCenter=true) {
7617         $this->center = $aCenter;
7618     }
7619         
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) {
7623         return true;
7624     }
7625
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);    
7630     }
7631         
7632 } // Class
7633
7634 require_once "jpgraph_plotmark.inc" ;
7635
7636 //==============================================================================
7637 // The following section contains classes to implement the "band" functionality
7638 //==============================================================================
7639
7640 // Utility class to hold coordinates for a rectangle
7641 class Rectangle {
7642     var $x,$y,$w,$h;
7643     var $xe, $ye;
7644     function Rectangle($aX,$aY,$aWidth,$aHeight) {
7645         $this->x=$aX;
7646         $this->y=$aY;
7647         $this->w=$aWidth;
7648         $this->h=$aHeight;
7649         $this->xe=$aX+$aWidth-1;
7650         $this->ye=$aY+$aHeight-1;
7651     }
7652 }
7653
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 //=====================================================================
7661 class RectPattern {
7662     var $color;
7663     var $weight;
7664     var $rect=null;
7665     var $doframe=true;
7666     var $linespacing;   // Line spacing in pixels
7667     var $iBackgroundColor=-1;  // Default is no background fill
7668         
7669     function RectPattern($aColor,$aWeight=1) {
7670         $this->color = $aColor;
7671         $this->weight = $aWeight;               
7672     }
7673
7674     function SetBackground($aBackgroundColor) {
7675         $this->iBackgroundColor=$aBackgroundColor;
7676     }
7677
7678     function SetPos(&$aRect) {
7679         $this->rect = $aRect;
7680     }
7681         
7682     function ShowFrame($aShow=true) {
7683         $this->doframe=$aShow;
7684     }
7685
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;
7692
7693     }
7694
7695     function Stroke(&$aImg) {
7696         if( $this->rect == null )
7697             JpGraphError::Raise(" No positions specified for pattern.");
7698
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); 
7702         }
7703
7704         $aImg->SetColor($this->color);
7705         $aImg->SetLineWeight($this->weight);
7706
7707         // Virtual function implemented by subclass
7708         $this->DoPattern($aImg);
7709
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);
7713     }
7714
7715 }
7716
7717
7718 //=====================================================================
7719 // Class RectPatternSolid
7720 // Implements a solid band
7721 //=====================================================================
7722 class RectPatternSolid extends RectPattern {
7723
7724     function RectPatternSolid($aColor="black",$aWeight=1) {
7725         parent::RectPattern($aColor,$aWeight);
7726     }
7727
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);
7732     }
7733 }
7734
7735 //=====================================================================
7736 // Class RectPatternHor
7737 // Implements horizontal line pattern
7738 //=====================================================================
7739 class RectPatternHor extends RectPattern {
7740                 
7741     function RectPatternHor($aColor="black",$aWeight=1,$aLineSpacing=7) {
7742         parent::RectPattern($aColor,$aWeight);
7743         $this->linespacing = $aLineSpacing;
7744     }
7745                 
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;
7753         }
7754     }
7755 }
7756
7757 //=====================================================================
7758 // Class RectPatternVert
7759 // Implements vertical line pattern
7760 //=====================================================================
7761 class RectPatternVert extends RectPattern {
7762     var $linespacing=10;        // Line spacing in pixels
7763                 
7764     function RectPatternVert($aColor="black",$aWeight=1,$aLineSpacing=7) {
7765         parent::RectPattern($aColor,$aWeight);
7766         $this->linespacing = $aLineSpacing;
7767     }
7768
7769     //--------------------
7770     // Private methods
7771     //
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;
7779         }
7780     }
7781 }
7782
7783
7784 //=====================================================================
7785 // Class RectPatternRDiag
7786 // Implements right diagonal pattern
7787 //=====================================================================
7788 class RectPatternRDiag extends RectPattern {
7789     var $linespacing;   // Line spacing in pixels
7790                 
7791     function RectPatternRDiag($aColor="black",$aWeight=1,$aLineSpacing=12) {
7792         parent::RectPattern($aColor,$aWeight);
7793         $this->linespacing = $aLineSpacing;
7794     }
7795
7796     function DoPattern(&$aImg) {
7797         //  --------------------
7798         //  | /   /   /   /   /|
7799         //  |/   /   /   /   / |
7800         //  |   /   /   /   /  |
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);
7808
7809         while($x0<=$xe && $y1<=$ye) {
7810             $aImg->Line($x0,$y0,$x1,$y1);
7811             $x0 += $this->linespacing;
7812             $y1 += $this->linespacing;
7813         }
7814
7815         if( $xe-$x1 > $ye-$y0 ) { 
7816             // Width larger than height
7817             $x1 = $this->rect->x + ($y1-$ye);
7818             $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;
7824             }
7825             
7826             $y0=$this->rect->y + ($x0-$xe);
7827             $x0=$xe;
7828         }
7829         else {
7830             // Height larger than width
7831             $diff = $x0-$xe;
7832             $y0 = $diff+$this->rect->y;
7833             $x0 = $xe;
7834             $x1 = $this->rect->x;
7835             while( $y1 <= $ye ) {
7836                 $aImg->Line($x0,$y0,$x1,$y1);
7837                 $y1 += $this->linespacing;
7838                 $y0 += $this->linespacing;
7839             }
7840             
7841             $diff = $y1-$ye;
7842             $y1 = $ye;
7843             $x1 = $diff + $this->rect->x;
7844         }
7845
7846         while( $y0 <= $ye ) {
7847             $aImg->Line($x0,$y0,$x1,$y1);
7848             $y0 += $this->linespacing;          
7849             $x1 += $this->linespacing;
7850         }
7851     }
7852 }
7853  
7854 //=====================================================================
7855 // Class RectPatternLDiag
7856 // Implements left diagonal pattern
7857 //=====================================================================
7858 class RectPatternLDiag extends RectPattern {
7859     var $linespacing;   // Line spacing in pixels
7860                 
7861     function RectPatternLDiag($aColor="black",$aWeight=1,$aLineSpacing=12) {
7862         $this->linespacing = $aLineSpacing;
7863         parent::RectPattern($aColor,$aWeight);
7864     }
7865
7866     function DoPattern(&$aImg) {
7867         //  --------------------
7868         //  |\   \   \   \   \ |
7869         //  | \   \   \   \   \|
7870         //  |  \   \   \   \   |
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);
7878
7879         while($x0<=$xe && $y1>=$this->rect->y) {
7880             $aImg->Line($x0,$y0,$x1,$y1);
7881             $x0 += $this->linespacing;
7882             $y1 -= $this->linespacing;
7883         }
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;
7892             }
7893             
7894             $y0=$this->rect->ye - ($x0-$xe);
7895             $x0=$xe;
7896         }
7897         else {
7898             // Height larger than width
7899             $diff = $x0-$xe;
7900             $y0 = $ye-$diff;
7901             $x0 = $xe;
7902             while( $y1 >= $this->rect->y ) {
7903                 $aImg->Line($x0,$y0,$x1,$y1);
7904                 $y0 -= $this->linespacing;
7905                 $y1 -= $this->linespacing;
7906             }       
7907             $diff = $this->rect->y - $y1;
7908             $x1 = $this->rect->x + $diff;
7909             $y1 = $this->rect->y;
7910         }
7911         while( $y0 >= $this->rect->y ) {
7912             $aImg->Line($x0,$y0,$x1,$y1);
7913             $y0 -= $this->linespacing;
7914             $x1 += $this->linespacing;
7915         }
7916     }
7917 }
7918
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
7927     // converge.
7928
7929     function RectPattern3DPlane($aColor="black",$aWeight=1) {
7930         parent::RectPattern($aColor,$aWeight);
7931         $this->SetDensity(10);  // Slightly larger default
7932     }
7933
7934     function SetHorizon($aHorizon) {
7935         $this->alpha=$aHorizon;
7936     }
7937         
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;
7942         $x1 = $x0;
7943         $y1 = $this->rect->ye;
7944         $x0_right = $x0;
7945         $x1_right = $x1;
7946
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;
7953
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
7958
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;
7971         }
7972
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;
7985         }               
7986                 
7987         // Horizontal lines
7988         // They need some serious consideration since they are a function
7989         // of perspective depth (alpha) and density (linespacing)
7990         $x0=$this->rect->x;
7991         $x1=$this->rect->xe;
7992         $y=$this->rect->ye;
7993                 
7994         // The first line is drawn directly. Makes the loop below slightly
7995         // more readable.
7996         $aImg->Line($x0,$y,$x1,$y);
7997         $hls = $this->linespacing;
7998                 
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
8002         // square.
8003         $vls = $this->linespacing*0.6;
8004                 
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.
8008                 
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.
8012         /*
8013           $x0 = $middle;
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);
8020           $aImg->PopColor();
8021         */
8022                 
8023         $y -= $vls;             
8024         $k=($this->rect->ye-($this->rect->ye-$vls))/($middle-($middle-$ds));
8025         $dist = $hls;
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);
8031             $dist += $hls;
8032         }
8033     }
8034 }
8035
8036 //=====================================================================
8037 // Class RectPatternCross
8038 // Vert/Hor crosses
8039 //=====================================================================
8040 class RectPatternCross extends RectPattern {
8041     var $vert=null;
8042     var $hor=null;
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);
8047     }
8048
8049     function SetOrder($aDepth) {
8050         $this->vert->SetOrder($aDepth);
8051         $this->hor->SetOrder($aDepth);
8052     }
8053
8054     function SetPos(&$aRect) {
8055         parent::SetPos($aRect);
8056         $this->vert->SetPos($aRect);
8057         $this->hor->SetPos($aRect);
8058     }
8059
8060     function SetDensity($aDens) {
8061         $this->vert->SetDensity($aDens);
8062         $this->hor->SetDensity($aDens);
8063     }
8064
8065     function DoPattern(&$aImg) {
8066         $this->vert->DoPattern($aImg);
8067         $this->hor->DoPattern($aImg);
8068     }
8069 }
8070
8071 //=====================================================================
8072 // Class RectPatternDiagCross
8073 // Vert/Hor crosses
8074 //=====================================================================
8075
8076 class RectPatternDiagCross extends RectPattern {
8077     var $left=null;
8078     var $right=null;
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);
8083     }
8084
8085     function SetOrder($aDepth) {
8086         $this->left->SetOrder($aDepth);
8087         $this->right->SetOrder($aDepth);
8088     }
8089
8090     function SetPos(&$aRect) {
8091         parent::SetPos($aRect);
8092         $this->left->SetPos($aRect);
8093         $this->right->SetPos($aRect);
8094     }
8095
8096     function SetDensity($aDens) {
8097         $this->left->SetDensity($aDens);
8098         $this->right->SetDensity($aDens);
8099     }
8100
8101     function DoPattern(&$aImg) {
8102         $this->left->DoPattern($aImg);
8103         $this->right->DoPattern($aImg);
8104     }
8105
8106 }
8107
8108 //=====================================================================
8109 // Class RectPatternFactory
8110 // Factory class for rectangular pattern 
8111 //=====================================================================
8112 class RectPatternFactory {
8113     function RectPatternFactory() {
8114         // Empty
8115     }
8116     function Create($aPattern,$aColor,$aWeight=1) {
8117         switch($aPattern) {
8118             case BAND_RDIAG:
8119                 $obj =  new RectPatternRDiag($aColor,$aWeight);
8120                 break;
8121             case BAND_LDIAG:
8122                 $obj =  new RectPatternLDiag($aColor,$aWeight);
8123                 break;
8124             case BAND_SOLID:
8125                 $obj =  new RectPatternSolid($aColor,$aWeight);
8126                 break;
8127             case BAND_VLINE:
8128                 $obj =  new RectPatternVert($aColor,$aWeight);
8129                 break;
8130             case BAND_HLINE:
8131                 $obj =  new RectPatternHor($aColor,$aWeight);
8132                 break;
8133             case BAND_3DPLANE:
8134                 $obj =  new RectPattern3DPlane($aColor,$aWeight);
8135                 break;
8136             case BAND_HVCROSS:
8137                 $obj =  new RectPatternCross($aColor,$aWeight);
8138                 break;
8139             case BAND_DIAGCROSS:
8140                 $obj =  new RectPatternDiagCross($aColor,$aWeight);
8141                 break;
8142             default:
8143                 JpGraphError::Raise(" Unknown pattern specification ($aPattern)");
8144         }
8145         return $obj;
8146     }
8147 }
8148
8149
8150 //=====================================================================
8151 // Class PlotBand
8152 // Factory class which is used by the client.
8153 // It is responsible for factoring the corresponding pattern
8154 // concrete class.
8155 //=====================================================================
8156 class PlotBand {
8157     var $prect=null;
8158     var $depth;
8159     var $dir, $min, $max;
8160
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.');
8166         $this->dir = $aDir;
8167         $this->min = $aMin;
8168         $this->max = $aMax;
8169         $this->depth=$aDepth;
8170     }
8171         
8172     // Set position. aRect contains absolute image coordinates
8173     function SetPos(&$aRect) {
8174         assert( $this->prect != null ) ;
8175         $this->prect->SetPos($aRect);
8176     }
8177         
8178     function ShowFrame($aFlag=true) {
8179         $this->prect->ShowFrame($aFlag);
8180     }
8181
8182     // Set z-order. In front of pplot or in the back
8183     function SetOrder($aDepth) {
8184         $this->depth=$aDepth;
8185     }
8186         
8187     function SetDensity($aDens) {
8188         $this->prect->SetDensity($aDens);
8189     }
8190         
8191     function GetDir() {
8192         return $this->dir;
8193     }
8194         
8195     function GetMin() {
8196         return $this->min;
8197     }
8198         
8199     function GetMax() {
8200         return $this->max;
8201     }
8202         
8203     // Display band
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();
8209
8210             // Only draw the bar if it actually appears in the range
8211             if ($this->min < $aYScale->GetMaxVal() && $this->max > $aYScale->GetMinVal()) {
8212             
8213             // Trucate to limit of axis
8214             $this->min = max($this->min, $aYScale->GetMinVal());
8215             $this->max = min($this->max, $aYScale->GetMaxVal());
8216
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);
8223             }
8224         }
8225         else {  // VERTICAL
8226             if( $this->min === 'min' ) $this->min = $aXScale->GetMinVal();
8227             if( $this->max === 'max' ) $this->max = $aXScale->GetMaxVal();
8228             
8229             // Only draw the bar if it actually appears in the range
8230             if ($this->min < $aXScale->GetMaxVal() && $this->max > $aXScale->GetMinVal()) {
8231             
8232             // Trucate to limit of axis
8233             $this->min = max($this->min, $aXScale->GetMinVal());
8234             $this->max = min($this->max, $aXScale->GetMaxVal());
8235
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);
8242             }
8243         }
8244     }
8245 }
8246
8247 //===================================================
8248 // CLASS PlotLine
8249 // Description: 
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 //===================================================
8255 class PlotLine {
8256     var $weight=1;
8257     var $color="black";
8258     var $direction=-1; 
8259     var $scaleposition;
8260
8261 //---------------
8262 // CONSTRUCTOR
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;
8268     }
8269         
8270 //---------------
8271 // PUBLIC METHODS       
8272     function SetPosition($aScalePosition) {
8273         $this->scaleposition=$aScalePosition;
8274     }
8275         
8276     function SetDirection($aDir) {
8277         $this->direction = $aDir;
8278     }
8279         
8280     function SetColor($aColor) {
8281         $this->color=$aColor;
8282     }
8283         
8284     function SetWeight($aWeight) {
8285         $this->weight=$aWeight;
8286     }
8287         
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);
8296         }
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);
8302         }
8303         else
8304             JpGraphError::Raise(" Illegal direction for static line");
8305     }
8306 }
8307
8308 // <EOF>
8309 ?>