Location: PHPKode > projects > PhpSERA > phpsera-0.3alpha1/html/jpgraph.php
<?php 
//=======================================================================
// File:	JPGRAPH.PHP
// Description:	PHP4 Graph Plotting library. Base module.
// Created: 	2001-01-08
// Author:	Johan Persson (hide@address.com)
// Ver:		$Id: jpgraph.php,v 1.2 2004/01/05 14:19:58 koot Exp $
//
// License:	This code is released under QPL 1.0 
// Copyright (C) 2001,2002,2003 Johan Persson 
//========================================================================

//------------------------------------------------------------------------
// Directories for cache and font directory.
// Leave them undefined to use default values. 
// 
// Default values used if these defines are left commented out are:
// 
// UNIX: 
//   CACHE_DIR = /tmp/jpgraph_cache/
//   TTF_DIR   = /usr/X11R6/lib/X11/fonts/truetype/
//
// WINDOWS:
//   CACHE_DIR = $SERVER_TEMP/jpgraph_cache/
//   TTF_DIR   = $SERVER_SYSTEMROOT/fonts/
//    
//
//------------------------------------------------------------------------

// The full absolute name of the directory to be used to store the
// cached image files. This directory will not be used if the USE_CACHE
// define (further down) is false. If you enable the cache please note that
// this directory MUST be readable and writable for the process running PHP. 
// Must end with '/'
// DEFINE("CACHE_DIR","/tmp/jpgraph_cache/");

// Directory for jpGraph TTF fonts. Must end with '/'
// DEFINE("TTF_DIR","/usr/X11R6/lib/X11/fonts/truetype/");


//-------------------------------------------------------------------------
// Cache directory specification for use with CSIM graphs that are
// using the cache.
// The directory must be the filesysystem name as seen by PHP
// and the 'http' version must be the same directory but as 
// seen by the HTTP server relative to the 'htdocs' ddirectory. 
// If a relative path is specified it is taken to be relative from where
// the image script is executed.
// Note: The default setting is to create a subdirectory in the 
// directory from where the image script is executed and store all files
// there. As ususal this directory must be writeable by the PHP process.
DEFINE("CSIMCACHE_DIR","csimcache/"); 
DEFINE("CSIMCACHE_HTTP_DIR","csimcache/");

//------------------------------------------------------------------------
// Various JpGraph Settings. Adjust accordingly to your
// preferences. Note that cache functionality is turned off by
// default (Enable by setting USE_CACHE to true)
//------------------------------------------------------------------------

// Deafult graphic format set to "auto" which will automatically
// choose the best available format in the order png,gif,jpg
// (The supported format depends on what your PHP installation supports)
DEFINE("DEFAULT_GFORMAT","auto");

// Should the image be a truecolor image? 
// Note 1: Has only effect with GD 2.0.1 and above.
// Note 2: GD 2.0.1 + PHP 4.0.6 on Win32 crashes when trying to use 
// trucolor. Truecolor support is to be considered alpha since GD 2.x
// is still not considered stable (especially on Win32). 
// Note 3: MUST be enabled to get background images working with GD2
// Note 4: If enabled then truetype fonts will look very ugly with GD 2.0.1
// => You can't have both background images and truetype fonts in the same
// image until these bugs has been fixed in GD 2.01. There is a patch
// available for GD 2.0.1 though. See the README file.
DEFINE('USE_TRUECOLOR',true);

// Specify what version of the GD library is installed.
// If this is set to 'auto' the version will be automatically 
// determined.
// However since determining the library takes ~1ms you can also 
// manually specify the version if you know what version you have. 
// This means that you should 
// set this define to true if you have GD 2.x installed to save 1ms. 
DEFINE("USE_LIBRARY_GD2",'auto');

// Should the cache be used at all? By setting this to false no
// files will be generated in the cache directory.  
// The difference from READ_CACHE being that setting READ_CACHE to
// false will still create the image in the cache directory
// just not use it. By setting USE_CACHE=false no files will even
// be generated in the cache directory.
DEFINE("USE_CACHE",false);

// Should we try to find an image in the cache before generating it? 
// Set this define to false to bypass the reading of the cache and always
// regenerate the image. Note that even if reading the cache is 
// disabled the cached will still be updated with the newly generated
// image. Set also "USE_CACHE" below.
DEFINE("READ_CACHE",true);

// Determine if the error handler should be image based or purely
// text based. Image based makes it easier since the script will
// always return an image even in case of errors.
DEFINE("USE_IMAGE_ERROR_HANDLER",true);

// If the color palette is full should JpGraph try to allocate
// the closest match? If you plan on using background images or
// gradient fills it might be a good idea to enable this.
// If not you will otherwise get an error saying that the color palette is 
// exhausted. The drawback of using approximations is that the colors 
// might not be exactly what you specified. 
// Note1: This does only apply to paletted images, not truecolor 
// images since they don't have the limitations of maximum number
// of colors.
DEFINE("USE_APPROX_COLORS",true);

// Special unicode cyrillic language support
DEFINE("LANGUAGE_CYRILLIC",false);

// If you are setting this config to true the conversion
// will assume that the input text is windows 1251, if
// false it will assume koi8-r
DEFINE("CYRILLIC_FROM_WINDOWS",false);

// Should usage of deprecated functions and parameters give a fatal error?
// (Useful to check if code is future proof.)
DEFINE("ERR_DEPRECATED",true);

// Should the time taken to generate each picture be branded to the lower
// left in corner in each generated image? Useful for performace measurements
// generating graphs
DEFINE("BRAND_TIMING",false);

// What format should be used for the timing string?
DEFINE("BRAND_TIME_FORMAT","(%01.3fs)");

//------------------------------------------------------------------------
// The following constants should rarely have to be changed !
//------------------------------------------------------------------------

// What group should the cached file belong to
// (Set to "" will give the default group for the "PHP-user")
// Please note that the Apache user must be a member of the
// specified group since otherwise it is impossible for Apache
// to set the specified group.
DEFINE("CACHE_FILE_GROUP","wwwadmin");

// What permissions should the cached file have
// (Set to "" will give the default persmissions for the "PHP-user")
DEFINE("CACHE_FILE_MOD",0664);

// Decide if we should use the bresenham circle algorithm or the
// built in Arc(). Bresenham gives better visual apperance of circles 
// but is more CPU intensive and slower then the built in Arc() function
// in GD. Turned off by default for speed
DEFINE("USE_BRESENHAM",false);

// Special file name to indicate that we only want to calc
// the image map in the call to Graph::Stroke() used
// internally from the GetHTMLCSIM() method.
DEFINE("_CSIM_SPECIALFILE","_csim_special_");

// HTTP GET argument that is used with image map
// to indicate to the script to just generate the image
// and not the full CSIM HTML page.
DEFINE("_CSIM_DISPLAY","_jpg_csimd");

// Special filename for Graph::Stroke(). If this filename is given
// then the image will NOT be streamed to browser of file. Instead the
// Stroke call will return the handler for the created GD image.
DEFINE("_IMG_HANDLER","__handle");

// DON'T SET THIS FLAG YORSELF THIS IS ONLY FOR INTERNAL TESTING
// PURPOSES. ENABLING THIS FLAG WILL MAKE SOME OF YOUR SCRIPT 
// STOP WORKING
// Enable some extra debug information for CSIM etc to be shown. 
DEFINE("JPG_DEBUG",false);

// Version info
DEFINE('JPG_VERSION','1.12');

//------------------------------------------------------------------------
// Automatic settings of path for cache and font directory
// if they have not been previously specified
//------------------------------------------------------------------------
if (!defined('CACHE_DIR')) {
    if ( strstr( PHP_OS, 'WIN') ) {
        if( empty($_SERVER['TEMP']) ) {
	    die('JpGraph Error: No path specified for CACHE_DIR. Please specify a path for that DEFINE in jpgraph.php');
        }
	else {
	   DEFINE('CACHE_DIR', $_SERVER['TEMP'] . '/');
        }
    } else {
	DEFINE('CACHE_DIR','/tmp/jpgraph_cache/');
    }
}

if (!defined('TTF_DIR')) {
    if (strstr( PHP_OS, 'WIN') ) {
        if( empty($_SERVER['SystemRoot']) ) {
	    die('JpGraph Error: No path specified for TTF_DIR. Please specify a path for that DEFINE in jpgraph.php');
        }
	else {
	  DEFINE('TTF_DIR', $_SERVER['SystemRoot'] . '/fonts/');
        }
    } else {
	DEFINE('TTF_DIR','/usr/X11R6/lib/X11/fonts/truetype/');
    }
}

//------------------------------------------------------------------
// Constants which are used as parameters for the method calls
//------------------------------------------------------------------

// TTF Font families
DEFINE("FF_COURIER",10);
DEFINE("FF_VERDANA",11);
DEFINE("FF_TIMES",12);
DEFINE("FF_COMIC",14);
DEFINE("FF_ARIAL",15);
DEFINE("FF_GEORGIA",16);
DEFINE("FF_TREBUCHE",17);

// Chinese font
DEFINE("FF_SIMSUN",18);

// Older deprecated fonts 
DEFINE("FF_BOOK",91);    // Deprecated fonts from 1.9
DEFINE("FF_HANDWRT",92); // Deprecated fonts from 1.9

// TTF Font styles
DEFINE("FS_NORMAL",9001);
DEFINE("FS_BOLD",9002);
DEFINE("FS_ITALIC",9003);
DEFINE("FS_BOLDIT",9004);
DEFINE("FS_BOLDITALIC",9004);

//Definitions for internal font, new style
DEFINE("FF_FONT0",1);
DEFINE("FF_FONT1",2);
DEFINE("FF_FONT2",4);

//Definitions for internal font, old style
// (Only defined here to be able to generate an error mesage
// when used)
DEFINE("FONT0",99);		// Deprecated from 1.2
DEFINE("FONT1",98);		// Deprecated from 1.2
DEFINE("FONT1_BOLD",97);	// Deprecated from 1.2
DEFINE("FONT2",96);		// Deprecated from 1.2
DEFINE("FONT2_BOLD",95); 	// Deprecated from 1.2

// Tick density
DEFINE("TICKD_DENSE",1);
DEFINE("TICKD_NORMAL",2);
DEFINE("TICKD_SPARSE",3);
DEFINE("TICKD_VERYSPARSE",4);

// Side for ticks and labels. 
DEFINE("SIDE_LEFT",-1);
DEFINE("SIDE_RIGHT",1);
DEFINE("SIDE_DOWN",-1);
DEFINE("SIDE_BOTTOM",-1);
DEFINE("SIDE_UP",1);
DEFINE("SIDE_TOP",1);

// Legend type stacked vertical or horizontal
DEFINE("LEGEND_VERT",0);
DEFINE("LEGEND_HOR",1);

// Mark types for plot marks
DEFINE("MARK_SQUARE",1);
DEFINE("MARK_UTRIANGLE",2);
DEFINE("MARK_DTRIANGLE",3);
DEFINE("MARK_DIAMOND",4);
DEFINE("MARK_CIRCLE",5);
DEFINE("MARK_FILLEDCIRCLE",6);
DEFINE("MARK_CROSS",7);
DEFINE("MARK_STAR",8);
DEFINE("MARK_X",9);
DEFINE("MARK_LEFTTRIANGLE",10);
DEFINE("MARK_RIGHTTRIANGLE",11);
DEFINE("MARK_FLASH",12);
DEFINE("MARK_IMG",13);

// Builtin images
DEFINE("MARK_IMG_PUSHPIN",50);
DEFINE("MARK_IMG_SPUSHPIN",50);
DEFINE("MARK_IMG_LPUSHPIN",51);
DEFINE("MARK_IMG_DIAMOND",52);
DEFINE("MARK_IMG_SQUARE",53);
DEFINE("MARK_IMG_STAR",54);
DEFINE("MARK_IMG_BALL",55);
DEFINE("MARK_IMG_SBALL",55);
DEFINE("MARK_IMG_MBALL",56);
DEFINE("MARK_IMG_LBALL",57);
DEFINE("MARK_IMG_BEVEL",58);

