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