// Styles for gradient color fill
DEFINE("GRAD_VER",1);
DEFINE("GRAD_VERT",1);
DEFINE("GRAD_HOR",2);
DEFINE("GRAD_MIDHOR",3);
DEFINE("GRAD_MIDVER",4);
DEFINE("GRAD_CENTER",5);
DEFINE("GRAD_WIDE_MIDVER",6);
DEFINE("GRAD_WIDE_MIDHOR",7);
DEFINE("GRAD_LEFT_REFLECTION",8);
DEFINE("GRAD_RIGHT_REFLECTION",9);

// Inline defines
DEFINE("INLINE_YES",1);
DEFINE("INLINE_NO",0);

// Format for background images
DEFINE("BGIMG_FILLPLOT",1);
DEFINE("BGIMG_FILLFRAME",2);
DEFINE("BGIMG_COPY",3);
DEFINE("BGIMG_CENTER",4);

// Depth of objects
DEFINE("DEPTH_BACK",0);
DEFINE("DEPTH_FRONT",1);

// Direction
DEFINE("VERTICAL",1);
DEFINE("HORIZONTAL",0);

// Constants for types of static bands in plot area
DEFINE("BAND_RDIAG",1);	// Right diagonal lines
DEFINE("BAND_LDIAG",2); // Left diagonal lines
DEFINE("BAND_SOLID",3); // Solid one color
DEFINE("BAND_VLINE",4); // Vertical lines
DEFINE("BAND_HLINE",5);  // Horizontal lines
DEFINE("BAND_3DPLANE",6);  // "3D" Plane
DEFINE("BAND_HVCROSS",7);  // Vertical/Hor crosses
DEFINE("BAND_DIAGCROSS",8); // Diagonal crosses

// Axis styles for scientific style axis
DEFINE('AXSTYLE_SIMPLE',1);
DEFINE('AXSTYLE_BOXIN',2);
DEFINE('AXSTYLE_BOXOUT',3);
DEFINE('AXSTYLE_YBOXIN',4);
DEFINE('AXSTYLE_YBOXOUT',5);

// Style for title backgrounds
DEFINE('TITLEBKG_STYLE1',1);
DEFINE('TITLEBKG_STYLE2',2);
DEFINE('TITLEBKG_STYLE3',3);
DEFINE('TITLEBKG_FRAME_NONE',0);
DEFINE('TITLEBKG_FRAME_FULL',1);
DEFINE('TITLEBKG_FRAME_BOTTOM',2);
DEFINE('TITLEBKG_FRAME_BEVEL',3);
DEFINE('TITLEBKG_FILLSTYLE_HSTRIPED',1);
DEFINE('TITLEBKG_FILLSTYLE_VSTRIPED',2);
DEFINE('TITLEBKG_FILLSTYLE_SOLID',3);

// Width of tab titles
DEFINE('TABTITLE_WIDTHFIT',0);
DEFINE('TABTITLE_WIDTHFULL',-1);

//
// Get hold of gradient class (In Version 2.x)
// A client of the library has to manually include this
//
include "jpgraph_gradient.php";

//
// First of all set up a default error handler
//

//=============================================================
// The default trivial text error handler.
//=============================================================
class JpGraphErrObject {
    function JpGraphErrObject() {
	// Empty. Reserved for future use
    }

    // If aHalt is true then execution can't continue. Typical used for
    // fatal errors
    function Raise($aMsg,$aHalt=true) {
	$aMsg = "<b>JpGraph Error:</b> ".$aMsg;
	if( $aHalt )
	    die($aMsg);
	else 
	    echo $aMsg."<p>";
    }
}

//==============================================================
// An image based error handler
//==============================================================
class JpGraphErrObjectImg {

    function Raise($aMsg,$aHalt=true) {
	if( headers_sent() ) {
	    // Special case for headers already sent error. Dont
	    // return an image since it can't be displayed
	    die("<b>JpGraph Error:</b> ".$aMsg);		
	}

	// Create an image that contains the error text.
	$w=450; $h=110;
	$img = new Image($w,$h);
	$img->SetColor("darkred");
	$img->Rectangle(0,0,$w-1,$h-1);
	$img->SetFont(FF_FONT1,FS_BOLD);
	$img->StrokeText(10,20,"JpGraph Error:");
	$img->SetColor("black");
	$img->SetFont(FF_FONT1,FS_NORMAL);
	$txt = new Text(wordwrap($aMsg,70),10,20);
	$txt->Align("left","top");
	$txt->Stroke($img);
	$img->Headers();
	$img->Stream();
	die();
    }
}

//
// A wrapper class that is used to access the specified error object
// (to hide the global error parameter and avoid having a GLOBAL directive
// in all methods.
//
class JpGraphError {
    function Install($aErrObject) {
	GLOBAL $__jpg_err;
	$__jpg_err = $aErrObject;
    }
    function Raise($aMsg,$aHalt=true){
	GLOBAL $__jpg_err;
	$tmp = new $__jpg_err;
	$tmp->Raise($aMsg,$aHalt);
    }
}

//
// ... and install the default error handler
//
if( USE_IMAGE_ERROR_HANDLER ) {
    JpGraphError::Install("JpGraphErrObjectImg");
}
else {
    JpGraphError::Install("JpGraphErrObject");
}


//
//Check if there were any warnings, perhaps some wrong includes by the
//user
//
if( isset($GLOBALS['php_errormsg']) ) {
    JpGraphError::Raise("<b>General PHP error:</b><br>".$GLOBALS['php_errormsg']);
}


//
// Routine to determine if GD1 or GD2 is installed
//
function CheckGDVersion() {
    ob_start();
    phpinfo(8); // Just get the modules loaded
    $a = ob_get_contents();
    ob_end_clean();
    if( preg_match('/.*GD Version.*(1\.).*/',$a,$m) ) {
	$r=1;$v=$m[1];
    }
    elseif( preg_match('/.*GD Version.*(2\.).*/',$a,$m) ) {
	$r=2;$v=$m[1];
    }
    else {
	$r=0;$v=$m[1];
    }
    return $r;
}

//
// Check what version of the GD library is installed.
//
if( USE_LIBRARY_GD2 === 'auto' ) {
    $gdversion = CheckGDVersion();
    if( $gdversion == 2 ) {
	$GLOBALS['gd2'] = true;
	$GLOBALS['copyfunc'] = 'imagecopyresampled';
    }
    elseif( $gdversion == 1 ) {
	$GLOBALS['gd2'] = false;
	$GLOBALS['copyfunc'] = 'imagecopyresized';
    }
    else {
	JpGraphError::Raise(" Your PHP installation does not seem to 
	have the required GD library.
	Please see the PHP documentation on how to install and enable the GD library.");
    }
}
else {
    $GLOBALS['gd2'] = USE_LIBRARY_GD2;
    $GLOBALS['copyfunc'] = USE_LIBRARY_GD2 ? 'imagecopyresampled' : 'imagecopyresized';
}

// Usefull mathematical function
function sign($a) {return $a >= 0 ? 1 : -1;}

// Utility function to generate an image name based on the filename we
// are running from and assuming we use auto detection of graphic format
// (top level), i.e it is safe to call this function
// from a script that uses JpGraph
function GenImgName() {
    global $HTTP_SERVER_VARS;
    $supported = imagetypes();
    if( $supported & IMG_PNG )
	$img_format="png";
    elseif( $supported & IMG_GIF )
	$img_format="gif";
    elseif( $supported & IMG_JPG )
	$img_format="jpeg";
    if( !isset($HTTP_SERVER_VARS['PHP_SELF']) )
	JpGraphError::Raise(" 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.");
    $fname=basename($HTTP_SERVER_VARS['PHP_SELF']);
    // Replace the ".php" extension with the image format extension
    return substr($fname,0,strlen($fname)-4).".".$img_format;
}

class LanguageConv {
    var $g2312 = null ;

    function Convert($aTxt,$aFF) {
	if( LANGUAGE_CYRILLIC ) {
	    if( CYRILLIC_FROM_WINDOWS ) {
		$aTxt = convert_cyr_string($aTxt, "w", "k"); 
	    }
	    $isostring = convert_cyr_string($aTxt, "k", "i");
	    $unistring = LanguageConv::iso2uni($isostring);
	    return $unistring;
	}
	elseif( $aFF === FF_SIMSUN ) {
	    // Do Chinese conversion
	    if( $this->g2312 == null ) {
		include_once 'jpgraph_gb2312.php' ;
		$this->g2312 = new GB2312toUTF8();
	    }
	    return $this->g2312->gb2utf8($aTxt);
	}
	else 
	    return $aTxt;
    }

    // Translate iso encoding to unicode
    function iso2uni ($isoline){
	for ($i=0; $i < strlen($isoline); $i++){
	    $thischar=substr($isoline,$i,1);
	    $charcode=ord($thischar);
	    $uniline.=($charcode>175) ? "&#" . (1040+($charcode-176)). ";" : $thischar;
	}
	return $uniline;
    }
}

//===================================================
// CLASS JpgTimer
// Description: General timing utility class to handle
// timne measurement of generating graphs. Multiple
// timers can be started by pushing new on a stack.
//===================================================
class JpgTimer {
    var $start;
    var $idx;	
//---------------
// CONSTRUCTOR
    function JpgTimer() {
	$this->idx=0;
    }

//---------------
// PUBLIC METHODS	

    // Push a new timer start on stack
    function Push() {
	list($ms,$s)=explode(" ",microtime());	
	$this->start[$this->idx++]=floor($ms*1000) + 1000*$s;	
    }

    // Pop the latest timer start and return the diff with the
    // current time
    function Pop() {
	assert($this->idx>0);
	list($ms,$s)=explode(" ",microtime());	
	$etime=floor($ms*1000) + (1000*$s);
	$this->idx--;
	return $etime-$this->start[$this->idx];
    }
} // Class

$gJpgBrandTiming = BRAND_TIMING;
//===================================================
// CLASS DateLocale
// Description: Hold localized text used in dates
// ToDOo: Rewrite this to use the real local locale
// instead.
//===================================================
class DateLocale {
 
    var $iLocale = 'C'; // environmental locale be used by default

    var $iDayAbb = null;
    var $iShortDay = null;
    var $iShortMonth = null;
    var $iMonthName = null;

//---------------
// CONSTRUCTOR	
    function DateLocale() {
	settype($this->iDayAbb, 'array');
	settype($this->iShortDay, 'array');
	settype($this->iShortMonth, 'array');
	settype($this->iMonthName, 'array');


	$this->Set('C');
    }

//---------------
// PUBLIC METHODS	
    function Set($aLocale) {
	if ( in_array($aLocale, array_keys($this->iDayAbb)) ){ 
	    $this->iLocale = $aLocale;
	    return TRUE;  // already cached nothing else to do!
	}

	$pLocale = setlocale(LC_TIME, 0); // get current locale for LC_TIME
	$res = setlocale(LC_TIME, $aLocale);
	if ( ! $res ){
	    JpGraphError::Raise("You are trying to use the locale ($aLocale) which your PHP installation does not support. Hint: Use '' to indicate the default locale for this geographic region.");
	    return FALSE;
	}
 
	$this->iLocale = $aLocale;

	for ( $i = 0, $ofs = 0 - strftime('%w'); $i < 7; $i++, $ofs++ ){
	    $day = strftime('%a', strtotime("$ofs day"));
	    $day{0} = strtoupper($day{0});
	    $this->iDayAbb[$aLocale][]= $day{0};
	    $this->iShortDay[$aLocale][]= $day;
	}

	for($i=1; $i<=12; ++$i) {
	    list($short ,$full) = explode('|', strftime("%b|%B",strtotime("2001-$i-01")));
	    $this->iShortMonth[$aLocale][] = ucfirst($short);
	    $this->iMonthName [$aLocale][] = ucfirst($full);
	}
	
	
	setlocale(LC_TIME, $pLocale);

	return TRUE;
    }


    function GetDayAbb() {
	return $this->iDayAbb[$this->iLocale];
    }
	
    function GetShortDay() {
	return $this->iShortDay[$this->iLocale];
    }

    function GetShortMonth() {
	return $this->iShortMonth[$this->iLocale];
    }
	
    function GetShortMonthName($aNbr) {
	return $this->iShortMonth[$this->iLocale][$aNbr];
    }

    function GetLongMonthName($aNbr) {
	return $this->iMonthName[$this->iLocale][$aNbr];
    }

    function GetMonth() {
	return $this->iMonthName[$this->iLocale];
    }
}

$gDateLocale = new DateLocale();
$gJpgDateLocale = new DateLocale();


//===================================================
// CLASS FuncGenerator
// Description: Utility class to help generate data for function plots. 
// The class supports both parametric and regular functions.
//===================================================
class FuncGenerator {
    var $iFunc='',$iXFunc='',$iMin,$iMax,$iStepSize;
	
    function FuncGenerator($aFunc,$aXFunc='') {
	$this->iFunc = $aFunc;
	$this->iXFunc = $aXFunc;
    }
	
    function E($aXMin,$aXMax,$aSteps=50) {
	$this->iMin = $aXMin;
	$this->iMax = $aXMax;
	$this->iStepSize = ($aXMax-$aXMin)/$aSteps;

	if( $this->iXFunc != '' )
	    $t = 'for($i='.$aXMin.'; $i<='.$aXMax.'; $i += '.$this->iStepSize.') {$ya[]='.$this->iFunc.';$xa[]='.$this->iXFunc.';}';
	elseif( $this->iFunc != '' )
	    $t = 'for($x='.$aXMin.'; $x<='.$aXMax.'; $x += '.$this->iStepSize.') {$ya[]='.$this->iFunc.';$xa[]=$x;} $x='.$aXMax.';$ya[]='.$this->iFunc.';$xa[]=$x;';
	else
	    JpGraphError::Raise('FuncGenerator : No function specified. ');
			
	@eval($t);
		
	// If there is an error in the function specifcation this is the only
	// way we can discover that.
	if( empty($xa) || empty($ya) )
	    JpGraphError::Raise('FuncGenerator : Syntax error in function specification ');
				
	return array($xa,$ya);
    }
}


//=======================================================
// CLASS Footer
// Description: Encapsulates the footer line in the Graph
//
//=======================================================
class Footer {
    var $left,$center,$right;
    var $iLeftMargin = 3;
    var $iRightMargin = 3;
    var $iBottomMargin = 3;

    function Footer() {
	$this->left = new Text();
	$this->left->ParagraphAlign('left');
	$this->center = new Text();
	$this->center->ParagraphAlign('center');
	$this->right = new Text();
	$this->right->ParagraphAlign('right');
    }

    function Stroke($aImg) {
	$y = $aImg->height - $this->iBottomMargin;
	$x = $this->iLeftMargin;
	$this->left->Align('left','bottom');
	$this->left->Stroke($aImg,$x,$y);

	$x = ($aImg->width - $this->iLeftMargin - $this->iRightMargin)/2;
	$this->center->Align('center','bottom');
	$this->center->Stroke($aImg,$x,$y);

	$x = $aImg->width - $this->iRightMargin;
	$this->right->Align('right','bottom');
	$this->right->Stroke($aImg,$x,$y);
    }
}

    DEFINE('BGRAD_FRAME',1);
    DEFINE('BGRAD_MARGIN',2);
    DEFINE('BGRAD_PLOT',3);



//===================================================
// CLASS Graph
// Description: Main class to handle graphs
//===================================================
class Graph {
    var $cache=null;		// Cache object (singleton)
    var $img=null;			// Img object (singleton)
    var $plots=array();	// Array of all plot object in the graph (for Y 1 axis)
    var $y2plots=array();// Array of all plot object in the graph (for Y 2 axis)
    var $xscale=null;		// X Scale object (could be instance of LinearScale or LogScale
    var $yscale=null,$y2scale=null;
    var $cache_name;		// File name to be used for the current graph in the cache directory
    var $xgrid=null;		// X Grid object (linear or logarithmic)
    var $ygrid=null,$y2grid=null; //dito for Y
    var $doframe=true,$frame_color=array(0,0,0), $frame_weight=1;	// Frame around graph
    var $boxed=false, $box_color=array(0,0,0), $box_weight=1;		// Box around plot area
    var $doshadow=false,$shadow_width=4,$shadow_color=array(102,102,102);	// Shadow for graph
    var $xaxis=null;		// X-axis (instane of Axis class)
    var $yaxis=null, $y2axis=null;	// Y axis (instance of Axis class)
    var $margin_color=array(200,200,200);	// Margin color of graph
    var $plotarea_color=array(255,255,255);	// Plot area color
    var $title,$subtitle,$subsubtitle; 	// Title and subtitle(s) text object
    var $axtype="linlin";	// Type of axis
    var $xtick_factor;	// Factot to determine the maximum number of ticks depending on the plot with
    var $texts=null;		// Text object to ge shown in the graph
    var $lines=null;
    var $bands=null;
    var $text_scale_off=0;	// Text scale offset in world coordinates
    var $background_image="",$background_image_type=-1,$background_image_format="png";
    var $background_image_bright=0,$background_image_contr=0,$background_image_sat=0;
    var $image_bright=0, $image_contr=0, $image_sat=0;
    var $inline;
    var $showcsim=0,$csimcolor="red"; //debug stuff, draw the csim boundaris on the image if <>0
    var $grid_depth=DEPTH_BACK;	// Draw grid under all plots as default
    var $iAxisStyle = AXSTYLE_SIMPLE;
    var $iCSIMdisplay=false,$iHasStroked = false;
    var $footer;
    var $csimcachename = '', $csimcachetimeout = 0;
    var $iDoClipping = false;
    var $y2orderback=true;
    var $tabtitle;
    var $bkg_gradtype=-1,$bkg_gradstyle=BGRAD_MARGIN;
    var $bkg_gradfrom='navy', $bkg_gradto='silver';
    var $titlebackground = false;
    var	$titlebackground_color = 'lightblue',
	$titlebackground_style = 1,
	$titlebackground_framecolor = 'blue',
	$titlebackground_framestyle = 2,
	$titlebackground_frameweight = 1,
	$titlebackground_bevelheight = 3 ;
    var $titlebkg_fillstyle=TITLEBKG_FILLSTYLE_SOLID;
    var $titlebkg_scolor1='black',$titlebkg_scolor2='white';
    var $framebevel = false, $framebeveldepth = 2 ;
    var $framebevelborder = false, $framebevelbordercolor='black';
    var $framebevelcolor1='hide@address.com', $framebevelcolor2='hide@address.com';

//---------------
// CONSTRUCTOR

    // aWIdth 		Width in pixels of image
    // aHeight  	Height in pixels of image
    // aCachedName	Name for image file in cache directory 
    // aTimeOut		Timeout in minutes for image in cache
    // aInline		If true the image is streamed back in the call to Stroke()
    //			If false the image is just created in the cache
    function Graph($aWidth=300,$aHeight=200,$aCachedName="",$aTimeOut=0,$aInline=true) {
	GLOBAL $gJpgBrandTiming;
	// If timing is used create a new timing object
	if( $gJpgBrandTiming ) {
	    global $tim;
	    $tim = new JpgTimer();
	    $tim->Push();
	}
		
	// Automatically generate the image file name based on the name of the script that
	// generates the graph
	if( $aCachedName=="auto" )
	    $aCachedName=GenImgName();
			
	// Should the image be streamed back to the browser or only to the cache?
	$this->inline=$aInline;
		
	$this->img	= new RotImage($aWidth,$aHeight);

	$this->cache 	= new ImgStreamCache($this->img);
	$this->cache->SetTimeOut($aTimeOut);

	$this->title = new Text();
	$this->title->ParagraphAlign('center');
	$this->title->SetFont(FF_FONT2,FS_BOLD);
	$this->title->SetMargin(3);

	$this->subtitle = new Text();
	$this->subtitle->ParagraphAlign('center');

	$this->subsubtitle = new Text();
	$this->subsubtitle->ParagraphAlign('center');

	$this->legend = new Legend();
	$this->footer = new Footer();

	// If the cached version exist just read it directly from the
	// cache, stream it back to browser and exit
	if( $aCachedName!="" && READ_CACHE && $aInline )
	    if( $this->cache->GetAndStream($aCachedName) ) {
		exit();
	    }
				
	$this->cache_name = $aCachedName;
	$this->SetTickDensity(); // Normal density

	$this->tabtitle = new GraphTabTitle();
    }
//---------------
// PUBLIC METHODS	

    // Should the grid be in front or back of the plot?
    function SetGridDepth($aDepth) {
	$this->grid_depth=$aDepth;
    }
	
    // Specify graph angle 0-360 degrees.
    function SetAngle($aAngle) {
	$this->img->SetAngle($aAngle);
    }

    function SetAlphaBlending($aFlg=true) {
	$this->img->SetAlphaBlending($aFlg);
    }

    // Shortcut to image margin
    function SetMargin($lm,$rm,$tm,$bm) {
	$this->img->SetMargin($lm,$rm,$tm,$bm);
    }

    function SetY2OrderBack($aBack=true) {
	$this->y2orderback = $aBack;
    }

    // Rotate the graph 90 degrees and set the margin 
    // when we have done a 90 degree rotation
    function Set90AndMargin($lm=0,$rm=0,$tm=0,$bm=0) {
	$lm = $lm ==0 ? floor(0.2 * $this->img->width)  : $lm ;
	$rm = $rm ==0 ? floor(0.1 * $this->img->width)  : $rm ;
	$tm = $tm ==0 ? floor(0.2 * $this->img->height) : $tm ;
	$bm = $bm ==0 ? floor(0.1 * $this->img->height) : $bm ;

	$adj = ($this->img->height - $this->img->width)/2;
	$this->img->SetMargin($tm-$adj,$bm-$adj,$rm+$adj,$lm+$adj);
	$this->img->SetCenter(floor($this->img->width/2),floor($this->img->height/2));
	$this->SetAngle(90);
	$this->xaxis->SetLabelAlign('right','center');
	$this->yaxis->SetLabelAlign('center','bottom');
    }
	
    function SetClipping($aFlg=true) {
	$this->iDoClipping = $aFlg ;
    }

    // Add a plot object to the graph
    function Add(&$aPlot) {
	if( $aPlot == null )
	    JpGraphError::Raise("<b></b> Graph::Add() You tried to add a null plot to the graph.");
	if( is_array($aPlot) && count($aPlot) > 0 )
	    $cl = get_class($aPlot[0]);
	else
	    $cl = get_class($aPlot);

	if( $cl == 'text' ) 
	    $this->AddText($aPlot);
	elseif( $cl == 'plotline' )
	    $this->AddLine($aPlot);
	elseif( $cl == 'plotband' )
	    $this->AddBand($aPlot);
	else
	    $this->plots[] = &$aPlot;
    }

    // Add plot to second Y-scale
    function AddY2(&$aPlot) {
	if( $aPlot == null )
	    JpGraphError::Raise("<b></b> Graph::AddY2() You tried to add a null plot to the graph.");	
	$this->y2plots[] = &$aPlot;
    }
	
    // Add text object to the graph
    function AddText(&$aTxt) {
	if( $aTxt == null )
	    JpGraphError::Raise("<b></b> Graph::AddText() You tried to add a null text to the graph.");		
	if( is_array($aTxt) ) {
	    for($i=0; $i < count($aTxt); ++$i )
		$this->texts[]=&$aTxt[$i];
	}
	else
	    $this->texts[] = &$aTxt;
    }
	
    // Add a line object (class PlotLine) to the graph
    function AddLine(&$aLine) {
	if( $aLine == null )
	    JpGraphError::Raise("<b></b> Graph::AddLine() You tried to add a null line to the graph.");		
	if( is_array($aLine) ) {
	    for($i=0; $i<count($aLine); ++$i )
		$this->lines[]=&$aLine[$i];
	}
	else
	    $this->lines[] = &$aLine;
    }

    // Add vertical or horizontal band
    function AddBand(&$aBand) {
	if( $aBand == null )
	    JpGraphError::Raise(" Graph::AddBand() You tried to add a null band to the graph.");
	if( is_array($aBand) ) {
	    for($i=0; $i<count($aBand); ++$i )
		$this->bands[] = &$aBand[$i];
	}
	else
	    $this->bands[] = &$aBand;
    }

    function SetBackgroundGradient($aFrom='navy',$aTo='silver',$aGradType=GRAD_HOR,$aStyle=BGRAD_FRAME) {
	$this->bkg_gradtype=$aGradType;
	$this->bkg_gradstyle=$aStyle;
	$this->bkg_gradfrom = $aFrom;
	$this->bkg_gradto = $aTo;
    } 
	
    // Specify a background image
    function SetBackgroundImage($aFileName,$aBgType=BGIMG_FILLPLOT,$aImgFormat="auto") {

	if( $GLOBALS['gd2'] && !USE_TRUECOLOR ) {
	    JpGraphError::Raise("You are using GD 2.x and are trying to use a background images on a non truecolor image. To use background images with GD 2.x you <b>must</b> enable truecolor by setting the USE_TRUECOLOR constant to TRUE. Due to a bug in GD 2.0.1 using any truetype fonts with truecolor images will result in very poor quality fonts.");
	}

	// Get extension to determine image type
	if( $aImgFormat == "auto" ) {
	    $e = explode('.',$aFileName);
	    if( !$e ) {
		JpGraphError::Raise('Incorrect file name for Graph::SetBackgroundImage() : '.$aFileName.' Must have a valid image extension (jpg,gif,png) when using autodetection of image type');
	    }

	    $valid_formats = array('png', 'jpg', 'gif');
	    $aImgFormat = strtolower($e[count($e)-1]);
	    if ($aImgFormat == 'jpeg')  {
		$aImgFormat = 'jpg';
	    }
	    elseif (!in_array($aImgFormat, $valid_formats) )  {
		JpGraphError::Raise('Unknown file extension ($aImgFormat) in Graph::SetBackgroundImage() for filename: '.$aFileName);
	    }    
	}

	$this->background_image = $aFileName;
	$this->background_image_type=$aBgType;
	$this->background_image_format=$aImgFormat;
    }
	
    // Adjust brightness and constrast for background image
    function AdjBackgroundImage($aBright,$aContr=0,$aSat=0) {
	$this->background_image_bright=$aBright;
	$this->background_image_contr=$aContr;
	$this->background_image_sat=$aSat;
    }
	
    // Adjust brightness and constrast for image
    function AdjImage($aBright,$aContr=0,$aSat=0) {
	$this->image_bright=$aBright;
	$this->image_contr=$aContr;
	$this->image_sat=$aSat;
    }

    // Specify axis style (boxed or single)
    function SetAxisStyle($aStyle) {
        $this->iAxisStyle = $aStyle ;
    }
	
    // Set a frame around the plot area
    function SetBox($aDrawPlotFrame=true,$aPlotFrameColor=array(0,0,0),$aPlotFrameWeight=1) {
	$this->boxed = $aDrawPlotFrame;
	$this->box_weight = $aPlotFrameWeight;
	$this->box_color = $aPlotFrameColor;
    }
	
    // Specify color for the plotarea (not the margins)
    function SetColor($aColor) {
	$this->plotarea_color=$aColor;
    }
	
    // Specify color for the margins (all areas outside the plotarea)
    function SetMarginColor($aColor) {
	$this->margin_color=$aColor;
    }
	
    // Set a frame around the entire image
    function SetFrame($aDrawImgFrame=true,$aImgFrameColor=array(0,0,0),$aImgFrameWeight=1) {
	$this->doframe = $aDrawImgFrame;
	$this->frame_color = $aImgFrameColor;
	$this->frame_weight = $aImgFrameWeight;
    }

    function SetFrameBevel($aDepth=3,$aBorder=false,$aBorderColor='black',$aColor1='hide@address.com',$aColor2='hide@address.com',$aFlg=true) {
	$this->framebevel = $aFlg ;
	$this->framebeveldepth = $aDepth ;
	$this->framebevelborder = $aBorder ;
	$this->framebevelbordercolor = $aBorderColor ;
	$this->framebevelcolor1 = $aColor1 ;
	$this->framebevelcolor2 = $aColor2 ;

	$this->doshadow = false ;
    }

    // Set the shadow around the whole image
    function SetShadow($aShowShadow=true,$aShadowWidth=5,$aShadowColor=array(102,102,102)) {
	$this->doshadow = $aShowShadow;
	$this->shadow_color = $aShadowColor;
	$this->shadow_width = $aShadowWidth;
	$this->footer->iBottomMargin += $aShadowWidth;
	$this->footer->iRightMargin += $aShadowWidth;
    }

    // Specify x,y scale. Note that if you manually specify the scale
    // you must also specify the tick distance with a call to Ticks::Set()
    function SetScale($aAxisType,$aYMin=1,$aYMax=1,$aXMin=1,$aXMax=1) {
	$this->axtype = $aAxisType;

	if( $aYMax < $aYMin || $aXMax < $aXMin )
	    JpGraphError::Raise('Graph::SetScale(): Specified Max value must be larger than the specified Min value.');

	$yt=substr($aAxisType,-3,3);
	if( $yt=="lin" )
	    $this->yscale = new LinearScale($aYMin,$aYMax);
	elseif( $yt == "int" ) {
	    $this->yscale = new LinearScale($aYMin,$aYMax);
	    $this->yscale->SetIntScale();
	}
	elseif( $yt=="log" )
	    $this->yscale = new LogScale($aYMin,$aYMax);
	else
	    JpGraphError::Raise("Unknown scale specification for Y-scale. ($aAxisType)");
			
	$xt=substr($aAxisType,0,3);
	if( $xt == "lin" || $xt == "tex" ) {
	    $this->xscale = new LinearScale($aXMin,$aXMax,"x");
	    $this->xscale->textscale = ($xt == "tex");
	}
	elseif( $xt == "int" ) {
	    $this->xscale = new LinearScale($aXMin,$aXMax,"x");
	    $this->xscale->SetIntScale();
	}
	elseif( $xt == "log" )
	    $this->xscale = new LogScale($aXMin,$aXMax,"x");
	else
	    JpGraphError::Raise(" Unknown scale specification for X-scale. ($aAxisType)");

	$this->xscale->Init($this->img);
	$this->yscale->Init($this->img);						
					
	$this->xaxis = new Axis($this->img,$this->xscale);
	$this->yaxis = new Axis($this->img,$this->yscale);
	$this->xgrid = new Grid($this->xaxis);
	$this->ygrid = new Grid($this->yaxis);	
	$this->ygrid->Show();			
    }
	
    // Specify secondary Y scale
    function SetY2Scale($aAxisType="lin",$aY2Min=1,$aY2Max=1) {
	if( $aAxisType=="lin" ) 
	    $this->y2scale = new LinearScale($aY2Min,$aY2Max);
	elseif( $aAxisType == "int" ) {
	    $this->y2scale = new LinearScale($aY2Min,$aY2Max);
	    $this->y2scale->SetIntScale();
	}
	elseif( $aAxisType=="log" ) {
	    $this->y2scale = new LogScale($aY2Min,$aY2Max);
	}
	else JpGraphError::Raise("JpGraph: Unsupported Y2 axis type: $axtype<br>");
			
	$this->y2scale->Init($this->img);	
	$this->y2axis = new Axis($this->img,$this->y2scale);
	$this->y2axis->scale->ticks->SetDirection(SIDE_LEFT); 
	$this->y2axis->SetLabelSide(SIDE_RIGHT); 
		
	// Deafult position is the max x-value
	$this->y2grid = new Grid($this->y2axis);							
    }
	
    // Specify density of ticks when autoscaling 'normal', 'dense', 'sparse', 'verysparse'
    // The dividing factor have been determined heuristically according to my aesthetic 
    // sense (or lack off) y.m.m.v !
    function SetTickDensity($aYDensity=TICKD_NORMAL,$aXDensity=TICKD_NORMAL) {
	$this->xtick_factor=30;
	$this->ytick_factor=25;		
	switch( $aYDensity ) {
	    case TICKD_DENSE:
		$this->ytick_factor=12;			
		break;
	    case TICKD_NORMAL:
		$this->ytick_factor=25;			
		break;
	    case TICKD_SPARSE:
		$this->ytick_factor=40;			
		break;
	    case TICKD_VERYSPARSE:
		$this->ytick_factor=100;			
		break;		
	    default:
		JpGraphError::Raise("JpGraph: Unsupported Tick density: $densy");
	}
	switch( $aXDensity ) {
	    case TICKD_DENSE:
		$this->xtick_factor=15;							
		break;
	    case TICKD_NORMAL:
		$this->xtick_factor=30;			
		break;
	    case TICKD_SPARSE:
		$this->xtick_factor=45;					
		break;
	    case TICKD_VERYSPARSE:
		$this->xtick_factor=60;								
		break;		
	    default:
		JpGraphError::Raise("JpGraph: Unsupported Tick density: $densx");
	}		
    }
	

    // Get a string of all image map areas	
    function GetCSIMareas() {
	if( !$this->iHasStroked )
	    $this->Stroke(_CSIM_SPECIALFILE);
	$csim=$this->legend->GetCSIMAreas();

	$n = count($this->plots);
	for( $i=0; $i<$n; ++$i ) 
	    $csim .= $this->plots[$i]->GetCSIMareas();

	$n = count($this->y2plots);
	for( $i=0; $i<$n; ++$i ) 
	    $csim .= $this->y2plots[$i]->GetCSIMareas();

	return $csim;
    }
	
    // Get a complete <MAP>..</MAP> tag for the final image map
    function GetHTMLImageMap($aMapName) {
	$im = "<MAP NAME=\"$aMapName\">\n";
	$im .= $this->GetCSIMareas();
	$im .= "</MAP>"; 
	return $im;
    }

    function CheckCSIMCache($aCacheName,$aTimeOut=60) {
	global $HTTP_SERVER_VARS;

	if( $aCacheName=='auto' )
	    $aCacheName=basename($HTTP_SERVER_VARS['PHP_SELF']);

	$this->csimcachename = CSIMCACHE_DIR.$aCacheName;
	$this->csimcachetimeout = $aTimeOut;

	// First determine if we need to check for a cached version
	// This differs from the standard cache in the sense that the
	// image and CSIM map HTML file is written relative to the directory
	// the script executes in and not the specified cache directory.
	// The reason for this is that the cache directory is not necessarily
	// accessible from the HTTP server.
	if( $this->csimcachename != '' ) {
	    $dir = dirname($this->csimcachename);
	    $base = basename($this->csimcachename);
	    $base = strtok($base,'.');
	    $suffix = strtok('.');
	    $basecsim = $dir.'/'.$base.'_csim_.html';
	    $baseimg = $dir.'/'.$base.'.'.$this->img->img_format;

	    $timedout=false;
		
	    // Does it exist at all ?
	    
	    if( file_exists($basecsim) && file_exists($baseimg) ) {
		// Check that it hasn't timed out
		$diff=time()-filemtime($basecsim);
		if( $this->csimcachetimeout>0 && ($diff > $this->csimcachetimeout*60) ) {
		    $timedout=true;
		    @unlink($basecsim);
		    @unlink($baseimg);
		}
		else {
		    if ($fh = @fopen($basecsim, "r")) {
			fpassthru($fh);
			exit();
		    }
		    else
			JpGraphError::Raise(" Can't open cached CSIM \"$basecsim\" for reading.");
		}
	    }
	}
	return false;
    }

    function StrokeCSIM($aScriptName='',$aCSIMName='',$aBorder=0) {
	GLOBAL $HTTP_GET_VARS;

	if( $aCSIMName=='' ) {
	    // create a random map name
	    srand ((double) microtime() * 1000000);
	    $r = rand(0,100000);
	    $aCSIMName='__mapname'.$r.'__';
	}
	if( empty($HTTP_GET_VARS[_CSIM_DISPLAY]) ) {
	    // First determine if we need to check for a cached version
	    // This differs from the standard cache in the sense that the
	    // image and CSIM map HTML file is written relative to the directory
	    // the script executes in and not the specified cache directory.
	    // The reason for this is that the cache directory is not necessarily
	    // accessible from the HTTP server.
	    if( $this->csimcachename != '' ) {
		$dir = dirname($this->csimcachename);
		$base = basename($this->csimcachename);
		$base = strtok($base,'.');
		$suffix = strtok('.');
		$basecsim = $dir.'/'.$base.'_csim_.html';
		$baseimg = $base.'.'.$this->img->img_format;

		// Check that apache can write to directory specified

		if( file_exists($dir) && !is_writeable($dir) ) {
		    JpgraphError::Raise('Apache/PHP does not have permission to write to the CSIM cache directory ('.$dir.'). Check permissions.');
		}
		
		// Make sure directory exists
		$this->cache->MakeDirs($dir);

		// Write the image file
		$this->Stroke(CSIMCACHE_DIR.$baseimg);

		// Construct wrapper HTML and write to file and send it back to browser
		$htmlwrap = $this->GetHTMLImageMap($aCSIMName)."\n".
		    '<img src="'.CSIMCACHE_HTTP_DIR.$baseimg.'" ISMAP USEMAP="#'.$aCSIMName.'" border='.$aBorder.'>'."\n";
		if($fh =  @fopen($basecsim,'w') ) {
		    fwrite($fh,$htmlwrap);
		    fclose($fh);
		    echo $htmlwrap;
		}
		else
		    JpGraphError::Raise(" Can't write CSIM \"$basecsim\" for writing. Check free space and permissions.");
	    }
	    else {

		if( $aScriptName=='' ) {
		    JpGraphError::Raise('Missing script name in call to StrokeCSIM(). You must specify the name of the actual image script as the first parameter to StrokeCSIM().');
		    exit();
		}

		// Construct the HTML wrapper page
		// Get all user defined URL arguments
		reset($HTTP_GET_VARS);
		
		// This is a JPGRAPH internal defined that prevents
		// us from recursively coming here again
		$urlarg='?'._CSIM_DISPLAY.'=1';

		while( list($key,$value) = each($HTTP_GET_VARS) ) {
		    if( is_array($value) ) {
			$n = count($value);
			for( $i=0; $i < $n; ++$i ) {
			    $urlarg .= '&'.$key.'%5B%5D='.urlencode($value[$i]);
			}
		    }
		    else {
			$urlarg .= '&'.$key.'='.urlencode($value);
		    }
		}
		
		echo $this->GetHTMLImageMap($aCSIMName);

		echo "<img src='".$aScriptName.$urlarg."' ISMAP USEMAP='#".$aCSIMName."' border=$aBorder>";
	    }
	}
	else {
	    $this->Stroke();
	}
    }

    function GetTextsYMinMax() {
	$n = count($this->texts);
	$min=null;
	$max=null;
	for( $i=0; $i < $n; ++$i ) {
	    if( $this->texts[$i]->iScalePosY !== null && 
		$this->texts[$i]->iScalePosX !== null  ) {
		if( $min === null  ) {
		    $min = $max = $this->texts[$i]->iScalePosY ;
		}
		else {
		    $min = min($min,$this->texts[$i]->iScalePosY);
		    $max = max($max,$this->texts[$i]->iScalePosY);
		}
	    }
	}
	if( $min !== null ) {
	    return array($min,$max);
	}
	else
	    return null;
    }

    function GetTextsXMinMax() {
	$n = count($this->texts);
	$min=null;
	$max=null;
	for( $i=0; $i < $n; ++$i ) {
	    if( $this->texts[$i]->iScalePosY !== null && 
		$this->texts[$i]->iScalePosX !== null  ) {
		if( $min === null  ) {
		    $min = $max = $this->texts[$i]->iScalePosX ;
		}
		else {
		    $min = min($min,$this->texts[$i]->iScalePosX);
		    $max = max($max,$this->texts[$i]->iScalePosX);
		}
	    }
	}
	if( $min !== null ) {
	    return array($min,$max);
	}
	else
	    return null;
    }



    function GetXMinMax() {
	list($min,$ymin) = $this->plots[0]->Min();
	list($max,$ymax) = $this->plots[0]->Max();
	foreach( $this->plots as $p ) {
	    list($xmin,$ymin) = $p->Min();
	    list($xmax,$ymax) = $p->Max();			
	    $min = Min($xmin,$min);
	    $max = Max($xmax,$max);
	}
	if( $this->y2axis != null ) {
	    foreach( $this->y2plots as $p ) {
		list($xmin,$ymin) = $p->Min();
			list($xmax,$ymax) = $p->Max();			
			$min = Min($xmin,$min);
			$max = Max($xmax,$max);
	    }		    
	}
	return array($min,$max);
    }

    function AdjustMarginsForTitles() {
	$totrequired = 
	    ($this->title->t != '' ? 
	     $this->title->GetTextHeight($this->img) + $this->title->margin + 5 : 0 ) +
	    ($this->subtitle->t != '' ? 
	     $this->subtitle->GetTextHeight($this->img) + $this->subtitle->margin + 5 : 0 ) + 
	    ($this->subsubtitle->t != '' ? 
	     $this->subsubtitle->GetTextHeight($this->img) + $this->subsubtitle->margin + 5 : 0 ) ;
	

	$btotrequired = 0;
	if( !$this->xaxis->hide && !$this->xaxis->hide_labels ) {
	    // Minimum bottom margin
	    if( $this->xaxis->title->t != '' ) {
		if( $this->img->a == 90 ) 
		    $btotrequired = $this->yaxis->title->GetTextHeight($this->img) + 5 ;
		else
		    $btotrequired = $this->xaxis->title->GetTextHeight($this->img) + 5 ;
	    }
	    else
		$btotrequired = 0;
	    
	    if( $this->img->a == 90 ) {
		$this->img->SetFont($this->yaxis->font_family,$this->yaxis->font_style,
				    $this->yaxis->font_size);
		$lh = $this->img->GetTextHeight('Mg',$this->yaxis->label_angle);
	    }
	    else {
		$this->img->SetFont($this->xaxis->font_family,$this->xaxis->font_style,
				    $this->xaxis->font_size);
		$lh = $this->img->GetTextHeight('Mg',$this->xaxis->label_angle);
	    }
	    
	    $btotrequired += $lh + 5;
	}

	if( $this->img->a == 90 ) {
	    // DO Nothing. It gets too messy to do this properly for 90 deg...
	}
	else{
	    if( $this->img->top_margin < $totrequired ) {
		$this->SetMargin($this->img->left_margin,$this->img->right_margin,
				 $totrequired,$this->img->bottom_margin);
	    }
	    if( $this->img->bottom_margin < $btotrequired ) {
		$this->SetMargin($this->img->left_margin,$this->img->right_margin,
				 $this->img->top_margin,$btotrequired);
	    }
	}
    }

    // Stroke the graph
    // $aStrokeFileName	If != "" the image will be written to this file and NOT
    // streamed back to the browser
    function Stroke($aStrokeFileName="") {		

	// Start by adjusting the margin so that potential titles will fit.
	$this->AdjustMarginsForTitles();

	// If the filename is the predefined value = '_csim_special_'
	// we assume that the call to stroke only needs to do enough
	// to correctly generate the CSIM maps.
	// We use this variable to skip things we don't strictly need
	// to do to generate the image map to improve performance
	// a best we can. Therefor you will see a lot of tests !$_csim in the
	// code below.
	$_csim = ($aStrokeFileName===_CSIM_SPECIALFILE);

	// We need to know if we have stroked the plot in the
	// GetCSIMareas. Otherwise the CSIM hasn't been generated
	// and in the case of GetCSIM called before stroke to generate
	// CSIM without storing an image to disk GetCSIM must call Stroke.
	$this->iHasStroked = true;

	// Do any pre-stroke adjustment that is needed by the different plot types
	// (i.e bar plots want's to add an offset to the x-labels etc)
	for($i=0; $i<count($this->plots) ; ++$i ) {
	    $this->plots[$i]->PreStrokeAdjust($this);
	    $this->plots[$i]->DoLegend($this);
	}
		
	// Any plots on the second Y scale?
	if( $this->y2scale != null ) {
	    for($i=0; $i<count($this->y2plots)	; ++$i ) {
		$this->y2plots[$i]->PreStrokeAdjust($this);
		$this->y2plots[$i]->DoLegend($this);
	    }
	}
		
	// Bail out if any of the Y-axis not been specified and
	// has no plots. (This means it is impossible to do autoscaling and
	// no other scale was given so we can't possible draw anything). If you use manual
	// scaling you also have to supply the tick steps as well.
	if( (!$this->yscale->IsSpecified() && count($this->plots)==0) ||
	    ($this->y2scale!=null && !$this->y2scale->IsSpecified() && count($this->y2plots)==0) ) {
	    $e = "Can't draw unspecified Y-scale.<br>\nYou have either:<br>\n";
	    $e .= "1. Specified an Y axis for autoscaling but have not supplied any plots<br>\n";
	    $e .= "2. Specified a scale manually but have forgot to specify the tick steps";
	    JpGraphError::Raise($e);
	}
		
	// Bail out if no plots and no specified X-scale
	if( (!$this->xscale->IsSpecified() && count($this->plots)==0 && count($this->y2plots)==0) )
	    JpGraphError::Raise("<strong>JpGraph: Can't draw unspecified X-scale.</strong><br>No plots.<br>");

	//Check if we should autoscale y-axis
	if( !$this->yscale->IsSpecified() && count($this->plots)>0 ) {
	    list($min,$max) = $this->GetPlotsYMinMax($this->plots);
 	    $lres = $this->GetLinesYMinMax($this->lines);
	    if( $lres ) {
		list($linmin,$linmax) = $lres ;
		$min = min($min,$linmin);
		$max = max($max,$linmax);
	    }
	    $tres = $this->GetTextsYMinMax();
	    if( $tres ) {
		list($tmin,$tmax) = $tres ;
		$min = min($min,$tmin);
		$max = max($max,$tmax);
	    }
	    $this->yscale->AutoScale($this->img,$min,$max,
				     $this->img->plotheight/$this->ytick_factor);
	}
	elseif( $this->yscale->IsSpecified() && 
		( $this->yscale->auto_ticks || !$this->yscale->ticks->IsSpecified()) ) {
	    // The tick calculation will use the user suplied min/max values to determine
	    // the ticks. If auto_ticks is false the exact user specifed min and max
	    // values will be used for the scale. 
	    // If auto_ticks is true then the scale might be slightly adjusted
	    // so that the min and max values falls on an even major step.
	    $min = $this->yscale->scale[0];
	    $max = $this->yscale->scale[1];
	    $this->yscale->AutoScale($this->img,$min,$max,
				     $this->img->plotheight/$this->ytick_factor,
				     $this->yscale->auto_ticks);
	}

	if( $this->y2scale != null) {
	    if( !$this->y2scale->IsSpecified() && count($this->y2plots)>0 ) {
		list($min,$max) = $this->GetPlotsYMinMax($this->y2plots);
		$this->y2scale->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor);
	    }			
	    elseif( $this->y2scale->IsSpecified() && 
		    ( $this->y2scale->auto_ticks || !$this->y2scale->ticks->IsSpecified()) ) {
		// The tick calculation will use the user suplied min/max values to determine
		// the ticks. If auto_ticks is false the exact user specifed min and max
		// values will be used for the scale. 
		// If auto_ticks is true then the scale might be slightly adjusted
		// so that the min and max values falls on an even major step.
		$min = $this->y2scale->scale[0];
		$max = $this->y2scale->scale[1];
		$this->y2scale->AutoScale($this->img,$min,$max,
					  $this->img->plotheight/$this->ytick_factor,
					  $this->y2scale->auto_ticks);
	    }
	}
				
	//Check if we should autoscale x-axis
	if( !$this->xscale->IsSpecified() ) {
	    if( substr($this->axtype,0,4) == "text" ) {
		$max=0;
		foreach( $this->plots as $p ) {
		    $max=max($max,$p->numpoints-1);
		}
		$min=0;
		if( $this->y2axis != null ) {
		    foreach( $this->y2plots as $p ) {
			$max=max($max,$p->numpoints-1);
		    }		    
		}
		$this->xscale->Update($this->img,$min,$max);
		$this->xscale->ticks->Set($this->xaxis->tick_step,1);
		$this->xscale->ticks->SupressMinorTickMarks();
	    }
	    else {
		list($min,$max) = $this->GetXMinMax();
		$lres = $this->GetLinesXMinMax($this->lines);
		if( $lres ) {
		    list($linmin,$linmax) = $lres ;
		    $min = min($min,$linmin);
		    $max = max($max,$linmax);
		}
		$tres = $this->GetTextsXMinMax();
		if( $tres ) {
		    list($tmin,$tmax) = $tres ;
		    $min = min($min,$tmin);
		    $max = max($max,$tmax);
		}
		$this->xscale->AutoScale($this->img,$min,$max,$this->img->plotwidth/$this->xtick_factor);
	    }
			
	    //Adjust position of y-axis and y2-axis to minimum/maximum of x-scale
	    if( !is_numeric($this->yaxis->pos) && !is_string($this->yaxis->pos) )
	    	$this->yaxis->SetPos($this->xscale->GetMinVal());
	    if( $this->y2axis != null ) {
		if( !is_numeric($this->y2axis->pos) && !is_string($this->y2axis->pos) )
		    $this->y2axis->SetPos($this->xscale->GetMaxVal());
		$this->y2axis->SetTitleSide(SIDE_RIGHT);
	    }
	}	
	elseif( $this->xscale->IsSpecified() &&  
		( $this->xscale->auto_ticks || !$this->xscale->ticks->IsSpecified()) ) {
	    // The tick calculation will use the user suplied min/max values to determine
	    // the ticks. If auto_ticks is false the exact user specifed min and max
	    // values will be used for the scale. 
	    // If auto_ticks is true then the scale might be slightly adjusted
	    // so that the min and max values falls on an even major step.
	    $min = $this->xscale->scale[0];
	    $max = $this->xscale->scale[1];
	    $this->xscale->AutoScale($this->img,$min,$max,
				     $this->img->plotwidth/$this->xtick_factor,
				     false);

	    if( $this->y2axis != null ) {
		if( !is_numeric($this->y2axis->pos) && !is_string($this->y2axis->pos) )
		    $this->y2axis->SetPos($this->xscale->GetMaxVal());
		$this->y2axis->SetTitleSide(SIDE_RIGHT);
	    }

	}
		
	// If we have a negative values and x-axis position is at 0
	// we need to supress the first and possible the last tick since
	// they will be drawn on top of the y-axis (and possible y2 axis)
	// The test below might seem strange the reasone being that if
	// the user hasn't specified a value for position this will not
	// be set until we do the stroke for the axis so as of now it
	// is undefined.
	// For X-text scale we ignore all this since the tick are usually
	// much further in and not close to the Y-axis. Hence the test 
	// for 'text'	

	if( ($this->yaxis->pos==$this->xscale->GetMinVal() || 
	     (is_string($this->yaxis->pos) && $this->yaxis->pos=='min')) &&  
	    !is_numeric($this->xaxis->pos) && $this->yscale->GetMinVal() < 0 && 
	    substr($this->axtype,0,4) != 'text' && $this->xaxis->pos!="min" ) {

	    //$this->yscale->ticks->SupressZeroLabel(false);
	    $this->xscale->ticks->SupressFirst();
	    if( $this->y2axis != null ) {
		$this->xscale->ticks->SupressLast();
	    }
	}
	elseif( !is_numeric($this->yaxis->pos) && $this->yaxis->pos=='max' ) {
	    $this->xscale->ticks->SupressLast();
	}
	

	if( !$_csim ) {
	    $this->StrokePlotArea();
	    $this->StrokeAxis();
	}

	// Stroke bands
	if( $this->bands != null && !$_csim) 
	    for($i=0; $i<count($this->bands); ++$i) {
		// Stroke all bands that asks to be in the background
		if( $this->bands[$i]->depth == DEPTH_BACK )
		    $this->bands[$i]->Stroke($this->img,$this->xscale,$this->yscale);
	    }

	if( $this->grid_depth == DEPTH_BACK && !$_csim) {
	    $this->ygrid->Stroke();
	    $this->xgrid->Stroke();
	}
				
	// Stroke Y2-axis
	if( $this->y2axis != null && !$_csim) {		
	    $this->y2axis->Stroke($this->xscale); 				
	    $this->y2grid->Stroke();
	}
		
	$oldoff=$this->xscale->off;
	if(substr($this->axtype,0,4)=="text") {
	    $this->xscale->off += 
		ceil($this->xscale->scale_factor*$this->text_scale_off*$this->xscale->ticks->minor_step);
	}

	if( $this->iDoClipping ) {
	    $oldimage = $this->img->CloneCanvasH();
	}

	if( ! $this->y2orderback ) {
	    // Stroke all plots for Y1 axis
	    for($i=0; $i < count($this->plots); ++$i) {
		$this->plots[$i]->Stroke($this->img,$this->xscale,$this->yscale);
		$this->plots[$i]->StrokeMargin($this->img);
	    }						
	}

	// Stroke all plots for Y2 axis
	if( $this->y2scale != null )
	    for($i=0; $i< count($this->y2plots); ++$i ) {	
		$this->y2plots[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
	    }		

	if( $this->y2orderback ) {
	    // Stroke all plots for Y1 axis
	    for($i=0; $i < count($this->plots); ++$i) {
		$this->plots[$i]->Stroke($this->img,$this->xscale,$this->yscale);
		$this->plots[$i]->StrokeMargin($this->img);
	    }						
	}


	if( $this->iDoClipping ) {
	    // Clipping only supports graphs at 0 and 90 degrees
	    if( $this->img->a == 0 ) {
		$this->img->CopyCanvasH($oldimage,$this->img->img,
					$this->img->left_margin,$this->img->top_margin,
					$this->img->left_margin,$this->img->top_margin,
					$this->img->plotwidth+1,$this->img->plotheight);
	    }
	    elseif( $this->img->a == 90 ) {
		$adj = ($this->img->height - $this->img->width)/2;
		$this->img->CopyCanvasH($oldimage,$this->img->img,
					$this->img->bottom_margin-$adj,$this->img->left_margin+$adj,
					$this->img->bottom_margin-$adj,$this->img->left_margin+$adj,
					$this->img->plotheight+1,$this->img->plotwidth);
	    }
	    else {
		JpGraphError::Raise('You have enabled clipping. Cliping is only supported for graphs at 0 or 90 degrees rotation. Please adjust you current angle (='.$this->img->a.' degrees) or disable clipping.');
	    }
	    $this->img->Destroy();
	    $this->img->SetCanvasH($oldimage);
	}

	$this->xscale->off=$oldoff;
		
	if( $this->grid_depth == DEPTH_FRONT && !$_csim ) {
	    $this->ygrid->Stroke();
	    $this->xgrid->Stroke();
	}

	// Stroke bands
	if( $this->bands!= null )
	    for($i=0; $i<count($this->bands); ++$i) {
		// Stroke all bands that asks to be in the foreground
		if( $this->bands[$i]->depth == DEPTH_FRONT )
		    $this->bands[$i]->Stroke($this->img,$this->xscale,$this->yscale);
	    }

	// Stroke any lines added
	if( $this->lines != null ) {
	    for($i=0; $i<count($this->lines); ++$i) {
		$this->lines[$i]->Stroke($this->img,$this->xscale,$this->yscale);
	    }
	}
		
	// Finally draw the axis again since some plots may have nagged
	// the axis in the edges.
	if( !$_csim )
	    $this->StrokeAxis();

	if( $this->y2scale != null && !$_csim ) 
	    $this->y2axis->Stroke($this->xscale); 	
		
	if( !$_csim ) {
	    $this->StrokePlotBox();
	}
		
	if( !$_csim ) {
	    // The titles and legends never gets rotated so make sure
	    // that the angle is 0 before stroking them				
	    $aa = $this->img->SetAngle(0);
	    $this->StrokeTitles();
	    $this->footer->Stroke($this->img);
	}

	$this->legend->Stroke($this->img);		

	if( !$_csim ) {

	    $this->StrokeTexts();	
	    $this->img->SetAngle($aa);	
			
	    // Draw an outline around the image map	
	    if(JPG_DEBUG)
		$this->DisplayClientSideaImageMapAreas();		
	    
	    // Adjust the appearance of the image
	    $this->AdjustSaturationBrightnessContrast();

	    // If the filename is given as the special "__handle"
	    // then the image handler is returned and the image is NOT
	    // streamed back
	    if( $aStrokeFileName == _IMG_HANDLER ) {
		return $this->img->img;
	    }
	    else {
		// Finally stream the generated picture					
		$this->cache->PutAndStream($this->img,$this->cache_name,$this->inline,
					   $aStrokeFileName);		
	    }
	}
    }

//---------------
// PRIVATE METHODS	
    function StrokeAxis() {
		
	// Stroke axis
	if( $this->iAxisStyle != AXSTYLE_SIMPLE ) {
	    switch( $this->iAxisStyle ) {
	        case AXSTYLE_BOXIN :
	            $toppos = SIDE_DOWN;
		    $bottompos = SIDE_UP;
	            $leftpos = SIDE_RIGHT;
	            $rightpos = SIDE_LEFT;
	            break;
		case AXSTYLE_BOXOUT :
		    $toppos = SIDE_UP;
	            $bottompos = SIDE_DOWN;	    
	            $leftpos = SIDE_LEFT;
		    $rightpos = SIDE_RIGHT;
	            break;
		case AXSTYLE_YBOXIN:
	            $toppos = -100;
		    $bottompos = SIDE_UP;
	            $leftpos = SIDE_RIGHT;
	            $rightpos = SIDE_LEFT;
		    break;
		case AXSTYLE_YBOXOUT:
		    $toppos = -100;
	            $bottompos = SIDE_DOWN;	    
	            $leftpos = SIDE_LEFT;
		    $rightpos = SIDE_RIGHT;
		    break;
		default:
	            JpGRaphError::Raise('Unknown AxisStyle() : '.$this->iAxisStyle);
	            break;
	    }
	    $this->xaxis->SetPos('min');
	    
	    // By default we hide the first label so it doesn't cross the
	    // Y-axis in case the positon hasn't been set by the user.
	    // However, if we use a box we always want the first value
	    // displayed so we make sure it will be displayed.
	    $this->xscale->ticks->SupressFirst(false);
	    
	    $this->xaxis->SetLabelSide(SIDE_DOWN);
	    $this->xaxis->scale->ticks->SetSide($bottompos);
	    $this->xaxis->Stroke($this->yscale);

	    if( $toppos != -100 ) {
		// To avoid side effects we work on a new copy
		$maxis = $this->xaxis;
		$maxis->SetPos('max');
		$maxis->SetLabelSide(SIDE_UP);
		$maxis->SetLabelMargin(7);
		$this->xaxis->scale->ticks->SetSide($toppos);
		$maxis->Stroke($this->yscale);
	    }

	    $this->yaxis->SetPos('min');
	    $this->yaxis->SetLabelMargin(10);
	    $this->yaxis->SetLabelSide(SIDE_LEFT);
	    $this->yaxis->scale->ticks->SetSide($leftpos);
	    $this->yaxis->Stroke($this->xscale);

	    $myaxis = $this->yaxis;
	    $myaxis->SetPos('max');
	    $myaxis->SetLabelMargin(10);
	    $myaxis->SetLabelSide(SIDE_RIGHT);
	    $myaxis->title->Set('');
	    $myaxis->scale->ticks->SetSide($rightpos);
	    $myaxis->Stroke($this->xscale);
	    
	}
	else {
	    $this->xaxis->Stroke($this->yscale);
	    $this->yaxis->Stroke($this->xscale);		
	}
    }


    // Private helper function for backgound image
    function LoadBkgImage($aImgFormat='',$aFile='') {
	if( $aFile == '' )
	    $aFile = $this->background_image;
	// Remove case sensitivity and setup appropriate function to create image
	// Get file extension. This should be the LAST '.' separated part of the filename
	$e = explode('.',$aFile);
	$ext = strtolower($e[count($e)-1]);
	if ($ext == "jpeg")  {
	    $ext = "jpg";
	}
	
	if( trim($ext) == '' ) 
	    $ext = 'png';  // Assume PNG if no extension specified

	if( $aImgFormat == '' )
	    $imgtag = $ext;
	else
	    $imgtag = $aImgFormat;

	if( $imgtag == "jpg" || $imgtag == "jpeg")
	{
		$f = "imagecreatefromjpeg";
		$imgtag = "jpg";
	}
	else
	{
		$f = "imagecreatefrom".$imgtag;
	}

	// Compare specified image type and file extension
	if( $imgtag != $ext ) {
	    $t = " Background image seems to be of different type (has different file extension)".
		 " than specified imagetype. <br>Specified: '".
		$aImgFormat."'<br>File: '".$aFile."'";
	    JpGraphError::Raise($t);
	}

	$img = @$f($aFile);
	if( !$img ) {
	    JpGraphError::Raise(" Can't read background image: '".$aFile."'");   
	}
	return $img;
    }	

    function StrokeBackgroundGrad() {
	if( $this->bkg_gradtype < 0  ) 
	    return;
	$grad = new Gradient($this->img);
	if( $this->bkg_gradstyle == BGRAD_PLOT ) {
	    $xl = $this->img->left_margin;
	    $yt = $this->img->top_margin;
	    $xr = $xl + $this->img->plotwidth ;
	    $yb = $yt + $this->img->plotheight ;
	}
	else {
	    $xl = 0;
	    $yt = 0;
	    $xr = $xl + $this->img->width - 1;
	    $yb = $yt + $this->img->height - 1;
	}
	if( $this->doshadow  ) {
	    $xr -= $this->shadow_width; 
	    $yb -= $this->shadow_width; 
	}
	$grad->FilledRectangle($xl,$yt,$xr,$yb,
			       $this->bkg_gradfrom,$this->bkg_gradto,
			       $this->bkg_gradtype);
    }

    function StrokeFrameBackground() {
	if( $this->background_image == "" ) 
	    return;

	$bkgimg = $this->LoadBkgImage($this->background_image_format);
	$this->img->_AdjBrightContrast($bkgimg,$this->background_image_bright,
				       $this->background_image_contr);
	$this->img->_AdjSat($bkgimg,$this->background_image_sat);
	$bw = ImageSX($bkgimg);
	$bh = ImageSY($bkgimg);

	// No matter what the angle is we always stroke the image and frame
	// assuming it is 0 degree
	$aa = $this->img->SetAngle(0);
		
	switch( $this->background_image_type ) {
	    case BGIMG_FILLPLOT: // Resize to just fill the plotarea
		$this->StrokeFrame();
		$GLOBALS['copyfunc']($this->img->img,$bkgimg,
				     $this->img->left_margin,$this->img->top_margin,
				     0,0,$this->img->plotwidth,$this->img->plotheight,
				     $bw,$bh);
		break;
	    case BGIMG_FILLFRAME: // Fill the whole area from upper left corner, resize to just fit
		$GLOBALS['copyfunc']($this->img->img,$bkgimg,
				     0,0,0,0,
				     $this->img->width,$this->img->height,
				     $bw,$bh);
		$this->StrokeFrame();
		break;
	    case BGIMG_COPY: // Just copy the image from left corner, no resizing
		$GLOBALS['copyfunc']($this->img->img,$bkgimg,
				     0,0,0,0,
				     $bw,$bh,
				     $bw,$bh);
		$this->StrokeFrame();
		break;
	    case BGIMG_CENTER: // Center original image in the plot area
		$centerx = round($this->img->plotwidth/2+$this->img->left_margin-$bw/2);
		$centery = round($this->img->plotheight/2+$this->img->top_margin-$bh/2);
		$GLOBALS['copyfunc']($this->img->img,$bkgimg,
				     $centerx,$centery,
				     0,0,
				     $bw,$bh,
				     $bw,$bh);
		$this->StrokeFrame();
		break;
	    default:
		JpGraphError::Raise(" Unknown background image layout");
	}			
	$this->img->SetAngle($aa);		
    }

    // Private
    // Draw a frame around the image
    function StrokeFrame() {
	if( !$this->doframe ) return;
	if( $this->background_image_type <= 1 && 
	    ($this->bkg_gradtype < 0 || 
	     ($this->bkg_gradtype > 0 && $this->bkg_gradstyle==BGRAD_PLOT) ) ) 
	    $c = $this->margin_color;
	else
	    $c = false;
	
	if( $this->doshadow ) {
	    $this->img->SetColor($this->frame_color);			
	    $this->img->ShadowRectangle(0,0,$this->img->width,$this->img->height,
					$c,$this->shadow_width,$this->shadow_color);
	}
	elseif( $this->framebevel ) {
	    if( $c ) {
		$this->img->SetColor($this->margin_color);
		$this->img->FilledRectangle(0,0,$this->img->width-1,$this->img->height-1); 
	    }
	    $this->img->Bevel(1,1,$this->img->width-2,$this->img->height-2,
			      $this->framebeveldepth,
			      $this->framebevelcolor1,$this->framebevelcolor2);
	    if( $this->framebevelborder ) {
		$this->img->SetColor($this->framebevelbordercolor);
		$this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);
	    }
	}
	else {
	    $this->img->SetLineWeight($this->frame_weight);
	    if( $c ) {
		$this->img->SetColor($this->margin_color);
		$this->img->FilledRectangle(0,0,$this->img->width-1,$this->img->height-1); 
	    }
	    $this->img->SetColor($this->frame_color);
	    $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);		
	}
	$this->StrokeBackgroundGrad();
    }

    // Stroke the plot area with either a solid color or a background image
    function StrokePlotArea() {
	// Note: To be consistent we really should take a possible shadow
	// into account. However, that causes some problem for the LinearScale class
	// since in the current design it does not have any links to class Graph which
	// means it has no way of compensating for the adjusted plotarea in case of a 
	// shadow. So, until I redesign LinearScale we can't compensate for this.
	// So just set the two adjustment parameters to zero for now.
	$boxadj = 0; //$this->doframe ? $this->frame_weight : 0 ;
	$adj = 0; //$this->doshadow ? $this->shadow_width : 0 ;

	if( $this->background_image != "" ) {
	    $this->StrokeFrameBackground();
	}
	else {
	    $aa = $this->img->SetAngle(0);
	    $this->StrokeFrame();
	    if( $this->bkg_gradtype < 0 || 
		($this->bkg_gradtype > 0 && $this->bkg_gradstyle==BGRAD_MARGIN ) ) {
		$this->img->SetAngle($aa);			
		$this->img->PushColor($this->plotarea_color);
		$this->img->FilledRectangle($this->img->left_margin+$boxadj,
					    $this->img->top_margin+$boxadj,
					    $this->img->width-$this->img->right_margin-$adj-2*$boxadj,
					    $this->img->height-$this->img->bottom_margin-$adj-2*$boxadj);	
		$this->img->PopColor();
	    }
	}	
    }	
	
	
    function StrokePlotBox() {
	// Should we draw a box around the plot area?
	if( $this->boxed ) {
	    $this->img->SetLineWeight($this->box_weight);
	    $this->img->SetColor($this->box_color);
	    $this->img->Rectangle(
		$this->img->left_margin,$this->img->top_margin,
		$this->img->width-$this->img->right_margin,
		$this->img->height-$this->img->bottom_margin);
	}						
    }		

    function SetTitleBackgroundFillStyle($aStyle,$aColor1='black',$aColor2='white') {
	$this->titlebkg_fillstyle = $aStyle;
	$this->titlebkg_scolor1 = $aColor1;
	$this->titlebkg_scolor2 = $aColor2;
    }

    function SetTitleBackground($aBackColor='gray', $aStyle=TITLEBKG_STYLE1, $aFrameStyle=TITLEBKG_FRAME_NONE, $aFrameColor='black', $aFrameWeight=1, $aBevelHeight=3, $aEnable=true) {
	$this->titlebackground = $aEnable;
	$this->titlebackground_color = $aBackColor;
	$this->titlebackground_style = $aStyle;
	$this->titlebackground_framecolor = $aFrameColor;
	$this->titlebackground_framestyle = $aFrameStyle;
	$this->titlebackground_frameweight = $aFrameWeight;	
	$this->titlebackground_bevelheight = $aBevelHeight ;
    }


    function StrokeTitles() {

	$margin=3;

	if( $this->titlebackground ) {

	    // Find out height
	    $this->title->margin += 2 ;
	    $h = $this->title->GetTextHeight($this->img)+$this->title->margin+$margin;
	    if( $this->subtitle->t != "" && !$this->subtitle->hide ) {
		$h += $this->subtitle->GetTextHeight($this->img)+$margin+
		    $this->subtitle->margin;
	    }
	    if( $this->subsubtitle->t != "" && !$this->subsubtitle->hide ) {
		$h += $this->subsubtitle->GetTextHeight($this->img)+$margin+
		    $this->subsubtitle->margin;
	    }
	    $this->img->PushColor($this->titlebackground_color);
	    if( $this->titlebackground_style === 1 ) {
		// Inside the frame
		if( $this->framebevel ) {
		    $x1 = $y1 = $this->framebeveldepth + 1 ;
		    $x2 = $this->img->width - $this->framebeveldepth - 2 ; 
		    $this->title->margin += $this->framebeveldepth + 1 ;
		    $h += $y1 ;
		}
		else {
		    $x1 = $y1 = $this->frame_weight;
		    $x2 = $this->img->width - 2*$x1;
		}
	    }
	    elseif( $this->titlebackground_style === 2 ) {
		// Cover the frame as well
		$x1 = $y1 = 0;
		$x2 = $this->img->width - 1 ;
	    }
	    elseif( $this->titlebackground_style === 3) {
		// Cover the frame as well (the difference is that
		// for style==3 a bevel frame border is on top
		// of the title background)
		$x1 = $y1 = 0;
		$x2 = $this->img->width - 1 ;
		$h += $this->framebeveldepth ;
		$this->title->margin += $this->framebeveldepth ;
	    }
	    else {
		JpGraphError::Raise('Unknown title background style.');
	    }

	    if( $this->titlebackground_framestyle === 3 ) {
		$h += $this->titlebackground_bevelheight*2 + 1  ;
		$this->title->margin += $this->titlebackground_bevelheight ;
	    }

	    if( $this->doshadow ) {
		$x2 -= $this->shadow_width ;
	    }
	    
	    if( $this->titlebkg_fillstyle==TITLEBKG_FILLSTYLE_HSTRIPED ) {
		$this->img->FilledRectangle2($x1,$y1,$x2,$h,
					     $this->titlebkg_scolor1,
					     $this->titlebkg_scolor2);
	    }
	    elseif( $this->titlebkg_fillstyle==TITLEBKG_FILLSTYLE_VSTRIPED ) {
		$this->img->FilledRectangle2($x1,$y1,$x2,$h,
					     $this->titlebkg_scolor1,
					     $this->titlebkg_scolor2,2);
	    }
	    else {
		// Solid fill
		$this->img->FilledRectangle($x1,$y1,$x2,$h);
	    }
	    $this->img->PopColor();

	    $this->img->PushColor($this->titlebackground_framecolor);
	    $this->img->SetLineWeight($this->titlebackground_frameweight);
	    if( $this->titlebackground_framestyle == 1 ) {
		// Frame background
		$this->img->Rectangle($x1,$y1,$x2,$h);
	    }
	    elseif( $this->titlebackground_framestyle == 2 ) {
		// Bottom line only
		$this->img->Line($x1,$h,$x2,$h);
	    }
	    elseif( $this->titlebackground_framestyle == 3 ) {
		$this->img->Bevel($x1,$y1,$x2,$h,$this->titlebackground_bevelheight);
	    }
	    $this->img->PopColor();

	    // This is clumsy. But we neeed to stroke the whole graph frame if it is
	    // set to bevel to get the bevel shading on top of the text background
	    if( $this->framebevel && $this->doframe && 
		$this->titlebackground_style === 3 ) {
		$this->img->Bevel(1,1,$this->img->width-2,$this->img->height-2,
				  $this->framebeveldepth,
				  $this->framebevelcolor1,$this->framebevelcolor2);
		if( $this->framebevelborder ) {
		    $this->img->SetColor($this->framebevelbordercolor);
		    $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);
		}
	    }
	}

	// Stroke title
	$y = $this->title->margin; 
	$this->title->Center(0,$this->img->width,$y);
	$this->title->Stroke($this->img);
		
	// ... and subtitle
	$y += $this->title->GetTextHeight($this->img) + $margin + $this->subtitle->margin;
	$this->subtitle->Center(0,$this->img->width,$y);	
	$this->subtitle->Stroke($this->img);

	// ... and subsubtitle
	$y += $this->subtitle->GetTextHeight($this->img) + $margin + $this->subsubtitle->margin;
	$this->subsubtitle->Center(0,$this->img->width,$y);
	$this->subsubtitle->Stroke($this->img);

	// ... and fancy title
	$this->tabtitle->Stroke($this->img);

    }

    function StrokeTexts() {
	// Stroke any user added text objects
	if( $this->texts != null ) {
	    for($i=0; $i<count($this->texts); ++$i) {
		$this->texts[$i]->StrokeWithScale($this->img,$this->xscale,$this->yscale);
	    }
	}
    }

    function DisplayClientSideaImageMapAreas() {
	// Debug stuff - display the outline of the image map areas
	foreach ($this->plots as $p) {
	    $csim.= $p->GetCSIMareas();
	}
	$csim .= $this->legend->GetCSIMareas();
	if (preg_match_all("/area shape=\"(\w+)\" coords=\"([0-9\, ]+)\"/", $csim, $coords)) {
	    $this->img->SetColor($this->csimcolor);
	    for ($i=0; $i<count($coords[0]); $i++) {
		if ($coords[1][$i]=="poly") {
		    preg_match_all('/\s*([0-9]+)\s*,\s*([0-9]+)\s*,*/',$coords[2][$i],$pts);
		    $this->img->SetStartPoint($pts[1][count($pts[0])-1],$pts[2][count($pts[0])-1]);
		    for ($j=0; $j<count($pts[0]); $j++) {
			$this->img->LineTo($pts[1][$j],$pts[2][$j]);
		    }
		} else if ($coords[1][$i]=="rect") {
		    $pts = preg_split('/,/', $coords[2][$i]);
		    $this->img->SetStartPoint($pts[0],$pts[1]);
		    $this->img->LineTo($pts[2],$pts[1]);
		    $this->img->LineTo($pts[2],$pts[3]);
		    $this->img->LineTo($pts[0],$pts[3]);
		    $this->img->LineTo($pts[0],$pts[1]);					
		}
	    }
	}
    }

    function AdjustSaturationBrightnessContrast() {
	// Adjust the brightness and contrast of the image
	if( $this->image_contr || $this->image_bright )
	    $this->img->AdjBrightContrast($this->image_bright,$this->image_contr);
	if( $this->image_sat )											 
	    $this->img->AdjSat($this->image_sat);
    }

    // Text scale offset in world coordinates
    function SetTextScaleOff($aOff) {
	$this->text_scale_off = $aOff;
	$this->xscale->text_scale_off = $aOff;
    }

    // Get Y min and max values for added lines
    function GetLinesYMinMax( $aLines ) {
	$n = count($aLines);
	if( $n == 0 ) return false;
	$min = $aLines[0]->scaleposition ;
	$max = $min ;
	$flg = false;
	for( $i=0; $i < $n; ++$i ) {
	    if( $aLines[$i]->direction == HORIZONTAL ) {
		$flg = true ;
		$v = $aLines[$i]->scaleposition ;
		if( $min > $v ) $min = $v ;
		if( $max < $v ) $max = $v ;
	    }
	}
	return $flg ? array($min,$max) : false ;
    }

    // Get X min and max values for added lines
    function GetLinesXMinMax( $aLines ) {
	$n = count($aLines);
	if( $n == 0 ) return false ;
	$min = $aLines[0]->scaleposition ;
	$max = $min ;
	$flg = false;
	for( $i=0; $i < $n; ++$i ) {
	    if( $aLines[$i]->direction == VERTICAL ) {
		$flg = true ;
		$v = $aLines[$i]->scaleposition ;
		if( $min > $v ) $min = $v ;
		if( $max < $v ) $max = $v ;
	    }
	}
	return $flg ? array($min,$max) : false ;
    }

    // Get min and max values for all included plots
    function GetPlotsYMinMax(&$aPlots) {
	list($xmax,$max) = $aPlots[0]->Max();
	list($xmin,$min) = $aPlots[0]->Min();
	for($i=0; $i<count($aPlots); ++$i ) {
	    list($xmax,$ymax)=$aPlots[$i]->Max();
	    list($xmin,$ymin)=$aPlots[$i]->Min();
	    if (!is_string($ymax) || $ymax != "") $max=max($max,$ymax);
	    if (!is_string($ymin) || $ymin != "") $min=min($min,$ymin);
	}
	if( $min == "" ) $min = 0;
	if( $max == "" ) $max = 0;
	if( $min == 0 && $max == 0 ) {
	    // Special case if all values are 0
	    $min=0;$max=1;			
	}
	return array($min,$max);
    }

} // Class


//===================================================
// CLASS TTF
// Description: Handle TTF font names
//===================================================
class TTF {
    var $font_files,$style_names;
//---------------
// CONSTRUCTOR
    function TTF() {
	$this->style_names=array(FS_NORMAL=>'normal',FS_BOLD=>'bold',FS_ITALIC=>'italic',FS_BOLDITALIC=>'bolditalic');
	// File names for available fonts
	$this->font_files=array(
	    FF_COURIER => array(FS_NORMAL=>'cour', FS_BOLD=>'courbd', FS_ITALIC=>'couri', FS_BOLDITALIC=>'courbi' ),
	    FF_GEORGIA => array(FS_NORMAL=>'georgia', FS_BOLD=>'georgiab', FS_ITALIC=>'georgiai', FS_BOLDITALIC=>'' ),
	    FF_TREBUCHE =>array(FS_NORMAL=>'trebuc', FS_BOLD=>'trebucbd',   FS_ITALIC=>'trebucit', FS_BOLDITALIC=>'trebucbi' ),
	    FF_VERDANA => array(FS_NORMAL=>'verdana', FS_BOLD=>'verdanab',  FS_ITALIC=>'verdanai', FS_BOLDITALIC=>'' ),
	    FF_TIMES =>   array(FS_NORMAL=>'times',   FS_BOLD=>'timesbd',   FS_ITALIC=>'timesi',   FS_BOLDITALIC=>'timesbi' ),
	    FF_COMIC =>   array(FS_NORMAL=>'comic',   FS_BOLD=>'comicbd',   FS_ITALIC=>'',         FS_BOLDITALIC=>'' ),
	    FF_ARIAL =>   array(FS_NORMAL=>'arial',   FS_BOLD=>'arialbd',   FS_ITALIC=>'ariali',   FS_BOLDITALIC=>'arialbi' ) ,
	    FF_SIMSUN =>   array(FS_NORMAL=>'simsun',   FS_BOLD=>'simhei',   FS_ITALIC=>'simsun',   FS_BOLDITALIC=>'simhei' )	    
);
    }

//---------------
// PUBLIC METHODS	
    // Create the TTF file from the font specification
    function File($family,$style=FS_NORMAL) {
	
	if( $family == FF_HANDWRT || $family==FF_BOOK )
	    JpGraphError::Raise('Font families FF_HANDWRT and FF_BOOK are no longer available due to copyright problem with these fonts. Fonts can no longer be distributed with JpGraph. Please download fonts from http://corefonts.sourceforge.net/');

	$fam = @$this->font_files[$family];
	if( !$fam ) JpGraphError::Raise("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/");
	$f = @$fam[$style];

	if( $f==='' )
	    JpGraphError::Raise('Style "'.$this->style_names[$style].'" is not available for font family '.$this->font_files[$family][FS_NORMAL].'.');
	if( !$f )
	    JpGraphError::Raise("Unknown font style specification [$fam].");
	$f = TTF_DIR.$f.'.ttf';
	if( file_exists($f) === false || is_readable($f) === false ) {
	    JpGraphError::Raise("Font file \"$f\" is not readable or does not exist.");
	}
	return $f;
    }
} // Class

//===================================================
// CLASS LineProperty
// Description: Holds properties for a line
//===================================================
class LineProperty {
    var $iWeight=1, $iColor="black",$iStyle="solid";
    var $iShow=true;
	
//---------------
// PUBLIC METHODS	
    function SetColor($aColor) {
	$this->iColor = $aColor;
    }
	
    function SetWeight($aWeight) {
	$this->iWeight = $aWeight;
    }
	
    function SetStyle($aStyle) {
	$this->iStyle = $aStyle;
    }
		
    function Show($aShow=true) {
	$this->iShow=$aShow;
    }
	
    function Stroke($aImg,$aX1,$aY1,$aX2,$aY2) {
	if( $this->iShow ) {
	    $aImg->SetColor($this->iColor);
	    $aImg->SetLineWeight($this->iWeight);
	    $aImg->SetLineStyle($this->iStyle);			
	    $aImg->StyleLine($aX1,$aY1,$aX2,$aY2);
	}
    }
}


//===================================================
// CLASS Text
// Description: Arbitrary text object that can be added to the graph
//===================================================
class Text {
    var $t,$x=0,$y=0,$halign="left",$valign="top",$color=array(0,0,0);
    var $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12;
    var $hide=false, $dir=0;
    var $